diff --git a/advanced-features/openvidu-recording-improved-node/public/app.js b/advanced-features/openvidu-recording-improved-node/public/app.js index e1dec7bf..cef9860d 100644 --- a/advanced-features/openvidu-recording-improved-node/public/app.js +++ b/advanced-features/openvidu-recording-improved-node/public/app.js @@ -47,6 +47,15 @@ async function joinRoom() { await updateRecordingInfo(recordingStatus); }); + // When a message is received... + room.on(LivekitClient.RoomEvent.DataReceived, async (payload, _participant, _kind, topic) => { + // If the message is a recording deletion notification, remove the recording from the list + if (topic === "RECORDING_DELETED") { + const { recordingName } = JSON.parse(new TextDecoder().decode(payload)); + deleteRecordingContainer(recordingName); + } + }); + try { // Get the room name and participant name from the form const roomName = document.getElementById("room-name").value; @@ -267,8 +276,21 @@ async function deleteRecording(recordingName) { const [error, _] = await httpRequest("DELETE", `/recordings/${recordingName}`); if (!error || error.status === 404) { - const roomId = await room?.getSid(); - await listRecordings(room?.name, roomId); + deleteRecordingContainer(recordingName); + } +} + +function deleteRecordingContainer(recordingName) { + const recordingContainer = document.getElementById(recordingName); + + if (recordingContainer) { + recordingContainer.remove(); + + const recordingsList = document.getElementById("recording-list"); + + if (recordingsList.children.length === 0) { + recordingsList.innerHTML = "There are no recordings available"; + } } } diff --git a/advanced-features/openvidu-recording-improved-node/src/controllers/recording.controller.js b/advanced-features/openvidu-recording-improved-node/src/controllers/recording.controller.js index 6e685c0b..9992d36e 100644 --- a/advanced-features/openvidu-recording-improved-node/src/controllers/recording.controller.js +++ b/advanced-features/openvidu-recording-improved-node/src/controllers/recording.controller.js @@ -1,7 +1,9 @@ import { Router } from "express"; import { RecordingService } from "../services/recording.service.js"; +import { RoomService } from "../services/room.service.js"; const recordingService = new RecordingService(); +const roomService = new RoomService(); export const recordingController = Router(); @@ -104,7 +106,11 @@ recordingController.delete("/:recordingName", async (req, res) => { } try { + const { roomName } = await recordingService.getRecordingMetadata(recordingName); await recordingService.deleteRecording(recordingName); + + // Notify to all participants that the recording was deleted + await roomService.sendDataToRoom(roomName, { recordingName }); res.json({ message: "Recording deleted" }); } catch (error) { console.error("Error deleting recording.", error); diff --git a/advanced-features/openvidu-recording-improved-node/src/services/recording.service.js b/advanced-features/openvidu-recording-improved-node/src/services/recording.service.js index a404e025..50c1da14 100644 --- a/advanced-features/openvidu-recording-improved-node/src/services/recording.service.js +++ b/advanced-features/openvidu-recording-improved-node/src/services/recording.service.js @@ -1,5 +1,11 @@ import { EgressClient, EgressStatus, EncodedFileOutput, EncodedFileType } from "livekit-server-sdk"; -import { LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET, RECORDINGS_PATH, RECORDINGS_METADATA_PATH } from "../config.js"; +import { + LIVEKIT_URL, + LIVEKIT_API_KEY, + LIVEKIT_API_SECRET, + RECORDINGS_PATH, + RECORDINGS_METADATA_PATH +} from "../config.js"; import { S3Service } from "./s3.service.js"; const s3Service = new S3Service(); @@ -58,34 +64,41 @@ export class RecordingService { } } + async getRecordingMetadata(recordingName) { + const key = this.getMetadataKey(recordingName); + return s3Service.getObjectAsJson(key); + } + async getRecordingStream(recordingName) { - const key = RECORDINGS_PATH + recordingName; + const key = this.getRecordingKey(recordingName); return s3Service.getObject(key); } async existsRecording(recordingName) { - const key = RECORDINGS_PATH + recordingName; + const key = this.getRecordingKey(recordingName); return s3Service.exists(key); } async deleteRecording(recordingName) { const recordingKey = RECORDINGS_PATH + recordingName; - const metadataKey = RECORDINGS_PATH + RECORDINGS_METADATA_PATH + recordingName.replace(".mp4", ".json"); + const metadataKey = this.getMetadataKey(recordingName); // Delete the recording file and metadata file from S3 await Promise.all([s3Service.deleteObject(recordingKey), s3Service.deleteObject(metadataKey)]); } async saveRecordingMetadata(egressInfo) { const recordingInfo = this.convertToRecordingInfo(egressInfo); - const metadataName = recordingInfo.name.replace(".mp4", ".json"); - const key = RECORDINGS_PATH + RECORDINGS_METADATA_PATH + metadataName; + const key = this.getMetadataKey(recordingInfo.name); await s3Service.uploadObject(key, recordingInfo); } convertToRecordingInfo(egressInfo) { const file = egressInfo.fileResults[0]; return { + id: egressInfo.egressId, name: file.filename.split("/").pop(), + roomName: egressInfo.roomName, + roomId: egressInfo.roomId, startedAt: Number(egressInfo.startedAt) / 1_000_000, duration: Number(file.duration) / 1_000_000_000, size: Number(file.size) @@ -106,4 +119,12 @@ export class RecordingService { return "FAILED"; } } + + getRecordingKey(recordingName) { + return RECORDINGS_PATH + recordingName; + } + + getMetadataKey(recordingName) { + return RECORDINGS_PATH + RECORDINGS_METADATA_PATH + recordingName.replace(".mp4", ".json"); + } } diff --git a/advanced-features/openvidu-recording-improved-node/src/services/room.service.js b/advanced-features/openvidu-recording-improved-node/src/services/room.service.js index 8b04820a..8c79323b 100644 --- a/advanced-features/openvidu-recording-improved-node/src/services/room.service.js +++ b/advanced-features/openvidu-recording-improved-node/src/services/room.service.js @@ -1,6 +1,8 @@ -import { RoomServiceClient } from "livekit-server-sdk"; +import { DataPacket_Kind, RoomServiceClient } from "livekit-server-sdk"; import { LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET, APP_NAME } from "../config.js"; +const encoder = new TextEncoder(); + export class RoomService { static instance; @@ -37,4 +39,18 @@ export class RoomService { }; return this.roomClient.updateRoomMetadata(roomName, JSON.stringify(metadata)); } + + async sendDataToRoom(roomName, rawData) { + const data = encoder.encode(JSON.stringify(rawData)); + const options = { + topic: "RECORDING_DELETED", + destinationSids: [] + }; + + try { + await this.roomClient.sendData(roomName, data, DataPacket_Kind.RELIABLE, options); + } catch (error) { + console.error("Error sending data to room", error); + } + } }