openvidu-recording-improved: Refactor code to upload custom recording metadata to S3 when "egress_ended" webhook is received instead of using LiveKit Egress manifest autosave

This commit is contained in:
juancarmore 2024-09-09 18:45:44 +02:00
parent 5b7baf2946
commit 685f4bed8b
3 changed files with 59 additions and 33 deletions

View File

@ -27,7 +27,8 @@ recordingController.post("/start", async (req, res) => {
// Use the EncodedFileOutput to save the recording to an MP4 file
const fileOutput = new EncodedFileOutput({
fileType: EncodedFileType.MP4,
filepath: `${RECORDINGS_PATH}{room_name}-{room_id}-{time}`
filepath: `${RECORDINGS_PATH}{room_name}-{room_id}-{time}`,
disableManifest: true
});
try {
@ -69,6 +70,7 @@ recordingController.post("/stop", async (req, res) => {
const recording = {
name: file.filename.split("/").pop(),
startedAt: Number(egressInfo.startedAt) / 1_000_000,
duration: Number(file.duration) / 1_000_000_000,
size: Number(file.size)
};
res.json({ message: "Recording stopped", recording });
@ -83,13 +85,14 @@ recordingController.get("/", async (req, res) => {
const roomId = req.query.roomId?.toString();
try {
const keyStart = RECORDINGS_PATH + (roomName ? `${roomName}` + (roomId ? `-${roomId}` : "") : "");
const keyEnd = ".mp4.json";
const keyStart =
RECORDINGS_PATH + ".metadata/" + (roomName ? `${roomName}` + (roomId ? `-${roomId}` : "") : "");
const keyEnd = ".json";
const regex = new RegExp(`^${keyStart}.*${keyEnd}$`);
// List all Egress metadata files in the recordings path that match the regex
const payloadKeys = await s3Service.listObjects(RECORDINGS_PATH, regex);
const recordings = await Promise.all(payloadKeys.map((payloadKey) => getRecordingInfo(payloadKey)));
const metadataKeys = await s3Service.listObjects(RECORDINGS_PATH + ".metadata/", regex);
const recordings = await Promise.all(metadataKeys.map((metadataKey) => s3Service.getObjectAsJson(metadataKey)));
res.json({ recordings });
} catch (error) {
console.error("Error listing recordings.", error);
@ -126,8 +129,9 @@ recordingController.get("/:recordingName", async (req, res) => {
recordingController.delete("/:recordingName", async (req, res) => {
const { recordingName } = req.params;
const key = RECORDINGS_PATH + recordingName;
const exists = await s3Service.exists(key);
const recordingKey = RECORDINGS_PATH + recordingName;
const metadataKey = RECORDINGS_PATH + ".metadata/" + recordingName.replace(".mp4", ".json");
const exists = await s3Service.exists(recordingKey);
if (!exists) {
res.status(404).json({ errorMessage: "Recording not found" });
@ -136,7 +140,7 @@ recordingController.delete("/:recordingName", async (req, res) => {
try {
// Delete the recording file and metadata file from S3
await Promise.all([s3Service.deleteObject(key), s3Service.deleteObject(`${key}.json`)]);
await Promise.all([s3Service.deleteObject(recordingKey), s3Service.deleteObject(metadataKey)]);
res.json({ message: "Recording deleted" });
} catch (error) {
console.error("Error deleting recording.", error);
@ -153,20 +157,3 @@ const getActiveEgressesByRoom = async (roomName) => {
return [];
}
};
const getRecordingInfo = async (payloadKey) => {
// Get the Egress metadata file as JSON
const data = await s3Service.getObjectAsJson(payloadKey);
// Get the recording file size
const recordingKey = payloadKey.replace(".json", "");
const size = await s3Service.getObjectSize(recordingKey);
const recordingName = recordingKey.split("/").pop();
const recording = {
name: recordingName,
startedAt: Number(data.started_at) / 1000000,
size: size
};
return recording;
};

View File

@ -1,8 +1,10 @@
import express, { Router } from "express";
import { WebhookReceiver } from "livekit-server-sdk";
import { 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();
export const webhookController = Router();
webhookController.use(express.raw({ type: "application/webhook+json" }));
@ -11,9 +13,40 @@ webhookController.post("/", async (req, res) => {
try {
const event = await webhookReceiver.receive(req.body, req.get("Authorization"));
console.log(event);
switch (event.event) {
case "egress_started":
case "egress_updated":
await handleEgressUpdated(event.egressInfo);
break;
case "egress_ended":
await handleEgressEnded(event.egressInfo);
break;
}
} catch (error) {
console.error("Error validating webhook event.", error);
}
res.status(200).send();
});
const handleEgressUpdated = async (egressInfo) => {
};
const handleEgressEnded = async (egressInfo) => {
const recordingInfo = convertToRecordingInfo(egressInfo);
const metadataName = recordingInfo.name.replace(".mp4", ".json");
const key = `recordings/.metadata/${metadataName}`;
await s3Service.uploadObject(key, recordingInfo);
};
const convertToRecordingInfo = (egressInfo) => {
const file = egressInfo.fileResults[0];
return {
name: file.filename.split("/").pop(),
startedAt: Number(egressInfo.startedAt) / 1_000_000,
duration: Number(file.duration) / 1_000_000_000,
size: Number(file.size)
};
};

View File

@ -3,7 +3,8 @@ import {
GetObjectCommand,
DeleteObjectCommand,
ListObjectsV2Command,
HeadObjectCommand
HeadObjectCommand,
PutObjectCommand
} from "@aws-sdk/client-s3";
import { AWS_REGION, S3_ACCESS_KEY, S3_BUCKET, S3_ENDPOINT, S3_SECRET_KEY } from "../config.js";
@ -29,6 +30,16 @@ export class S3Service {
return this;
}
async uploadObject(key, body) {
const params = {
Bucket: S3_BUCKET,
Key: key,
Body: JSON.stringify(body)
};
const command = new PutObjectCommand(params);
return this.run(command);
}
async exists(key) {
try {
await this.headObject(key);
@ -47,11 +58,6 @@ export class S3Service {
return this.run(command);
}
async getObjectSize(key) {
const { ContentLength } = await this.headObject(key);
return ContentLength;
}
async getObject(key) {
const params = {
Bucket: S3_BUCKET,