Web (Next.js)¶
The Tele part of a web integration is one fetch. Media runs on the livekit-client SDK, shown below.
Install¶
Flow¶
- Your page gets a Tele user token from your backend (Authentication).
- It calls the Tele join endpoint.
- It connects a media
Roomwith the returnedws_url,tokenandice_servers.
Example¶
app/call/[code]/CallView.tsx
"use client";
import { useEffect, useMemo, useState } from "react";
import { Room } from "livekit-client";
import { RoomContext, VideoConference, RoomAudioRenderer, StartAudio } from "@livekit/components-react";
import "@livekit/components-styles";
const TELE_API = process.env.NEXT_PUBLIC_TELE_API!; // https://tele-api.cloudguys.io
async function teleJoin(code: string) {
const { token } = await fetch("/api/tele-token", { method: "POST" }).then((r) => r.json()); // your backend
const res = await fetch(`${TELE_API}/v1/invites/${encodeURIComponent(code)}/join`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "X-Client-Platform": "web" },
});
const body = await res.json();
if (!res.ok) throw body.error; // { code, message, opens_at? }, see Errors
return body as { ws_url: string; token: string; ice_servers: RTCIceServer[] | null; role: string };
}
export default function CallView({ code }: { code: string }) {
const room = useMemo(() => new Room({ adaptiveStream: true, dynacast: true }), []);
const [error, setError] = useState<{ code: string; message: string }>();
useEffect(() => {
let cancelled = false;
teleJoin(code)
.then(async (join) => {
if (cancelled) return;
await room.connect(join.ws_url, join.token, {
// Tele's TURN servers replace the SDK defaults; null → let the SDK use its own.
rtcConfig: join.ice_servers ? { iceServers: join.ice_servers } : undefined,
});
await room.localParticipant.enableCameraAndMicrophone();
})
.catch(setError);
return () => {
cancelled = true;
room.disconnect();
};
}, [room, code]);
if (error) return <p>Can't join: {error.message}</p>;
return (
<RoomContext.Provider value={room}>
<VideoConference />
<RoomAudioRenderer />
<StartAudio label="Click to enable audio" />
</RoomContext.Provider>
);
}
app/call/[code]/page.tsx
import dynamic from "next/dynamic";
const CallView = dynamic(() => import("./CallView"), { ssr: false });
export default async function Page({ params }: { params: Promise<{ code: string }> }) {
return <CallView code={(await params).code} />;
}
Gotchas¶
- Client-only: keep everything media-related in
"use client"components and load the call view withssr: false. - HTTPS: browsers allow camera and microphone only on HTTPS (and
localhost). - Safari autoplay: render
<StartAudio/>, or callroom.startAudio()after a user gesture. - React StrictMode: in development, effects mount twice. The
cancelledflag above prevents a double connect;useSequentialRoomConnectDisconnectfrom@livekit/components-reactis an alternative. - Reconnect: on
RoomEvent.Disconnectedwith a reason other than a deliberate leave, callteleJoinagain with a fresh token. See Reconnects. - Chat: see Chat.
VideoConferenceincludes a chat panel that useslk.chat.