i got this error āœ“ Compiled /api/record/start in ...
# ask-ai
n
i got this error āœ“ Compiled /api/record/start in 807ms (998 modules) Attempting to start recording for room: vercel-570ce5b0-2d6a-465e-bf37-916896b943c2 LiveKit URL: wss://XXXXXXX.ngrok-free.app Egress URL: https://XXXXXXX.ngrok-free.app Room vercel-570ce5b0-2d6a-465e-bf37-916896b943c2 found with 0 participants Generated filename: livekit/record/vercel-570ce5b0-2d6a-465e-bf37-916896b943c2.mp4 File output configuration: { "filepath": "livekit/record/vercel-570ce5b0-2d6a-465e-bf37-916896b943c2.mp4", "outputType": "s3", "region": "XXXXXX", "bucket": "XXXXXX" } Starting room composite egress... Error starting recording: twirp error unknown: request has missing or invalid field: ws_url GET /api/record/start?roomName=vercel-570ce5b0-2d6a-465e-bf37-916896b943c2 500 in 1768ms this is my code import { EgressClient, EncodedFileOutput, S3Upload, RoomServiceClient } from 'livekit-server-sdk'; import { NextRequest, NextResponse } from 'next/server'; // import { getMeetingByRoomName, updateMeetingRecordingLink } from '@/lib/mongodb'; // import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob'; // Function to generate versioned filename based on existing recordings // COMMENTED OUT: Using simple filename for S3 upload /* async function generateVersionedFilename( roomName: string, storageAccountName: string, storageAccountKey: string, containerName: string ): Promise<string> { try { // Create blob service client const sharedKeyCredential = new StorageSharedKeyCredential(storageAccountName, storageAccountKey); const blobServiceClient = new BlobServiceClient(
https://${storageAccountName}.<http://blob.core.windows.net|blob.core.windows.net>
,
sharedKeyCredential ); const containerClient = blobServiceClient.getContainerClient(containerName); // List all blobs that start with the room name pattern const prefix = `livekit/record/${roomName}`; const existingFiles: string[] = []; for await (const blob of containerClient.listBlobsFlat({ prefix })) { existingFiles.push(blob.name); } // Extract version numbers from existing files const versions: number[] = []; const basePattern = `livekit/record/${roomName}`; for (const fileName of existingFiles) { if (fileName ===
${basePattern}.mp4
) {
// This is the base file (no version number) versions.push(0); } else { _// Try to extract version number from pattern: roomName_X.mp4_ _const match = fileName.match(new RegExp(
^${basePattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}_([0-9]+)\\.mp4$
));_ if (match) { versions.push(parseInt(match[1], 10)); } } } // Determine the next version number let nextVersion = 0; if (versions.length > 0) { const maxVersion = Math.max(...versions); nextVersion = maxVersion + 1; } // console.log(
Room: ${roomName}, Found versions: [${versions.join(', ')}], Next version: ${nextVersion}
);
// Generate filename based on version if (nextVersion === 0) { return `livekit/record/${roomName}.mp4`; } else if (nextVersion === 1) { _return `livekit/record/${roomName}_1.mp4`;_ } else { _return `livekit/record/${roomName}_${nextVersion}.mp4`;_ } } catch (error) { console.error('Error checking existing files:', error); // Fallback to base filename if there's an error return `livekit/record/${roomName}.mp4`; } } */ export async function *GET*(_req_: NextRequest) { try { const roomName = req.nextUrl.searchParams.get('roomName'); if (roomName === null) { return new NextResponse('Missing roomName parameter', { status: 403 }); } const { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL, } = process.env; // S3 configuration const S3_BUCKET_NAME = 'XXXXXXXXXXXXX'; const S3_REGION = 'XXXXXXXXXXXXX'; // Debug logging console.log(
Attempting to start recording for room: ${roomName}
); console.log(
LiveKit URL: ${LIVEKIT_URL}
); // Use the same URL conversion as the original working code const hostURL = new URL(LIVEKIT_URL!); hostURL.protocol = 'https:'; console.log(
Egress URL: ${hostURL.origin}
); const egressClient = new EgressClient(hostURL.origin, LIVEKIT_API_KEY, LIVEKIT_API_SECRET); // Check if room exists first try { const roomClient = new RoomServiceClient(LIVEKIT_URL!, LIVEKIT_API_KEY, LIVEKIT_API_SECRET); const rooms = await roomClient.listRooms([roomName]); if (rooms.length === 0) { console.log(
Room ${roomName} does not exist
); return new NextResponse('Room does not exist. Please ensure participants have joined the room before starting recording.', { status: 404 }); } console.log(
Room ${roomName} found with ${rooms[0].numParticipants} participants
); } catch (roomError) { console.error('Error checking room existence:', roomError); // Continue anyway, let egress handle it } const existingEgresses = await egressClient.listEgress({ roomName }); if (existingEgresses.length > 0 && existingEgresses.some((e) => e.status < 2)) { return new NextResponse('Meeting is already being recorded', { status: 409 }); } // Generate simple filename for S3 upload const fileName = `livekit/record/${roomName}.mp4`; console.log(
Generated filename: ${fileName}
); const fileOutput = new EncodedFileOutput({ filepath: fileName, output: { case: 's3', value: new S3Upload({ _// accessKey: AWS_ACCESS_KEY_ID!,_ _// secret: AWS_SECRET_ACCESS_KEY!,_ region: S3_REGION, bucket: S3_BUCKET_NAME, }), }, }); console.log('File output configuration:', JSON.stringify({ filepath: fileName, outputType: 's3', region: S3_REGION, bucket: S3_BUCKET_NAME }, null, 2)); // Use the exact method signature from your original working code console.log('Starting room composite egress...'); await egressClient.startRoomCompositeEgress( roomName, { file: fileOutput, }, { layout: 'speaker', }, ); console.log('Recording started successfully'); // Construct the URL of the saved file in S3 const fileUrl = `https://${S3_BUCKET_NAME}.s3.${S3_REGION}.amazonaws.com/${fileName}`; console.log(
Recording started. File will be saved to: ${fileUrl}
); // save the meeting record try { const response = await fetch(
${process.env.PYTHON_DISPATCHER_URL}/recordings
, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ room_name: roomName, recording_url: fileUrl, }), }); if (!response.ok) { console.error(
Failed to save recording link. Status: ${response.status}
); } } catch (saveError) { console.error(
Error saving recording link for room: ${roomName}
, saveError); } return new NextResponse(null, { status: 200 }); } catch (error) { if (error instanceof Error) { console.error('Error starting recording:', error.message); return new NextResponse(error.message, { status: 500 }); } console.error('Unknown error starting recording:', error); return new NextResponse('Internal server error', { status: 500 }); } }
t
Thanks for reaching out, Mohamed. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
The error seems to indicate that the 'ws_url' field might be missing or not quite right. Could you please double-check if the 'LIVEKIT_URL' environment variable is properly set to the WebSocket URL of your LiveKit server? Also, it would be great to confirm that you're using a valid API key and secret for authentication. I'm here to help you get this sorted out!