217 lines
7.4 KiB
TypeScript
217 lines
7.4 KiB
TypeScript
import { EgressStatus } from '@livekit/protocol';
|
|
import { MeetRecordingInfo, MeetRecordingStatus } from '@typings-ce';
|
|
import { EgressInfo } from 'livekit-server-sdk';
|
|
import { uid as secureUid } from 'uid/secure';
|
|
import { container } from '../config/index.js';
|
|
import { RoomService } from '../services/index.js';
|
|
|
|
export class RecordingHelper {
|
|
private constructor() {
|
|
// Prevent instantiation of this utility class
|
|
}
|
|
|
|
static async toRecordingInfo(egressInfo: EgressInfo): Promise<MeetRecordingInfo> {
|
|
const status = RecordingHelper.extractOpenViduStatus(egressInfo.status);
|
|
const size = RecordingHelper.extractSize(egressInfo);
|
|
// const outputMode = RecordingHelper.extractOutputMode(egressInfo);
|
|
const duration = RecordingHelper.extractDuration(egressInfo);
|
|
const startDateMs = RecordingHelper.extractStartDate(egressInfo);
|
|
const endDateMs = RecordingHelper.extractEndDate(egressInfo);
|
|
const filename = RecordingHelper.extractFilename(egressInfo);
|
|
const uid = RecordingHelper.extractUidFromFilename(filename);
|
|
const { egressId, roomName: roomId, errorCode, error, details } = egressInfo;
|
|
|
|
const roomService = container.get(RoomService);
|
|
const { roomName } = await roomService.getMeetRoom(roomId);
|
|
|
|
return {
|
|
recordingId: `${roomId}--${egressId}--${uid}`,
|
|
roomId,
|
|
roomName,
|
|
// outputMode,
|
|
status,
|
|
filename,
|
|
startDate: startDateMs,
|
|
endDate: endDateMs,
|
|
duration,
|
|
size,
|
|
errorCode: errorCode ? Number(errorCode) : undefined,
|
|
error: error ? String(error) : undefined,
|
|
details: details ? String(details) : undefined
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Checks if the egress is for recording.
|
|
* @param egress - The egress information.
|
|
* @returns A boolean indicating if the egress is for recording.
|
|
*/
|
|
static isRecordingEgress(egress: EgressInfo): boolean {
|
|
const { streamResults = [], fileResults = [] } = egress;
|
|
return fileResults.length > 0 && streamResults.length === 0;
|
|
}
|
|
|
|
static canBeDeleted(recordingInfo: MeetRecordingInfo): boolean {
|
|
const { status } = recordingInfo;
|
|
const isFinished = [
|
|
MeetRecordingStatus.COMPLETE,
|
|
MeetRecordingStatus.FAILED,
|
|
MeetRecordingStatus.ABORTED,
|
|
MeetRecordingStatus.LIMIT_REACHED
|
|
].includes(status);
|
|
return isFinished;
|
|
}
|
|
|
|
static extractOpenViduStatus(status: EgressStatus | undefined): MeetRecordingStatus {
|
|
switch (status) {
|
|
case EgressStatus.EGRESS_STARTING:
|
|
return MeetRecordingStatus.STARTING;
|
|
case EgressStatus.EGRESS_ACTIVE:
|
|
return MeetRecordingStatus.ACTIVE;
|
|
case EgressStatus.EGRESS_ENDING:
|
|
return MeetRecordingStatus.ENDING;
|
|
case EgressStatus.EGRESS_COMPLETE:
|
|
return MeetRecordingStatus.COMPLETE;
|
|
case EgressStatus.EGRESS_FAILED:
|
|
return MeetRecordingStatus.FAILED;
|
|
case EgressStatus.EGRESS_ABORTED:
|
|
return MeetRecordingStatus.ABORTED;
|
|
case EgressStatus.EGRESS_LIMIT_REACHED:
|
|
return MeetRecordingStatus.LIMIT_REACHED;
|
|
default:
|
|
return MeetRecordingStatus.FAILED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts the OpenVidu output mode based on the provided egress information.
|
|
* If the egress information contains roomComposite, it returns RecordingOutputMode.COMPOSED.
|
|
* Otherwise, it returns RecordingOutputMode.INDIVIDUAL.
|
|
*
|
|
* @param egressInfo - The egress information containing the roomComposite flag.
|
|
* @returns The extracted OpenVidu output mode.
|
|
*/
|
|
// static extractOutputMode(egressInfo: EgressInfo): MeetRecordingOutputMode {
|
|
// // if (egressInfo.request.case === 'roomComposite') {
|
|
// // return MeetRecordingOutputMode.COMPOSED;
|
|
// // } else {
|
|
// // return MeetRecordingOutputMode.INDIVIDUAL;
|
|
// // }
|
|
// return MeetRecordingOutputMode.COMPOSED;
|
|
// }
|
|
|
|
/**
|
|
* Extracts the filename/path for storing the recording.
|
|
* For EgressInfo, returns the last segment of the fileResults.
|
|
* For MeetRecordingInfo, returns a combination of roomId and filename.
|
|
*/
|
|
static extractFilename(recordingInfo: MeetRecordingInfo): string;
|
|
|
|
static extractFilename(egressInfo: EgressInfo): string;
|
|
|
|
static extractFilename(info: MeetRecordingInfo | EgressInfo): string {
|
|
if ('request' in info) {
|
|
// EgressInfo
|
|
return info.fileResults[0]!.filename.split('/').pop()!;
|
|
} else {
|
|
// MeetRecordingInfo
|
|
const { filename, roomId } = info;
|
|
|
|
return `${roomId}/${filename}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts the UID from the given filename.
|
|
*
|
|
* @param filename room-123--{uid}.mp4
|
|
* @returns
|
|
*/
|
|
static extractUidFromFilename(filename: string): string {
|
|
const uidWithExtension = filename.split('--')[1];
|
|
return uidWithExtension.split('.')[0];
|
|
}
|
|
|
|
/**
|
|
* Extracts the room name, egressId, and UID from the given recordingId.
|
|
* @param recordingId ${roomId}--${egressId}--${uid}
|
|
*/
|
|
static extractInfoFromRecordingId(recordingId: string): { roomId: string; egressId: string; uid: string } {
|
|
const [roomId, egressId, uid] = recordingId.split('--');
|
|
|
|
if (!roomId || !egressId || !uid) {
|
|
throw new Error(`Invalid recordingId format: ${recordingId}`);
|
|
}
|
|
|
|
return { roomId, egressId, uid };
|
|
}
|
|
|
|
/**
|
|
* Extracts the duration from the given egress information.
|
|
* If the duration is not available, it returns 0.
|
|
* @param egressInfo The egress information containing the file results.
|
|
* @returns The duration in milliseconds.
|
|
*/
|
|
static extractDuration(egressInfo: EgressInfo): number | undefined {
|
|
const duration = this.toSeconds(Number(egressInfo.fileResults?.[0]?.duration ?? 0));
|
|
return duration !== 0 ? duration : undefined;
|
|
}
|
|
|
|
/**
|
|
* Extracts the endedAt value from the given EgressInfo object and converts it to milliseconds.
|
|
* If the endedAt value is not provided, it defaults to 0.
|
|
*
|
|
* @param egressInfo - The EgressInfo object containing the endedAt value.
|
|
* @returns The endedAt value converted to milliseconds.
|
|
*/
|
|
static extractEndDate(egressInfo: EgressInfo): number | undefined {
|
|
const endDateMs = this.toMilliseconds(Number(egressInfo.endedAt ?? 0));
|
|
return endDateMs !== 0 ? endDateMs : undefined;
|
|
}
|
|
|
|
/**
|
|
* Extracts the creation timestamp from the given EgressInfo object.
|
|
* If the startedAt property is not defined, it returns 0.
|
|
* @param egressInfo The EgressInfo object from which to extract the creation timestamp.
|
|
* @returns The creation timestamp in milliseconds.
|
|
*/
|
|
static extractStartDate(egressInfo: EgressInfo): number {
|
|
const { startedAt, updatedAt } = egressInfo;
|
|
const createdAt = startedAt && Number(startedAt) !== 0 ? startedAt : (updatedAt ?? 0);
|
|
return this.toMilliseconds(Number(createdAt));
|
|
}
|
|
|
|
/**
|
|
* Extracts the size from the given EgressInfo object.
|
|
* If the size is not available, it returns 0.
|
|
*
|
|
* @param egressInfo - The EgressInfo object to extract the size from.
|
|
* @returns The size extracted from the EgressInfo object, or 0 if not available.
|
|
*/
|
|
static extractSize(egressInfo: EgressInfo): number | undefined {
|
|
const size = Number(egressInfo.fileResults?.[0]?.size ?? 0);
|
|
return size !== 0 ? size : undefined;
|
|
}
|
|
|
|
/**
|
|
* Builds the secrets for public and private access to recordings.
|
|
* @returns An object containing public and private access secrets.
|
|
*/
|
|
static buildAccessSecrets(): { publicAccessSecret: string; privateAccessSecret: string } {
|
|
return {
|
|
publicAccessSecret: secureUid(10),
|
|
privateAccessSecret: secureUid(10)
|
|
};
|
|
}
|
|
|
|
private static toSeconds(nanoseconds: number): number {
|
|
const nanosecondsToSeconds = 1 / 1_000_000_000;
|
|
return nanoseconds * nanosecondsToSeconds;
|
|
}
|
|
|
|
private static toMilliseconds(nanoseconds: number): number {
|
|
const nanosecondsToMilliseconds = 1 / 1_000_000;
|
|
return nanoseconds * nanosecondsToMilliseconds;
|
|
}
|
|
}
|