From f5143188ba2d3272342b08142ae06c56d67d861a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?B=C5=82az=CC=87ej=20Pankowski?=
<86720177+pblazej@users.noreply.github.com>
Date: Wed, 3 Dec 2025 13:21:45 +0100
Subject: [PATCH 01/19] Use session
---
examples/nextjs/pages/audio-only.tsx | 41 ++++++++-----
examples/nextjs/pages/clubhouse.tsx | 62 +++++++++++--------
examples/nextjs/pages/customize.tsx | 60 ++++++++++++-------
examples/nextjs/pages/e2ee.tsx | 59 +++++++++++--------
examples/nextjs/pages/minimal.tsx | 52 ++++++++--------
examples/nextjs/pages/processors.tsx | 51 ++++++++++------
examples/nextjs/pages/simple.tsx | 55 ++++++++++-------
examples/nextjs/pages/voice-assistant.tsx | 72 ++++++++++++-----------
packages/react/src/hooks/useSession.ts | 31 ++++++----
9 files changed, 288 insertions(+), 195 deletions(-)
diff --git a/examples/nextjs/pages/audio-only.tsx b/examples/nextjs/pages/audio-only.tsx
index 803eadfde..e91db0aef 100644
--- a/examples/nextjs/pages/audio-only.tsx
+++ b/examples/nextjs/pages/audio-only.tsx
@@ -1,32 +1,45 @@
'use client';
-import { AudioConference, LiveKitRoom, useToken } from '@livekit/components-react';
+import { AudioConference, SessionProvider, useSession } from '@livekit/components-react';
import type { NextPage } from 'next';
import { generateRandomUserId } from '../lib/helper';
-import { useState } from 'react';
+import { useMemo, useState, useEffect } from 'react';
+import { TokenSource } from 'livekit-client';
const AudioExample: NextPage = () => {
const params = typeof window !== 'undefined' ? new URLSearchParams(location.search) : null;
const roomName = params?.get('room') ?? 'test-room';
const [userIdentity] = useState(params?.get('user') ?? generateRandomUserId());
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, {
- userInfo: {
- identity: userIdentity,
- name: userIdentity,
- },
+ const tokenSource = useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
+ }, []);
+
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: userIdentity,
+ participantName: userIdentity,
});
+ useEffect(() => {
+ session.start({
+ tracks: {
+ microphone: { enabled: true },
+ },
+ roomConnectOptions: {
+ autoSubscribe: true,
+ },
+ });
+ return () => {
+ session.end();
+ };
+ }, [session]);
+
return (

{
const params = typeof window !== 'undefined' ? new URLSearchParams(location.search) : null;
const roomName = params?.get('room') ?? 'test-room';
const userIdentity = params?.get('user') ?? generateRandomUserId();
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, {
- userInfo: {
- identity: userIdentity,
- name: userIdentity,
- },
- });
const [room] = useState(new Room());
+ const tokenSource = useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
+ }, []);
+
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: userIdentity,
+ participantName: userIdentity,
+ room,
+ });
+
const [connect, setConnect] = useState(false);
const [isConnected, setIsConnected] = useState(false);
const handleDisconnect = () => {
@@ -41,6 +46,26 @@ const CustomizeExample: NextPage = () => {
setIsConnected(false);
};
+ useEffect(() => {
+ if (connect) {
+ session.start({
+ tracks: {
+ microphone: { enabled: true },
+ },
+ });
+ } else {
+ session.end();
+ }
+ }, [connect, session]);
+
+ useEffect(() => {
+ if (session.connectionState === 'connected') {
+ setIsConnected(true);
+ } else {
+ setIsConnected(false);
+ }
+ }, [session.connectionState]);
+
return (
@@ -52,21 +77,12 @@ const CustomizeExample: NextPage = () => {
{connect ? 'Disconnect' : 'Connect'}
)}
- setIsConnected(true)}
- onDisconnected={handleDisconnect}
- audio={true}
- video={true}
- >
+
{/* Render a custom Stage component once connected */}
{isConnected && }
-
+
);
@@ -104,7 +120,7 @@ export function Stage() {
{/* In addition, we can still specify a style attribute and further customize the styles. */}
{/* Custom components: Here we replace the provided
with our own implementation. */}
diff --git a/examples/nextjs/pages/e2ee.tsx b/examples/nextjs/pages/e2ee.tsx
index 3d88c3472..959dced60 100644
--- a/examples/nextjs/pages/e2ee.tsx
+++ b/examples/nextjs/pages/e2ee.tsx
@@ -1,24 +1,20 @@
'use client';
-import { LiveKitRoom, useToken, VideoConference, setLogLevel } from '@livekit/components-react';
+import { SessionProvider, useSession, VideoConference, setLogLevel } from '@livekit/components-react';
import type { NextPage } from 'next';
import * as React from 'react';
-import { Room, ExternalE2EEKeyProvider } from 'livekit-client';
+import { Room, ExternalE2EEKeyProvider, TokenSource } from 'livekit-client';
import { generateRandomUserId } from '../lib/helper';
const E2EEExample: NextPage = () => {
- const params = typeof window !== 'undefined' ? new URLSearchParams(location.search) : null;
+ const params = React.useMemo(
+ () => (typeof window !== 'undefined' ? new URLSearchParams(location.search) : null),
+ [],
+ );
const roomName = params?.get('room') ?? 'test-room';
- const userIdentity = React.useMemo(() => params?.get('user') ?? generateRandomUserId(), []);
+ const userIdentity = React.useMemo(() => params?.get('user') ?? generateRandomUserId(), [params]);
setLogLevel('warn', { liveKitClientLogLevel: 'debug' });
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, {
- userInfo: {
- identity: userIdentity,
- name: userIdentity,
- },
- });
-
const keyProvider = React.useMemo(() => new ExternalE2EEKeyProvider(), []);
keyProvider.setKey('password');
@@ -29,27 +25,44 @@ const E2EEExample: NextPage = () => {
e2ee:
typeof window !== 'undefined'
? {
- keyProvider,
- worker: new Worker(new URL('livekit-client/e2ee-worker', import.meta.url)),
- }
+ keyProvider,
+ worker: new Worker(new URL('livekit-client/e2ee-worker', import.meta.url)),
+ }
: undefined,
}),
- [],
+ [keyProvider],
);
room.setE2EEEnabled(true);
+ const tokenSource = React.useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
+ }, []);
+
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: userIdentity,
+ participantName: userIdentity,
+ room,
+ });
+
+ React.useEffect(() => {
+ session.start({
+ tracks: {
+ camera: { enabled: true },
+ microphone: { enabled: true },
+ },
+ });
+ return () => {
+ session.end();
+ };
+ }, [session]);
+
return (
-
+
-
+
);
};
diff --git a/examples/nextjs/pages/minimal.tsx b/examples/nextjs/pages/minimal.tsx
index 06442a7bf..cb2a3ebf7 100644
--- a/examples/nextjs/pages/minimal.tsx
+++ b/examples/nextjs/pages/minimal.tsx
@@ -1,43 +1,47 @@
'use client';
-import { LiveKitRoom, useToken, VideoConference, setLogLevel } from '@livekit/components-react';
+import { SessionProvider, useSession, VideoConference, setLogLevel } from '@livekit/components-react';
import type { NextPage } from 'next';
import { generateRandomUserId } from '../lib/helper';
-import { useMemo } from 'react';
+import { useMemo, useEffect } from 'react';
+import { TokenSource } from 'livekit-client';
const MinimalExample: NextPage = () => {
const params = typeof window !== 'undefined' ? new URLSearchParams(location.search) : null;
const roomName = params?.get('room') ?? 'test-room';
setLogLevel('debug', { liveKitClientLogLevel: 'info' });
- const tokenOptions = useMemo(() => {
- const userId = params?.get('user') ?? generateRandomUserId();
- return {
- userInfo: {
- identity: userId,
- name: userId,
- },
- };
+ const userIdentity = useMemo(
+ () => params?.get('user') ?? generateRandomUserId(),
+ [params],
+ );
+
+ const tokenSource = useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
}, []);
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, tokenOptions);
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: userIdentity,
+ participantName: userIdentity,
+ });
+
+ useEffect(() => {
+ session.start({
+ tracks: {
+ microphone: { enabled: false },
+ },
+ });
+ return () => {
+ session.end();
+ };
+ }, [session]);
return (
- {
- console.error(e);
- alert(
- 'Error acquiring camera or microphone permissions. Please make sure you grant the necessary permissions in your browser and reload the tab',
- );
- }}
- >
+
-
+
);
};
diff --git a/examples/nextjs/pages/processors.tsx b/examples/nextjs/pages/processors.tsx
index 44f3712ce..12a2cb953 100644
--- a/examples/nextjs/pages/processors.tsx
+++ b/examples/nextjs/pages/processors.tsx
@@ -4,16 +4,16 @@ import * as React from 'react';
import { setLogLevel } from '@livekit/components-core';
import {
GridLayout,
- LiveKitRoom,
+ SessionProvider,
+ useSession,
ParticipantTile,
TrackRefContext,
useLocalParticipant,
- useToken,
useTracks,
} from '@livekit/components-react';
import type { NextPage } from 'next';
import { ControlBarControls } from '@livekit/components-react';
-import { LocalVideoTrack, Track, TrackProcessor } from 'livekit-client';
+import { LocalVideoTrack, Track, TrackProcessor, TokenSource } from 'livekit-client';
import { BackgroundBlur } from '@livekit/track-processors';
function Stage() {
@@ -23,14 +23,18 @@ function Stage() {
const [blurEnabled, setBlurEnabled] = React.useState(false);
const [processorPending, setProcessorPending] = React.useState(false);
const { cameraTrack } = useLocalParticipant();
- const [blur] = React.useState(BackgroundBlur());
+ const [blur, setBlur] = React.useState
| undefined>();
+
+ React.useEffect(() => {
+ setBlur(BackgroundBlur());
+ }, []);
React.useEffect(() => {
const localCamTrack = cameraTrack?.track as LocalVideoTrack | undefined;
if (localCamTrack) {
setProcessorPending(true);
try {
- if (blurEnabled && !localCamTrack.getProcessor()) {
+ if (blurEnabled && !localCamTrack.getProcessor() && blur) {
localCamTrack.setProcessor(blur);
} else if (!blurEnabled) {
localCamTrack.stopProcessor();
@@ -39,7 +43,7 @@ function Stage() {
setProcessorPending(false);
}
}
- }, [blurEnabled, cameraTrack]);
+ }, [blurEnabled, cameraTrack, blur]);
return (
<>
@@ -65,24 +69,33 @@ const ProcessorsExample: NextPage = () => {
const roomName = params?.get('room') ?? 'test-room';
const userIdentity = params?.get('user') ?? 'test-identity';
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, {
- userInfo: {
- identity: userIdentity,
- name: userIdentity,
- },
+ const tokenSource = React.useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
+ }, []);
+
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: userIdentity,
+ participantName: userIdentity,
});
+ React.useEffect(() => {
+ session.start({
+ tracks: {
+ camera: { enabled: true },
+ microphone: { enabled: false },
+ },
+ });
+ return () => {
+ session.end();
+ };
+ }, [session]);
+
return (
-
+
-
+
);
};
diff --git a/examples/nextjs/pages/simple.tsx b/examples/nextjs/pages/simple.tsx
index 7e9e981ab..7b7e3bd49 100644
--- a/examples/nextjs/pages/simple.tsx
+++ b/examples/nextjs/pages/simple.tsx
@@ -4,17 +4,17 @@ import {
ConnectionState,
ControlBar,
GridLayout,
- LiveKitRoom,
+ SessionProvider,
+ useSession,
ParticipantTile,
RoomAudioRenderer,
RoomName,
TrackRefContext,
- useToken,
useTracks,
} from '@livekit/components-react';
-import { Track } from 'livekit-client';
+import { Track, TokenSource } from 'livekit-client';
import type { NextPage } from 'next';
-import { useMemo, useState } from 'react';
+import { useMemo, useState, useEffect } from 'react';
import styles from '../styles/Simple.module.css';
import { generateRandomUserId } from '../lib/helper';
@@ -25,16 +25,35 @@ const SimpleExample: NextPage = () => {
const [connect, setConnect] = useState(false);
const [isConnected, setIsConnected] = useState(false);
- const userInfo = useMemo(() => {
- return {
- userInfo: {
- identity: userIdentity,
- name: userIdentity,
- },
- };
+ const tokenSource = useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
}, []);
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, userInfo);
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: userIdentity,
+ participantName: userIdentity,
+ });
+
+ useEffect(() => {
+ if (connect) {
+ session.start({
+ tracks: {
+ microphone: { enabled: true },
+ },
+ });
+ } else {
+ session.end();
+ }
+ }, [connect, session]);
+
+ useEffect(() => {
+ if (session.connectionState === 'connected') {
+ setIsConnected(true);
+ } else {
+ setIsConnected(false);
+ }
+ }, [session.connectionState]);
const handleDisconnect = () => {
setConnect(false);
@@ -52,21 +71,13 @@ const SimpleExample: NextPage = () => {
{connect ? 'Disconnect' : 'Connect'}
)}
- setIsConnected(true)}
- onDisconnected={handleDisconnect}
- audio={true}
- video={true}
- >
+
{isConnected && }
-
+
);
diff --git a/examples/nextjs/pages/voice-assistant.tsx b/examples/nextjs/pages/voice-assistant.tsx
index 16387bd55..25963aa6c 100644
--- a/examples/nextjs/pages/voice-assistant.tsx
+++ b/examples/nextjs/pages/voice-assistant.tsx
@@ -1,16 +1,16 @@
'use client';
import {
- LiveKitRoom,
- useToken,
useVoiceAssistant,
BarVisualizer,
RoomAudioRenderer,
VoiceAssistantControlBar,
+ SessionProvider,
+ useSession,
} from '@livekit/components-react';
import type { NextPage } from 'next';
-import { useMemo, useState } from 'react';
-import { MediaDeviceFailure } from 'livekit-client';
+import { useMemo, useState, useEffect } from 'react';
+import { MediaDeviceFailure, TokenSource } from 'livekit-client';
import styles from '../styles/VoiceAssistant.module.scss';
import { generateRandomUserId } from '../lib/helper';
@@ -27,24 +27,32 @@ function SimpleVoiceAssistant() {
}
const VoiceAssistantExample: NextPage = () => {
- const params = typeof window !== 'undefined' ? new URLSearchParams(location.search) : null;
+ const params = useMemo(
+ () => (typeof window !== 'undefined' ? new URLSearchParams(location.search) : null),
+ [],
+ );
const roomName = useMemo(
() => params?.get('room') ?? 'test-room-' + Math.random().toFixed(5),
- [],
+ [params],
);
const [shouldConnect, setShouldConnect] = useState(false);
- const tokenOptions = useMemo(() => {
- const userId = params?.get('user') ?? generateRandomUserId();
- return {
- userInfo: {
- identity: userId,
- name: userId,
- },
- };
+ const tokenSource = useMemo(() => {
+ return TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT!);
}, []);
- const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, roomName, tokenOptions);
+ const session = useSession(tokenSource, {
+ roomName,
+ participantIdentity: params?.get('user') ?? generateRandomUserId(),
+ });
+
+ useEffect(() => {
+ if (shouldConnect) {
+ session.start();
+ } else {
+ session.end();
+ }
+ }, [shouldConnect, session]);
const onDeviceFailure = (e?: MediaDeviceFailure) => {
console.error(e);
@@ -55,27 +63,21 @@ const VoiceAssistantExample: NextPage = () => {
return (