diff --git a/app/api/record/start/route.ts b/app/api/record/start/route.ts
index 1cda0c4..07129ac 100644
--- a/app/api/record/start/route.ts
+++ b/app/api/record/start/route.ts
@@ -1,10 +1,11 @@
-import { EgressClient, EncodedFileOutput, S3Upload } from 'livekit-server-sdk';
+import { EgressClient, EncodedFileOutput, RoomServiceClient, S3Upload } from 'livekit-server-sdk';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
try {
const roomName = req.nextUrl.searchParams.get('roomName');
const now = req.nextUrl.searchParams.get('now');
+ const identity = req.nextUrl.searchParams.get('identity');
// new Date(Date.now()).toISOString();
/**
@@ -68,6 +69,11 @@ export async function GET(req: NextRequest) {
layout: 'speaker',
},
);
+ const roomClient = new RoomServiceClient(hostURL.origin, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
+ await roomClient.updateRoomMetadata(
+ roomName,
+ JSON.stringify({ recording: { isRecording: true, recorder: identity } }),
+ );
if (RUNNER_URL && RUNNER_SECRET) {
post_runner(RUNNER_URL, RUNNER_SECRET, filepath);
diff --git a/app/api/record/stop/route.ts b/app/api/record/stop/route.ts
index e2630ac..c9bfd7f 100644
--- a/app/api/record/stop/route.ts
+++ b/app/api/record/stop/route.ts
@@ -1,9 +1,10 @@
-import { EgressClient } from 'livekit-server-sdk';
+import { EgressClient, RoomServiceClient } from 'livekit-server-sdk';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
try {
const roomName = req.nextUrl.searchParams.get('roomName');
+ const identity = req.nextUrl.searchParams.get('identity');
/**
* CAUTION:
@@ -22,6 +23,7 @@ export async function GET(req: NextRequest) {
hostURL.protocol = 'https:';
const egressClient = new EgressClient(hostURL.origin, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
+ const roomClient = new RoomServiceClient(hostURL.origin, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
const activeEgresses = (await egressClient.listEgress({ roomName })).filter(
(info) => info.status < 2,
);
@@ -29,6 +31,10 @@ export async function GET(req: NextRequest) {
return new NextResponse('No active recording found', { status: 404 });
}
await Promise.all(activeEgresses.map((info) => egressClient.stopEgress(info.egressId)));
+ await roomClient.updateRoomMetadata(
+ roomName,
+ JSON.stringify({ recording: { isRecording: false, recorder: identity } }),
+ );
return new NextResponse(null, { status: 200 });
} catch (error) {
diff --git a/app/contexts/layout-context.tsx b/app/contexts/layout-context.tsx
index b031d46..b9ea6da 100644
--- a/app/contexts/layout-context.tsx
+++ b/app/contexts/layout-context.tsx
@@ -2,7 +2,7 @@ import { createContext, useContext } from 'react';
type LayoutContextType = {
// isSettingsOpen: SettingsContextType,
- // isChatOpen: ChatContextType,
+ isChatOpen: ChatContextType;
isParticipantsListOpen: ParticipantsListContextType;
};
diff --git a/app/custom/CustomControlBar.tsx b/app/custom/CustomControlBar.tsx
index 82e18b7..25563c7 100644
--- a/app/custom/CustomControlBar.tsx
+++ b/app/custom/CustomControlBar.tsx
@@ -1,10 +1,11 @@
'use client';
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import {
DisconnectButton,
useIsRecording,
useLayoutContext,
+ useLocalParticipant,
useRoomContext,
} from '@livekit/components-react';
import { Room, RoomEvent, Track } from 'livekit-client';
@@ -24,22 +25,35 @@ interface CustomControlBarProps {
export function CustomControlBar({ room, roomName }: CustomControlBarProps) {
const recordingEndpoint = process.env.NEXT_PUBLIC_LK_RECORD_ENDPOINT;
- const isRecording = useIsRecording();
+ const { localParticipant } = useLocalParticipant();
const [isRecordingRequestPending, setIsRecordingRequestPending] = useState(false);
const [participantCount, setParticipantCount] = useState(1);
const { dispatch } = useLayoutContext().widget;
const { isParticipantsListOpen } = useCustomLayoutContext();
const { toast } = useToast();
+ const [recordingState, setRecordingState] = useState({
+ recording: { isRecording: false, recorder: '' },
+ });
+ const isRecording = useMemo(() => {
+ return recordingState.recording.isRecording;
+ }, [recordingState]);
+ const isSelfRecord = useMemo(() => {
+ return recordingState.recording.recorder === localParticipant.identity;
+ }, [recordingState]);
+
const [isFirstMount, setIsFirstMount] = useState(true);
useEffect(() => {
- if (isFirstMount) return setIsFirstMount(false);
- setIsRecordingRequestPending(false);
+ setIsFirstMount(false);
+ }, []);
+
+ useEffect(() => {
if (isRecording) {
toast({
title: 'Recording in progress. Please be aware this call is being recorded.',
});
} else {
+ if (isFirstMount) return;
toast({
title: 'Recorded ended. This call is no longer being recorded.',
});
@@ -52,7 +66,7 @@ export function CustomControlBar({ room, roomName }: CustomControlBarProps) {
}
const toggleRoomRecording = async () => {
- if (isRecordingRequestPending) return;
+ if (isRecordingRequestPending || (isRecording && !isSelfRecord)) return;
setIsRecordingRequestPending(true);
if (!isRecording)
toast({
@@ -73,9 +87,14 @@ export function CustomControlBar({ room, roomName }: CustomControlBarProps) {
const now = new Date(Date.now()).toISOString();
// const fileName = `${now}-${room.name}.mp4`;
if (isRecording) {
- response = await fetch(recordingEndpoint + `/stop?roomName=${room.name}`);
+ response = await fetch(
+ recordingEndpoint + `/stop?roomName=${room.name}&identity=${localParticipant.identity}`,
+ );
} else {
- response = await fetch(recordingEndpoint + `/start?roomName=${room.name}&now=${now}`);
+ response = await fetch(
+ recordingEndpoint +
+ `/start?roomName=${room.name}&now=${now}&identity=${localParticipant.identity}`,
+ );
}
if (response.ok) {
} else {
@@ -87,6 +106,17 @@ export function CustomControlBar({ room, roomName }: CustomControlBarProps) {
}
};
+ const updateRoomMetadata = (metadata: string) => {
+ const parsedMetadata = JSON.parse(metadata === '' ? '{}' : metadata);
+ setIsRecordingRequestPending(false);
+ setRecordingState({
+ recording: {
+ isRecording: parsedMetadata.recording.isRecording,
+ recorder: parsedMetadata.recording.recorder,
+ },
+ });
+ };
+
useEffect(() => {
if (room) {
const updateParticipantCount = () => {
@@ -96,11 +126,13 @@ export function CustomControlBar({ room, roomName }: CustomControlBarProps) {
room.on(RoomEvent.Connected, updateParticipantCount);
room.on(RoomEvent.ParticipantConnected, updateParticipantCount);
room.on(RoomEvent.ParticipantDisconnected, updateParticipantCount);
+ room.on(RoomEvent.RoomMetadataChanged, updateRoomMetadata);
return () => {
room.off(RoomEvent.Connected, updateParticipantCount);
room.off(RoomEvent.ParticipantConnected, updateParticipantCount);
room.off(RoomEvent.ParticipantDisconnected, updateParticipantCount);
+ room.off(RoomEvent.RoomMetadataChanged, updateRoomMetadata);
};
}
}, [room]);
@@ -127,18 +159,16 @@ export function CustomControlBar({ room, roomName }: CustomControlBarProps) {