Skip to content

Commit 66b7266

Browse files
committed
misc work
1 parent a554f06 commit 66b7266

9 files changed

Lines changed: 525 additions & 20 deletions

File tree

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@
1111
"dependencies": {
1212
"@livekit/components-react": "^0.2.3",
1313
"@next/font": "13.1.5",
14+
"@pixi/react": "^7.0.0-alpha.1",
1415
"@types/node": "18.11.18",
1516
"@types/react": "18.0.27",
1617
"@types/react-dom": "18.0.10",
1718
"add": "^2.0.6",
19+
"daisyui": "^2.49.0",
1820
"eslint": "8.32.0",
1921
"eslint-config-next": "13.1.5",
2022
"livekit-client": "^1.6.3",
2123
"livekit-server-sdk": "^1.1.0",
2224
"next": "13.1.5",
25+
"pixi.js": "^7.1.1",
2326
"react": "18.2.0",
2427
"react-dom": "18.2.0",
28+
"react-hot-toast": "^2.4.0",
29+
"set-interval-async": "^3.0.3",
2530
"typescript": "4.9.4",
2631
"yarn": "^1.22.19"
2732
},

src/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ export default function RootLayout({
66
children: React.ReactNode;
77
}) {
88
return (
9-
<html lang="en">
9+
<html lang="en" data-theme="cyberpunk">
1010
{/*
1111
<head /> will contain the components returned by the nearest parent
1212
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
1313
*/}
1414
<head />
15-
<body className="bg-gray-800">
15+
<body>
1616
<div>{children}</div>
1717
</body>
1818
</html>

src/app/page.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,46 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { useCallback, useState } from "react";
5+
import { toast, Toaster } from "react-hot-toast";
6+
17
export default function Home() {
8+
const router = useRouter();
9+
const [roomName, setRoomName] = useState("");
10+
11+
const joinRoom = useCallback(() => {
12+
if (roomName === "") {
13+
toast.error("Please enter a room name");
14+
return;
15+
}
16+
router.push(`/room/${roomName}`);
17+
}, [roomName, router]);
18+
219
return (
320
<main>
4-
<div className="font-bold bg-red-400">Hello World</div>
21+
<Toaster />
22+
<div className="flex flex-col items-center justify-center h-screen w-screen">
23+
<h1 className="text-4xl mb-8">Spatial Audio LiveKit Example App</h1>
24+
<form
25+
onSubmit={(e) => {
26+
e.preventDefault();
27+
joinRoom();
28+
}}
29+
>
30+
<div className="form-control">
31+
<div className="input-group">
32+
<input
33+
value={roomName}
34+
onChange={(e) => setRoomName(e.currentTarget.value)}
35+
type="text"
36+
placeholder="Room Name"
37+
className="input input-bordered input-secondary"
38+
/>
39+
<button className="btn">Enter Room</button>
40+
</div>
41+
</div>
42+
</form>
43+
</div>
544
</main>
645
);
746
}

src/app/room/[room_name]/page.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import { GameView } from "@/components/GameView";
44
import { ParticipantList } from "@/components/ParticipantList";
5+
import { RoomInfo } from "@/components/RoomInfo";
56
import { UsernameInput } from "@/components/UsernameInput";
67
import {
78
ConnectionDetails,
89
ConnectionDetailsBody,
910
} from "@/pages/api/connection_details";
1011
import { LiveKitRoom } from "@livekit/components-react";
11-
import { useCallback, useState } from "react";
12+
import { useCallback, useMemo, useState } from "react";
13+
import { toast } from "react-hot-toast";
1214

1315
type Props = {
1416
params: { room_name: string };
@@ -18,6 +20,10 @@ export default function Page({ params: { room_name } }: Props) {
1820
const [connectionDetails, setConnectionDetails] =
1921
useState<ConnectionDetails | null>(null);
2022

23+
const humanRoomName = useMemo(() => {
24+
return decodeURI(room_name);
25+
}, [room_name]);
26+
2127
const requestConnectionDetails = useCallback(
2228
async (username: string) => {
2329
const body: ConnectionDetailsBody = { room_name, username };
@@ -38,8 +44,10 @@ export default function Page({ params: { room_name } }: Props) {
3844
// If we don't have any connection details yet, show the username form
3945
if (connectionDetails === null) {
4046
return (
41-
<div>
42-
<h2>{room_name}</h2>
47+
<div className="w-screen h-screen flex flex-col items-center justify-center">
48+
<h2 className="text-4xl mb-4">{humanRoomName}</h2>
49+
<RoomInfo roomName={room_name} />
50+
<div className="divider"></div>
4351
<UsernameInput
4452
submitText="Join Room"
4553
onSubmit={async (username) => {

src/components/RoomInfo.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { RoomInfo } from "@/pages/api/room_info/[room]";
2+
import { useCallback, useEffect, useMemo, useState } from "react";
3+
import { setIntervalAsync, clearIntervalAsync } from "set-interval-async";
4+
5+
type Props = {
6+
roomName: string;
7+
};
8+
9+
const DEFAULT_ROOM_INFO: RoomInfo = { num_participants: 0 };
10+
11+
export function RoomInfo({ roomName }: Props) {
12+
const [roomInfo, setRoomInfo] = useState<RoomInfo>(DEFAULT_ROOM_INFO);
13+
14+
const fetchRoomInfo = useCallback(async () => {
15+
const res = await fetch(`/api/room_info/${roomName}`);
16+
const roomInfo = (await res.json()) as RoomInfo;
17+
setRoomInfo(roomInfo);
18+
}, [roomName]);
19+
20+
useEffect(() => {
21+
fetchRoomInfo();
22+
const interval = setIntervalAsync(fetchRoomInfo, 1000);
23+
return () => {
24+
clearIntervalAsync(interval);
25+
};
26+
}, [fetchRoomInfo]);
27+
28+
return (
29+
<div>
30+
<div className="flex flex-col items-center">
31+
<span className="countdown font-mono text-6xl">
32+
<span style={{ "--value": roomInfo.num_participants } as any}></span>
33+
</span>
34+
<div className="text-xs">Participants currently in room</div>
35+
</div>
36+
</div>
37+
);
38+
}

src/components/UsernameInput.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,23 @@ export function UsernameInput({ submitText, onSubmit }: Props) {
99
const [username, setUsername] = useState("");
1010
return (
1111
<form
12-
className="flex"
1312
onSubmit={(e) => {
1413
e.preventDefault();
1514
onSubmit(username);
1615
}}
1716
>
18-
<input
19-
className="grow-3"
20-
placeholder="Enter your username"
21-
value={username}
22-
onChange={(e) => setUsername(e.currentTarget.value)}
23-
type="text"
24-
/>
25-
<button onClick={() => setUsername(username)} className="grow-1">
26-
{submitText}
27-
</button>
17+
<div className="form-control">
18+
<div className="input-group">
19+
<input
20+
value={username}
21+
onChange={(e) => setUsername(e.currentTarget.value)}
22+
type="text"
23+
placeholder="Username"
24+
className="input input-bordered input-secondary"
25+
/>
26+
<button className="btn">Join</button>
27+
</div>
28+
</div>
2829
</form>
2930
);
3031
}

src/pages/api/room_info/[room].ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2+
import type { NextApiRequest, NextApiResponse } from "next";
3+
import { RoomServiceClient } from "livekit-server-sdk";
4+
5+
export type RoomInfo = {
6+
num_participants: number;
7+
};
8+
9+
type ErrorResponse = {
10+
error: string;
11+
};
12+
13+
type Query = {
14+
room: string;
15+
};
16+
17+
export default async function handler(
18+
req: NextApiRequest,
19+
res: NextApiResponse<RoomInfo | ErrorResponse>
20+
) {
21+
if (req.method !== "GET") {
22+
return res.status(400).json({ error: "Invalid method" });
23+
}
24+
25+
const apiKey = process.env.LIVEKIT_API_KEY;
26+
const apiSecret = process.env.LIVEKIT_API_SECRET;
27+
const wsUrl = process.env.LIVEKIT_WS_URL;
28+
const { room } = req.query as Query;
29+
30+
if (!apiKey || !apiSecret || !wsUrl) {
31+
return res.status(500).json({ error: "Server misconfigured" });
32+
}
33+
34+
const livekitHost = wsUrl?.replace("wss://", "https://");
35+
const roomService = new RoomServiceClient(livekitHost, apiKey, apiSecret);
36+
37+
try {
38+
const participants = await roomService.listParticipants(room);
39+
return res.status(200).json({ num_participants: participants.length });
40+
} catch {
41+
return res.status(200).json({ num_participants: 0 });
42+
}
43+
}

tailwind.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ module.exports = {
66
theme: {
77
extend: {},
88
},
9-
plugins: [],
9+
plugins: [require('daisyui')],
1010
}

0 commit comments

Comments
 (0)