Skip to content

Commit 28705d2

Browse files
committed
feat: implement audio stream and voice room pages
1 parent ae07fd7 commit 28705d2

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

frontend/src/pages/StreamRoom.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useParams, useSearchParams, useNavigate } from 'react-router-dom';
2+
import { useMockRoom } from '@/hooks/useMockRoom';
3+
import { ControlBar } from '@/components/room/ControlBar';
4+
import { UserList } from '@/components/room/UserList';
5+
import { ChatArea } from '@/components/room/ChatArea';
6+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
7+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
8+
import { Mic } from 'lucide-react';
9+
10+
const StreamRoom = () => {
11+
const { id } = useParams();
12+
const [searchParams] = useSearchParams();
13+
const navigate = useNavigate();
14+
const role = searchParams.get('role') === 'host' ? 'host' : 'listener';
15+
16+
const {
17+
status,
18+
me,
19+
users,
20+
messages,
21+
toggleMic,
22+
sendMessage,
23+
goLive,
24+
endStream
25+
} = useMockRoom(id || 'default', 'stream', role);
26+
27+
const host = users.find(u => u.role === 'host');
28+
29+
return (
30+
<div className="flex flex-col h-[calc(100vh-65px)] overflow-hidden bg-background">
31+
<div className="flex-1 flex overflow-hidden">
32+
{/* Main Stage */}
33+
<div className="flex-1 flex items-center justify-center p-6 relative">
34+
{/* Background Blob */}
35+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[300px] h-[300px] bg-primary/20 blur-[100px] rounded-full pointer-events-none" />
36+
37+
{status === 'live' ? (
38+
<div className="flex flex-col items-center gap-6 z-10 animate-in fade-in zoom-in duration-500">
39+
<div className="relative">
40+
<Avatar className="h-40 w-40 border-4 border-primary ring-4 ring-primary/20">
41+
<AvatarImage src={host?.avatar} />
42+
<AvatarFallback className="text-4xl">{host?.name[0] || "H"}</AvatarFallback>
43+
</Avatar>
44+
{host?.isSpeaking && !host?.isMuted && (
45+
<span className="absolute inset-0 rounded-full border-4 border-green-500 animate-ping opacity-75" />
46+
)}
47+
<div className="absolute bottom-2 right-2 bg-primary text-primary-foreground p-2 rounded-full shadow-lg">
48+
<Mic className="h-6 w-6" />
49+
</div>
50+
</div>
51+
<div className="text-center space-y-2">
52+
<h2 className="text-3xl font-bold">{host?.name || "Broadcaster"} is live</h2>
53+
<p className="text-xl text-muted-foreground animate-pulse">Listening...</p>
54+
</div>
55+
</div>
56+
) : (
57+
<div className="text-center z-10 space-y-4">
58+
<h2 className="text-2xl font-semibold text-muted-foreground">Stream has not started yet</h2>
59+
{role === 'host' && (
60+
<p className="text-sm">You are the host. Click "Start Broadcast" below when ready.</p>
61+
)}
62+
</div>
63+
)}
64+
</div>
65+
66+
{/* Sidebar */}
67+
<div className="w-full md:w-[350px] border-l bg-background flex flex-col">
68+
<Tabs defaultValue="chat" className="flex-1 flex flex-col">
69+
<div className="px-4 pt-2">
70+
<TabsList className="w-full">
71+
<TabsTrigger value="chat" className="flex-1">Chat</TabsTrigger>
72+
<TabsTrigger value="users" className="flex-1">Users ({users.length})</TabsTrigger>
73+
</TabsList>
74+
</div>
75+
<TabsContent value="chat" className="flex-1 h-0 data-[state=active]:flex flex-col mt-0 border-0">
76+
<ChatArea messages={messages} onSendMessage={sendMessage} />
77+
</TabsContent>
78+
<TabsContent value="users" className="flex-1 h-0 data-[state=active]:flex flex-col mt-0 border-0">
79+
<UserList users={users} currentUser={me} />
80+
</TabsContent>
81+
</Tabs>
82+
</div>
83+
</div>
84+
85+
{/* Control Bar */}
86+
<ControlBar
87+
user={me}
88+
status={status}
89+
onToggleMic={toggleMic}
90+
onLeave={() => navigate('/')}
91+
onGoLive={goLive}
92+
onEndStream={endStream}
93+
/>
94+
</div>
95+
);
96+
};
97+
98+
export default StreamRoom;

