Refactor app use connection details endpoint (#302)
* update to connection-details endpoint * chore: Update .env.example * chore: Update .env.example * remove fallback logic * chore: Update connection details property names
This commit is contained in:
parent
15e58cd797
commit
f2f4ada03d
18
.env.example
18
.env.example
@ -1,13 +1,17 @@
|
||||
# 1. Copy this file and rename it to .env.local
|
||||
# 2. Update the enviroment variables below.
|
||||
|
||||
# API key and secret. If you use LiveKit Cloud this can be generated via the cloud dashboard.
|
||||
LIVEKIT_API_KEY=devkey
|
||||
LIVEKIT_API_SECRET=secret
|
||||
# REQUIRED SETTINGS
|
||||
# #################
|
||||
# If you are using LiveKit Cloud, the API key and secret can be generated from the Cloud Dashboard.
|
||||
LIVEKIT_API_KEY=
|
||||
LIVEKIT_API_SECRET=
|
||||
# URL pointing to the LiveKit server. (example: `wss://my-livekit-project.livekit.cloud`)
|
||||
LIVEKIT_URL=
|
||||
|
||||
# URL pointing to the LiveKit server.
|
||||
LIVEKIT_URL=wss://my-livekit-project.livekit.cloud
|
||||
|
||||
# OPTIONAL SETTINGS
|
||||
# #################
|
||||
# Recording
|
||||
# S3_KEY_ID=
|
||||
# S3_KEY_SECRET=
|
||||
@ -16,11 +20,9 @@ LIVEKIT_URL=wss://my-livekit-project.livekit.cloud
|
||||
# S3_REGION=
|
||||
|
||||
# PUBLIC
|
||||
NEXT_PUBLIC_LK_TOKEN_ENDPOINT=/api/token
|
||||
#NEXT_PUBLIC_LK_RECORD_ENDPOINT=/api/record
|
||||
|
||||
# Uncomment settings menu when using a LiveKit Cloud, it'll enable Krisp noise filters.
|
||||
# NEXT_PUBLIC_SHOW_SETTINGS_MENU=true
|
||||
# NEXT_PUBLIC_LK_RECORD_ENDPOINT=/api/record
|
||||
|
||||
# Optional, to pipe logs to datadog
|
||||
# NEXT_PUBLIC_DATADOG_CLIENT_TOKEN=client-token
|
||||
|
||||
61
app/api/connection-details/route.ts
Normal file
61
app/api/connection-details/route.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { ConnectionDetails } from '@/lib/types';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { AccessToken, AccessTokenOptions, VideoGrant } from 'livekit-server-sdk';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API_KEY = process.env.LIVEKIT_API_KEY;
|
||||
const API_SECRET = process.env.LIVEKIT_API_SECRET;
|
||||
const LIVEKIT_URL = process.env.LIVEKIT_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Parse query parameters
|
||||
const roomName = request.nextUrl.searchParams.get('roomName');
|
||||
const participantName = request.nextUrl.searchParams.get('participantName');
|
||||
const metadata = request.nextUrl.searchParams.get('metadata') ?? '';
|
||||
|
||||
if (typeof roomName !== 'string') {
|
||||
return new NextResponse('Missing required query parameter: roomName', { status: 400 });
|
||||
}
|
||||
if (participantName === null) {
|
||||
return new NextResponse('Missing required query parameter: participantName', { status: 400 });
|
||||
}
|
||||
|
||||
// Generate participant token
|
||||
const participantToken = await createParticipantToken(
|
||||
{
|
||||
identity: `${participantName}__${randomUUID()}`,
|
||||
name: participantName,
|
||||
metadata,
|
||||
},
|
||||
roomName,
|
||||
);
|
||||
|
||||
// Return connection details
|
||||
const data: ConnectionDetails = {
|
||||
serverUrl: LIVEKIT_URL!,
|
||||
roomName: roomName,
|
||||
participantToken: participantToken,
|
||||
participantName: participantName,
|
||||
};
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return new NextResponse(error.message, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createParticipantToken(userInfo: AccessTokenOptions, roomName: string) {
|
||||
const at = new AccessToken(API_KEY, API_SECRET, userInfo);
|
||||
at.ttl = '5m';
|
||||
const grant: VideoGrant = {
|
||||
room: roomName,
|
||||
roomJoin: true,
|
||||
canPublish: true,
|
||||
canPublishData: true,
|
||||
canSubscribe: true,
|
||||
};
|
||||
at.addGrant(grant);
|
||||
return at.toJwt();
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
import { EgressClient, EncodedFileOutput, S3Upload } from 'livekit-server-sdk';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(req: Request) {
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const searchParams = url.searchParams;
|
||||
const roomName = searchParams.get('roomName');
|
||||
const roomName = req.nextUrl.searchParams.get('roomName');
|
||||
|
||||
/**
|
||||
* CAUTION:
|
||||
@ -14,7 +12,7 @@ export async function GET(req: Request) {
|
||||
* DO NOT USE THIS FOR PRODUCTION PURPOSES AS IS
|
||||
*/
|
||||
|
||||
if (typeof roomName !== 'string') {
|
||||
if (roomName === null) {
|
||||
return new NextResponse('Missing roomName parameter', { status: 403 });
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { EgressClient } from 'livekit-server-sdk';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(req: Request) {
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const searchParams = url.searchParams;
|
||||
const roomName = searchParams.get('roomName');
|
||||
const roomName = req.nextUrl.searchParams.get('roomName');
|
||||
|
||||
/**
|
||||
* CAUTION:
|
||||
@ -14,7 +12,7 @@ export async function GET(req: Request) {
|
||||
* DO NOT USE THIS FOR PRODUCTION PURPOSES AS IS
|
||||
*/
|
||||
|
||||
if (typeof roomName !== 'string') {
|
||||
if (roomName === null) {
|
||||
return new NextResponse('Missing roomName parameter', { status: 403 });
|
||||
}
|
||||
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { AccessToken } from 'livekit-server-sdk';
|
||||
import type { AccessTokenOptions, VideoGrant } from 'livekit-server-sdk';
|
||||
import { TokenResult } from '@/lib/types';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const apiKey = process.env.LIVEKIT_API_KEY;
|
||||
const apiSecret = process.env.LIVEKIT_API_SECRET;
|
||||
|
||||
const createToken = (userInfo: AccessTokenOptions, grant: VideoGrant) => {
|
||||
const at = new AccessToken(apiKey, apiSecret, userInfo);
|
||||
at.ttl = '5m';
|
||||
at.addGrant(grant);
|
||||
return at.toJwt();
|
||||
};
|
||||
|
||||
const roomPattern = /\w{4}\-\w{4}/;
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const searchParams = url.searchParams;
|
||||
const roomName = searchParams.get('roomName');
|
||||
const identity = searchParams.get('identity');
|
||||
const name = searchParams.get('name');
|
||||
const metadata = searchParams.get('metadata') ?? '';
|
||||
|
||||
if (typeof identity !== 'string' || typeof roomName !== 'string') {
|
||||
return new NextResponse('Forbidden', { status: 401 });
|
||||
}
|
||||
if (name === null) {
|
||||
return new NextResponse('Provide a name.', { status: 400 });
|
||||
}
|
||||
if (Array.isArray(name)) {
|
||||
return new NextResponse('Provide only one room name.', { status: 400 });
|
||||
}
|
||||
if (Array.isArray(metadata)) {
|
||||
return new NextResponse('Provide only one metadata string.', { status: 400 });
|
||||
}
|
||||
|
||||
// enforce room name to be xxxx-xxxx
|
||||
// this is simple & naive way to prevent user from guessing room names
|
||||
// please use your own authentication mechanisms in your own app
|
||||
if (!roomName.match(roomPattern)) {
|
||||
return new NextResponse('Invalid room name format.', { status: 400 });
|
||||
}
|
||||
|
||||
const grant: VideoGrant = {
|
||||
room: roomName,
|
||||
roomJoin: true,
|
||||
canPublish: true,
|
||||
canPublishData: true,
|
||||
canSubscribe: true,
|
||||
};
|
||||
|
||||
const token = await createToken({ identity, name, metadata }, grant);
|
||||
const result: TokenResult = {
|
||||
identity,
|
||||
accessToken: token,
|
||||
};
|
||||
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return new NextResponse(error.message, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,9 @@
|
||||
import { getLiveKitURL } from '../../../lib/server-utils';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getLiveKitURL } from '@/lib/server-utils';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(req: Request) {
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const searchParams = url.searchParams;
|
||||
const region = searchParams.get('region');
|
||||
|
||||
if (Array.isArray(region)) {
|
||||
throw Error('provide max one region string');
|
||||
}
|
||||
const region = req.nextUrl.searchParams.get('region');
|
||||
const livekitUrl = getLiveKitURL(region);
|
||||
return NextResponse.json({ url: livekitUrl });
|
||||
} catch (error) {
|
||||
|
||||
176
app/rooms/[roomName]/PageClientImpl.tsx
Normal file
176
app/rooms/[roomName]/PageClientImpl.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
'use client';
|
||||
|
||||
import { decodePassphrase } from '@/lib/client-utils';
|
||||
import { DebugMode } from '@/lib/Debug';
|
||||
import { RecordingIndicator } from '@/lib/RecordingIndicator';
|
||||
import { SettingsMenu } from '@/lib/SettingsMenu';
|
||||
import { ConnectionDetails } from '@/lib/types';
|
||||
import {
|
||||
formatChatMessageLinks,
|
||||
LiveKitRoom,
|
||||
LocalUserChoices,
|
||||
PreJoin,
|
||||
VideoConference,
|
||||
} from '@livekit/components-react';
|
||||
import {
|
||||
ExternalE2EEKeyProvider,
|
||||
RoomOptions,
|
||||
VideoCodec,
|
||||
VideoPresets,
|
||||
Room,
|
||||
DeviceUnsupportedError,
|
||||
RoomConnectOptions,
|
||||
} from 'livekit-client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
const CONN_DETAILS_ENDPOINT =
|
||||
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT ?? '/api/connection-details';
|
||||
const SHOW_SETTINGS_MENU = process.env.NEXT_PUBLIC_SHOW_SETTINGS_MENU == 'true';
|
||||
|
||||
export function PageClientImpl(props: {
|
||||
roomName: string;
|
||||
region?: string;
|
||||
hq: boolean;
|
||||
codec: VideoCodec;
|
||||
}) {
|
||||
const [preJoinChoices, setPreJoinChoices] = React.useState<LocalUserChoices | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const preJoinDefaults = React.useMemo(() => {
|
||||
return {
|
||||
username: '',
|
||||
videoEnabled: true,
|
||||
audioEnabled: true,
|
||||
};
|
||||
}, []);
|
||||
const [connectionDetails, setConnectionDetails] = React.useState<ConnectionDetails | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const handlePreJoinSubmit = React.useCallback(async (values: LocalUserChoices) => {
|
||||
setPreJoinChoices(values);
|
||||
const connectionDetailsResp = await fetch(
|
||||
`${CONN_DETAILS_ENDPOINT}?roomName=${props.roomName}&participantName=${values.username}`,
|
||||
);
|
||||
const connectionDetailsData = await connectionDetailsResp.json();
|
||||
setConnectionDetails(connectionDetailsData);
|
||||
}, []);
|
||||
const handlePreJoinError = React.useCallback((e: any) => console.error(e), []);
|
||||
|
||||
return (
|
||||
<main data-lk-theme="default">
|
||||
{connectionDetails === undefined || preJoinChoices === undefined ? (
|
||||
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
|
||||
<PreJoin
|
||||
defaults={preJoinDefaults}
|
||||
onSubmit={handlePreJoinSubmit}
|
||||
onError={handlePreJoinError}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<VideoConferenceComponent
|
||||
connectionDetails={connectionDetails}
|
||||
userChoices={preJoinChoices}
|
||||
options={{ codec: props.codec, hq: props.hq }}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function VideoConferenceComponent(props: {
|
||||
userChoices: LocalUserChoices;
|
||||
connectionDetails: ConnectionDetails;
|
||||
options: {
|
||||
hq: boolean;
|
||||
codec: VideoCodec;
|
||||
};
|
||||
}) {
|
||||
const e2eePassphrase =
|
||||
typeof window !== 'undefined' && decodePassphrase(location.hash.substring(1));
|
||||
|
||||
const worker =
|
||||
typeof window !== 'undefined' &&
|
||||
e2eePassphrase &&
|
||||
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
|
||||
const e2eeEnabled = !!(e2eePassphrase && worker);
|
||||
const keyProvider = new ExternalE2EEKeyProvider();
|
||||
|
||||
const roomOptions = React.useMemo((): RoomOptions => {
|
||||
let videoCodec: VideoCodec | undefined = props.options.codec ? props.options.codec : 'vp9';
|
||||
if (e2eeEnabled && (videoCodec === 'av1' || videoCodec === 'vp9')) {
|
||||
videoCodec = undefined;
|
||||
}
|
||||
return {
|
||||
videoCaptureDefaults: {
|
||||
deviceId: props.userChoices.videoDeviceId ?? undefined,
|
||||
resolution: props.options.hq ? VideoPresets.h2160 : VideoPresets.h720,
|
||||
},
|
||||
publishDefaults: {
|
||||
dtx: false,
|
||||
videoSimulcastLayers: props.options.hq
|
||||
? [VideoPresets.h1080, VideoPresets.h720]
|
||||
: [VideoPresets.h540, VideoPresets.h216],
|
||||
red: !e2eeEnabled,
|
||||
videoCodec,
|
||||
},
|
||||
audioCaptureDefaults: {
|
||||
deviceId: props.userChoices.audioDeviceId ?? undefined,
|
||||
},
|
||||
adaptiveStream: { pixelDensity: 'screen' },
|
||||
dynacast: true,
|
||||
e2ee: e2eeEnabled
|
||||
? {
|
||||
keyProvider,
|
||||
worker,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
// @ts-ignore
|
||||
setLogLevel('debug', 'lk-e2ee');
|
||||
}, [props.userChoices, props.options.hq, props.options.codec]);
|
||||
|
||||
const room = React.useMemo(() => new Room(roomOptions), []);
|
||||
|
||||
if (e2eeEnabled) {
|
||||
keyProvider.setKey(decodePassphrase(e2eePassphrase));
|
||||
room.setE2EEEnabled(true).catch((e) => {
|
||||
if (e instanceof DeviceUnsupportedError) {
|
||||
alert(
|
||||
`You're trying to join an encrypted meeting, but your browser does not support it. Please update it to the latest version and try again.`,
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
const connectOptions = React.useMemo((): RoomConnectOptions => {
|
||||
return {
|
||||
autoSubscribe: true,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const router = useRouter();
|
||||
const handleOnLeave = React.useCallback(() => router.push('/'), [router]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LiveKitRoom
|
||||
room={room}
|
||||
token={props.connectionDetails.participantToken}
|
||||
serverUrl={props.connectionDetails.serverUrl}
|
||||
connectOptions={connectOptions}
|
||||
video={props.userChoices.videoEnabled}
|
||||
audio={props.userChoices.audioEnabled}
|
||||
onDisconnected={handleOnLeave}
|
||||
>
|
||||
<VideoConference
|
||||
chatMessageFormatter={formatChatMessageLinks}
|
||||
SettingsComponent={SHOW_SETTINGS_MENU ? SettingsMenu : undefined}
|
||||
/>
|
||||
<DebugMode />
|
||||
<RecordingIndicator />
|
||||
</LiveKitRoom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,180 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
LiveKitRoom,
|
||||
VideoConference,
|
||||
formatChatMessageLinks,
|
||||
useToken,
|
||||
LocalUserChoices,
|
||||
PreJoin,
|
||||
} from '@livekit/components-react';
|
||||
import {
|
||||
DeviceUnsupportedError,
|
||||
ExternalE2EEKeyProvider,
|
||||
Room,
|
||||
RoomConnectOptions,
|
||||
RoomOptions,
|
||||
VideoCodec,
|
||||
VideoPresets,
|
||||
setLogLevel,
|
||||
} from 'livekit-client';
|
||||
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import * as React from 'react';
|
||||
import { DebugMode } from '@/lib/Debug';
|
||||
import { decodePassphrase, useServerUrl } from '@/lib/client-utils';
|
||||
import { SettingsMenu } from '@/lib/SettingsMenu';
|
||||
import { RecordingIndicator } from '@/lib/RecordingIndicator';
|
||||
import { PageClientImpl } from './PageClientImpl';
|
||||
import { isVideoCodec } from '@/lib/types';
|
||||
|
||||
export default function Page({ params }: { params: { roomName: string } }) {
|
||||
const router = useRouter();
|
||||
const roomName = params.roomName;
|
||||
const [preJoinChoices, setPreJoinChoices] = React.useState<LocalUserChoices | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const preJoinDefaults = React.useMemo(() => {
|
||||
return {
|
||||
username: '',
|
||||
videoEnabled: true,
|
||||
audioEnabled: true,
|
||||
};
|
||||
}, []);
|
||||
const handlePreJoinSubmit = React.useCallback((values: LocalUserChoices) => {
|
||||
setPreJoinChoices(values);
|
||||
}, []);
|
||||
const onPreJoinError = React.useCallback((e: any) => {
|
||||
console.error(e);
|
||||
}, []);
|
||||
const onLeave = React.useCallback(() => router.push('/'), []);
|
||||
|
||||
return (
|
||||
<main data-lk-theme="default">
|
||||
{roomName && !Array.isArray(roomName) && preJoinChoices ? (
|
||||
<ActiveRoom roomName={roomName} userChoices={preJoinChoices} onLeave={onLeave}></ActiveRoom>
|
||||
) : (
|
||||
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
|
||||
<PreJoin
|
||||
onError={onPreJoinError}
|
||||
defaults={preJoinDefaults}
|
||||
onSubmit={handlePreJoinSubmit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function ActiveRoom(props: {
|
||||
userChoices: LocalUserChoices;
|
||||
roomName: string;
|
||||
onLeave: () => void;
|
||||
export default function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: { roomName: string };
|
||||
searchParams: {
|
||||
// FIXME: We should not allow values for regions if in playground mode.
|
||||
region?: string;
|
||||
hq?: string;
|
||||
codec?: string;
|
||||
};
|
||||
}) {
|
||||
const searchParams = useSearchParams();
|
||||
const region = searchParams?.get('region');
|
||||
const hq = searchParams?.get('hq');
|
||||
const codec = searchParams?.get('codec');
|
||||
|
||||
const tokenOptions = React.useMemo(() => {
|
||||
return {
|
||||
userInfo: {
|
||||
identity: props.userChoices.username,
|
||||
name: props.userChoices.username,
|
||||
},
|
||||
};
|
||||
}, [props.userChoices.username]);
|
||||
const token = useToken(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT, props.roomName, tokenOptions);
|
||||
|
||||
const e2eePassphrase =
|
||||
typeof window !== 'undefined' && decodePassphrase(location.hash.substring(1));
|
||||
|
||||
const liveKitUrl = useServerUrl(typeof region === 'string' ? region : undefined);
|
||||
|
||||
const worker =
|
||||
typeof window !== 'undefined' &&
|
||||
e2eePassphrase &&
|
||||
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
|
||||
const e2eeEnabled = !!(e2eePassphrase && worker);
|
||||
const keyProvider = new ExternalE2EEKeyProvider();
|
||||
|
||||
const roomOptions = React.useMemo((): RoomOptions => {
|
||||
let videoCodec: VideoCodec | undefined = codec && isVideoCodec(codec) ? codec : 'vp9';
|
||||
if (e2eeEnabled && (videoCodec === 'av1' || videoCodec === 'vp9')) {
|
||||
videoCodec = undefined;
|
||||
}
|
||||
return {
|
||||
videoCaptureDefaults: {
|
||||
deviceId: props.userChoices.videoDeviceId ?? undefined,
|
||||
resolution: hq === 'true' ? VideoPresets.h2160 : VideoPresets.h720,
|
||||
},
|
||||
publishDefaults: {
|
||||
dtx: false,
|
||||
videoSimulcastLayers:
|
||||
hq === 'true'
|
||||
? [VideoPresets.h1080, VideoPresets.h720]
|
||||
: [VideoPresets.h540, VideoPresets.h216],
|
||||
red: !e2eeEnabled,
|
||||
videoCodec,
|
||||
},
|
||||
audioCaptureDefaults: {
|
||||
deviceId: props.userChoices.audioDeviceId ?? undefined,
|
||||
},
|
||||
adaptiveStream: { pixelDensity: 'screen' },
|
||||
dynacast: true,
|
||||
e2ee: e2eeEnabled
|
||||
? {
|
||||
keyProvider,
|
||||
worker,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
// @ts-ignore
|
||||
setLogLevel('debug', 'lk-e2ee');
|
||||
}, [props.userChoices, hq, codec]);
|
||||
|
||||
const room = React.useMemo(() => new Room(roomOptions), []);
|
||||
|
||||
if (e2eeEnabled) {
|
||||
keyProvider.setKey(decodePassphrase(e2eePassphrase));
|
||||
room.setE2EEEnabled(true).catch((e) => {
|
||||
if (e instanceof DeviceUnsupportedError) {
|
||||
alert(
|
||||
`You're trying to join an encrypted meeting, but your browser does not support it. Please update it to the latest version and try again.`,
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
const connectOptions = React.useMemo((): RoomConnectOptions => {
|
||||
return {
|
||||
autoSubscribe: true,
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!liveKitUrl) {
|
||||
return null;
|
||||
}
|
||||
const codec =
|
||||
typeof searchParams.codec === 'string' && isVideoCodec(searchParams.codec)
|
||||
? searchParams.codec
|
||||
: 'vp9';
|
||||
const hq = searchParams.hq === 'true' ? true : false;
|
||||
|
||||
return (
|
||||
<>
|
||||
<LiveKitRoom
|
||||
room={room}
|
||||
token={token}
|
||||
serverUrl={liveKitUrl}
|
||||
connectOptions={connectOptions}
|
||||
video={props.userChoices.videoEnabled}
|
||||
audio={props.userChoices.audioEnabled}
|
||||
onDisconnected={props.onLeave}
|
||||
>
|
||||
<VideoConference
|
||||
chatMessageFormatter={formatChatMessageLinks}
|
||||
SettingsComponent={
|
||||
process.env.NEXT_PUBLIC_SHOW_SETTINGS_MENU === 'true' ? SettingsMenu : undefined
|
||||
}
|
||||
/>
|
||||
<DebugMode />
|
||||
<RecordingIndicator />
|
||||
</LiveKitRoom>
|
||||
</>
|
||||
<PageClientImpl roomName={params.roomName} region={searchParams.region} hq={hq} codec={codec} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -19,3 +19,10 @@ export interface TokenResult {
|
||||
export function isVideoCodec(codec: string): codec is VideoCodec {
|
||||
return videoCodecs.includes(codec as VideoCodec);
|
||||
}
|
||||
|
||||
export type ConnectionDetails = {
|
||||
serverUrl: string;
|
||||
roomName: string;
|
||||
participantName: string;
|
||||
participantToken: string;
|
||||
};
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@ -2281,7 +2281,7 @@ snapshots:
|
||||
eslint: 9.8.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@9.8.0))(eslint@9.8.0)
|
||||
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.8.0)
|
||||
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@9.8.0))(eslint@9.8.0))(eslint@9.8.0)
|
||||
eslint-plugin-jsx-a11y: 6.8.0(eslint@9.8.0)
|
||||
eslint-plugin-react: 7.33.2(eslint@9.8.0)
|
||||
eslint-plugin-react-hooks: 4.6.0(eslint@9.8.0)
|
||||
@ -2305,7 +2305,7 @@ snapshots:
|
||||
enhanced-resolve: 5.17.1
|
||||
eslint: 9.8.0
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@9.8.0))(eslint@9.8.0))(eslint@9.8.0)
|
||||
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.8.0)
|
||||
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@9.8.0))(eslint@9.8.0))(eslint@9.8.0)
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.2
|
||||
is-core-module: 2.13.1
|
||||
@ -2327,7 +2327,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.8.0):
|
||||
eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.10.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@9.8.0))(eslint@9.8.0))(eslint@9.8.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.7
|
||||
array.prototype.findlastindex: 1.2.3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user