From 1095fc6b8416479dda9979f46ecf3d305c646a50 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Mon, 9 Sep 2024 21:07:34 +0200 Subject: [PATCH] openvidu-recording-improved: Improve recording status update system --- .../public/app.js | 75 ++++++++++--------- .../src/controllers/webhook.controller.js | 36 ++++++++- .../src/index.js | 31 +++++++- 3 files changed, 101 insertions(+), 41 deletions(-) diff --git a/advanced-features/openvidu-recording-improved-node/public/app.js b/advanced-features/openvidu-recording-improved-node/public/app.js index d110eb48..e1dec7bf 100644 --- a/advanced-features/openvidu-recording-improved-node/public/app.js +++ b/advanced-features/openvidu-recording-improved-node/public/app.js @@ -42,8 +42,9 @@ async function joinRoom() { }); // When recording status changes... - room.on(LivekitClient.RoomEvent.RecordingStatusChanged, async (isRecording) => { - await updateRecordingInfo(isRecording); + room.on(LivekitClient.RoomEvent.RoomMetadataChanged, async (metadata) => { + const { recordingStatus } = JSON.parse(metadata); + await updateRecordingInfo(recordingStatus); }); try { @@ -68,7 +69,13 @@ async function joinRoom() { addTrack(localVideoTrack, userName, true); // Update recording info - await updateRecordingInfo(room.isRecording); + const { recordingStatus } = JSON.parse(room.metadata); + await updateRecordingInfo(recordingStatus); + + if (recordingStatus !== "STOPPED" && recordingStatus !== "FAILED") { + const roomId = await room.getSid(); + await listRecordings(room.name, roomId); + } } catch (error) { console.log("There was an error connecting to the room:", error.message); await leaveRoom(); @@ -200,49 +207,47 @@ async function getToken(roomName, participantName) { return body.token; } -async function updateRecordingInfo(isRecording) { +async function updateRecordingInfo(recordingStatus) { const recordingButton = document.getElementById("recording-button"); const recordingText = document.getElementById("recording-text"); - if (isRecording) { - recordingButton.disabled = false; - recordingButton.innerText = "Stop Recording"; - recordingButton.className = "btn btn-danger"; - recordingText.hidden = false; - } else { - recordingButton.disabled = false; - recordingButton.innerText = "Start Recording"; - recordingButton.className = "btn btn-primary"; - recordingText.hidden = true; - } + switch (recordingStatus) { + case "STARTING": + recordingButton.disabled = true; + recordingButton.innerText = "Starting..."; + break; + case "STARTED": + recordingButton.disabled = false; + recordingButton.innerText = "Stop Recording"; + recordingButton.className = "btn btn-danger"; + recordingText.hidden = false; + break; + case "STOPPING": + recordingButton.disabled = true; + recordingButton.innerText = "Stopping..."; + recordingButton.className = "btn btn-danger"; + recordingText.hidden = false; + break; + case "STOPPED": + case "FAILED": + recordingButton.disabled = false; + recordingButton.innerText = "Start Recording"; + recordingButton.className = "btn btn-primary"; + recordingText.hidden = true; - const roomId = await room.getSid(); - await listRecordings(room.name, roomId); + const roomId = await room.getSid(); + await listRecordings(room.name, roomId); + break; + } } async function manageRecording() { const recordingButton = document.getElementById("recording-button"); if (recordingButton.innerText === "Start Recording") { - recordingButton.disabled = true; - recordingButton.innerText = "Starting..."; - - const [error, _] = await startRecording(); - - if (error && error.status !== 409) { - recordingButton.disabled = false; - recordingButton.innerText = "Start Recording"; - } + await startRecording(); } else { - recordingButton.disabled = true; - recordingButton.innerText = "Stopping..."; - - const [error, _] = await stopRecording(); - - if (error && error.status !== 409) { - recordingButton.disabled = false; - recordingButton.innerText = "Stop Recording"; - } + await stopRecording(); } } diff --git a/advanced-features/openvidu-recording-improved-node/src/controllers/webhook.controller.js b/advanced-features/openvidu-recording-improved-node/src/controllers/webhook.controller.js index e36dd028..7e9c1824 100644 --- a/advanced-features/openvidu-recording-improved-node/src/controllers/webhook.controller.js +++ b/advanced-features/openvidu-recording-improved-node/src/controllers/webhook.controller.js @@ -1,10 +1,11 @@ import express, { Router } from "express"; -import { WebhookReceiver } from "livekit-server-sdk"; -import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from "../config.js"; +import { EgressStatus, RoomServiceClient, WebhookReceiver } from "livekit-server-sdk"; +import { LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from "../config.js"; import { S3Service } from "../services/s3.service.js"; const webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET); const s3Service = new S3Service(); +const roomClient = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET); export const webhookController = Router(); webhookController.use(express.raw({ type: "application/webhook+json" })); @@ -31,7 +32,7 @@ webhookController.post("/", async (req, res) => { }); const handleEgressUpdated = async (egressInfo) => { - + await updateRecordingStatus(egressInfo); }; const handleEgressEnded = async (egressInfo) => { @@ -39,6 +40,8 @@ const handleEgressEnded = async (egressInfo) => { const metadataName = recordingInfo.name.replace(".mp4", ".json"); const key = `recordings/.metadata/${metadataName}`; await s3Service.uploadObject(key, recordingInfo); + + await updateRecordingStatus(egressInfo); }; const convertToRecordingInfo = (egressInfo) => { @@ -50,3 +53,30 @@ const convertToRecordingInfo = (egressInfo) => { size: Number(file.size) }; }; + +const updateRecordingStatus = async (egressInfo) => { + const roomName = egressInfo.roomName; + const recordingStatus = getRecordingStatus(egressInfo.status); + await roomClient.updateRoomMetadata( + roomName, + JSON.stringify({ + createdBy: "openvidu-recording-improved-tutorial", + recordingStatus + }) + ); +}; + +const getRecordingStatus = (egressStatus) => { + switch (egressStatus) { + case EgressStatus.EGRESS_STARTING: + return "STARTING"; + case EgressStatus.EGRESS_ACTIVE: + return "STARTED"; + case EgressStatus.EGRESS_ENDING: + return "STOPPING"; + case EgressStatus.EGRESS_COMPLETE: + return "STOPPED"; + default: + return "FAILED"; + } +}; diff --git a/advanced-features/openvidu-recording-improved-node/src/index.js b/advanced-features/openvidu-recording-improved-node/src/index.js index a8bc5b89..4fd92019 100644 --- a/advanced-features/openvidu-recording-improved-node/src/index.js +++ b/advanced-features/openvidu-recording-improved-node/src/index.js @@ -3,8 +3,8 @@ import express from "express"; import cors from "cors"; import path from "path"; import { fileURLToPath } from "url"; -import { AccessToken } from "livekit-server-sdk"; -import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, SERVER_PORT } from "./config.js"; +import { AccessToken, RoomServiceClient } from "livekit-server-sdk"; +import { LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET, SERVER_PORT } from "./config.js"; import { recordingController } from "./controllers/recording.controller.js"; import { webhookController } from "./controllers/webhook.controller.js"; @@ -33,8 +33,33 @@ app.post("/token", async (req, res) => { const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, { identity: participantName }); - at.addGrant({ roomJoin: true, room: roomName, roomRecord: true }); + const permissions = { + room: roomName, + roomJoin: true, + roomAdmin: true, + roomList: true, + roomRecord: true + }; + at.addGrant(permissions); const token = await at.toJwt(); + + const roomClient = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET); + + // Check if room already exists + const rooms = await roomClient.listRooms([roomName]); + + // Create room if it doesn't exist + if (rooms.length === 0) { + const roomOptions = { + name: roomName, + metadata: JSON.stringify({ + createdBy: "openvidu-recording-improved-tutorial", + recordingStatus: "STOPPED" + }) + }; + await roomClient.createRoom(roomOptions); + } + res.json({ token }); });