frontend/src/pages/VoiceRoom.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useParams, useNavigate } from 'react-router-dom';
2+
import { useMockRoom } from '@/hooks/useMockRoom';
3+
import { ControlBar } from '@/components/room/ControlBar';
4+
import { UserList } from '@/components/room/UserList';
5+
import { ChatArea } from '@/components/room/ChatArea';
6+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
7+
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
8+
import { Button } from "@/components/ui/button";
9+
import { MessageSquare, Users } from 'lucide-react';
10+
11+
const VoiceRoom = () => {
12+
const { id } = useParams();
13+
const navigate = useNavigate();
14+
15+
// In Voice Room, everyone is a participant
16+
const {
17+
me,
18+
users,
19+
messages,
20+
toggleMic,
21+
sendMessage
22+
} = useMockRoom(id || 'default', 'voice', 'participant');
23+
24+
return (
25+
<div className="flex flex-col h-[calc(100vh-65px)] bg-background">
26+
<div className="flex-1 flex overflow-hidden relative">
27+
28+
{/* Main Grid */}
29+
<div className="flex-1 p-6 overflow-y-auto">
30+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 max-w-6xl mx-auto">
31+
{users.map((user) => (
32+
<div key={user.id} className="aspect-square bg-muted/30 rounded-2xl flex flex-col items-center justify-center relative border-2 border-transparent transition-all hover:bg-muted/50">
33+
{user.isSpeaking && !user.isMuted && (
34+
<div className="absolute inset-0 border-2 border-green-500 rounded-2xl animate-pulse shadow-[0_0_15px_rgba(34,197,94,0.3)]" />
35+
)}
36+
<Avatar className="h-24 w-24 mb-4">
37+
<AvatarImage src={user.avatar} />
38+
<AvatarFallback className="text-2xl">{user.name[0]}</AvatarFallback>
39+
</Avatar>
40+
<span className="font-semibold">{user.name}</span>
41+
{user.isMuted && <span className="text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded-full mt-2">Muted</span>}
42+
</div>
43+
))}
44+
</div>
45+
</div>
46+
47+
{/* Mobile/Desktop Drawers for Chat/Users (Different from Stream Layout for variety) */}
48+
<div className="absolute top-4 right-4 flex flex-col gap-2 md:hidden">
49+
<Sheet>
50+
<SheetTrigger asChild>
51+
<Button size="icon" variant="outline"><MessageSquare className="h-5 w-5" /></Button>
52+
</SheetTrigger>
53+
<SheetContent className="w-[85%] sm:w-[350px] p-0">
54+
<ChatArea messages={messages} onSendMessage={sendMessage} />
55+
</SheetContent>
56+
</Sheet>
57+
<Sheet>
58+
<SheetTrigger asChild>
59+
<Button size="icon" variant="outline"><Users className="h-5 w-5" /></Button>
60+
</SheetTrigger>
61+
<SheetContent side="right" className="w-[85%] sm:w-[350px] p-0">
62+
<UserList users={users} currentUser={me} />
63+
</SheetContent>
64+
</Sheet>
65+
</div>
66+
67+
{/* Desktop Sidebar (Optional, maybe just keep grid focused? No, need chat in voice room too) */}
68+
<div className="hidden md:flex w-[350px] border-l flex-col bg-background">
69+
<ChatArea messages={messages} onSendMessage={sendMessage} />
70+
</div>
71+
72+
</div>
73+
74+
<ControlBar
75+
user={me}
76+
status={'live'} // Voice rooms are always "live" effectively
77+
onToggleMic={toggleMic}
78+
onLeave={() => navigate('/')}
79+
/>
80+
</div>
81+
);
82+
};
83+
84+
export default VoiceRoom;

0 commit comments

Comments
 (0)