backend: refactor recording start process and add room validation checks

This commit is contained in:
Carlos Santos 2025-04-23 18:13:22 +02:00
parent b87e548cdf
commit 7a8f3fbe69

View File

@ -57,35 +57,24 @@ export class RecordingService {
async startRecording(roomId: string): Promise<MeetRecordingInfo> { async startRecording(roomId: string): Promise<MeetRecordingInfo> {
let acquiredLock: RedisLock | null = null; let acquiredLock: RedisLock | null = null;
let eventListener!: (info: Record<string, unknown>) => void;
try { try {
const room = await this.roomService.getMeetRoom(roomId); await this.validateRoomsPreconditions(roomId);
if (!room) throw errorRoomNotFound(roomId);
//TODO: Check if the room has participants before starting the recording
//room.numParticipants === 0 ? throw errorNoParticipants(roomId);
const lkRoom = await this.livekitService.getRoom(roomId);
if (!lkRoom) throw errorRoomNotFound(roomId);
const hasParticipants = await this.livekitService.roomHasParticipants(roomId);
if (!hasParticipants) throw errorRoomHasNoParticipants(roomId);
// Attempt to acquire lock. If the lock is not acquired, the recording is already active. // Attempt to acquire lock. If the lock is not acquired, the recording is already active.
acquiredLock = await this.acquireRoomRecordingActiveLock(roomId); acquiredLock = await this.acquireRoomRecordingActiveLock(roomId);
if (!acquiredLock) throw errorRecordingAlreadyStarted(roomId); if (!acquiredLock) throw errorRecordingAlreadyStarted(roomId);
const options = this.generateCompositeOptionsFromRequest(); let resolveRecording!: (r: MeetRecordingInfo) => void;
const output = this.generateFileOutputFromRequest(roomId); let rejectRecording!: (e: unknown) => void;
const egressInfo = await this.livekitService.startRoomComposite(roomId, output, options); const recordingPromise = new Promise<MeetRecordingInfo>((res, rej) => {
const recordingInfo = RecordingHelper.toRecordingInfo(egressInfo); resolveRecording = res;
const { recordingId } = recordingInfo; rejectRecording = rej;
});
let recordingId = '';
const recordingPromise = new Promise<MeetRecordingInfo>((resolve, reject) => { eventListener = (info: Record<string, unknown>) => {
const eventListener = (info: Record<string, unknown>) => {
// This listener is triggered only for the instance that started the recording. // This listener is triggered only for the instance that started the recording.
// Check if the recording ID matches the one that was started // Check if the recording ID matches the one that was started
const isEventForCurrentRecording = info?.recordingId === recordingId && info?.roomId === roomId; const isEventForCurrentRecording = info?.recordingId === recordingId && info?.roomId === roomId;
@ -93,14 +82,18 @@ export class RecordingService {
if (isEventForCurrentRecording) { if (isEventForCurrentRecording) {
this.taskSchedulerService.cancelTask(`${roomId}_recording_timeout`); this.taskSchedulerService.cancelTask(`${roomId}_recording_timeout`);
this.systemEventService.off(SystemEventType.RECORDING_ACTIVE, eventListener); this.systemEventService.off(SystemEventType.RECORDING_ACTIVE, eventListener);
resolve(info as unknown as MeetRecordingInfo); resolveRecording(info as unknown as MeetRecordingInfo);
} }
}; };
this.registerRecordingTimeout(roomId, recordingId, eventListener, reject);
this.systemEventService.on(SystemEventType.RECORDING_ACTIVE, eventListener); this.systemEventService.on(SystemEventType.RECORDING_ACTIVE, eventListener);
}); this.registerRecordingTimeout(roomId, recordingId, eventListener, rejectRecording);
const options = this.generateCompositeOptionsFromRequest();
const output = this.generateFileOutputFromRequest(roomId);
const egressInfo = await this.livekitService.startRoomComposite(roomId, output, options);
const recordingInfo = RecordingHelper.toRecordingInfo(egressInfo);
recordingId = recordingInfo.recordingId;
return await recordingPromise; return await recordingPromise;
} catch (error) { } catch (error) {
@ -108,6 +101,10 @@ export class RecordingService {
if (acquiredLock) await this.releaseRoomRecordingActiveLock(roomId); if (acquiredLock) await this.releaseRoomRecordingActiveLock(roomId);
if (eventListener) this.systemEventService.off(SystemEventType.RECORDING_ACTIVE, eventListener);
this.taskSchedulerService.cancelTask(`${roomId}_recording_timeout`);
throw error; throw error;
} }
} }
@ -369,6 +366,22 @@ export class RecordingService {
return this.roomService.sendSignal(roomId, payload, options); return this.roomService.sendSignal(roomId, payload, options);
} }
protected async validateRoomsPreconditions(roomId: string): Promise<void> {
const room = await this.roomService.getMeetRoom(roomId);
if (!room) throw errorRoomNotFound(roomId);
//TODO: Check if the room has participants before starting the recording
//room.numParticipants === 0 ? throw errorNoParticipants(roomId);
const lkRoom = await this.livekitService.getRoom(roomId);
if (!lkRoom) throw errorRoomNotFound(roomId);
const hasParticipants = await this.livekitService.roomHasParticipants(roomId);
if (!hasParticipants) throw errorRoomHasNoParticipants(roomId);
}
/** /**
* Retrieves the data required to delete a recording, including the file paths * Retrieves the data required to delete a recording, including the file paths
* to be deleted and the recording's metadata information. * to be deleted and the recording's metadata information.