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:
parent
5b7baf2946
commit
685f4bed8b
@ -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;
|
||||
};
|
||||
|
||||
@ -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)
|
||||
};
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user