openvidu-recording-basic: Improve video seeking

This commit is contained in:
juancarmore 2024-09-23 12:02:52 +02:00
parent 081e45bc9e
commit 4f75e00f9d
2 changed files with 32 additions and 16 deletions

View File

@ -14,6 +14,7 @@ const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || "devkey";
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || "secret"; const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || "secret";
const RECORDINGS_PATH = process.env.RECORDINGS_PATH ?? "recordings/"; const RECORDINGS_PATH = process.env.RECORDINGS_PATH ?? "recordings/";
const RECORDING_FILE_PORTION_SIZE = 5 * 1024 * 1024; // 5MB
const app = express(); const app = express();
@ -175,6 +176,7 @@ const getRecordingInfo = async (payloadKey) => {
app.get("/recordings/:recordingName", async (req, res) => { app.get("/recordings/:recordingName", async (req, res) => {
const { recordingName } = req.params; const { recordingName } = req.params;
const { range } = req.headers;
const key = RECORDINGS_PATH + recordingName; const key = RECORDINGS_PATH + recordingName;
const exists = await s3Service.exists(key); const exists = await s3Service.exists(key);
@ -185,21 +187,38 @@ app.get("/recordings/:recordingName", async (req, res) => {
try { try {
// Get the recording file from S3 // Get the recording file from S3
const { body, size } = await s3Service.getObject(key); const { stream, size, start, end } = await getRecordingStream(recordingName, range);
// Set the response headers // Set response headers
res.status(206);
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Content-Type", "video/mp4"); res.setHeader("Content-Type", "video/mp4");
res.setHeader("Content-Length", size);
res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Accept-Ranges", "bytes");
res.setHeader("Content-Range", `bytes ${start}-${end}/${size}`);
res.setHeader("Content-Length", end - start + 1);
// Pipe the recording file to the response // Pipe the recording file to the response
body.pipe(res).on("finish", () => res.end()); stream.pipe(res).on("finish", () => res.end());
} catch (error) { } catch (error) {
console.error("Error getting recording.", error); console.error("Error getting recording.", error);
res.status(500).json({ errorMessage: "Error getting recording" }); res.status(500).json({ errorMessage: "Error getting recording" });
} }
}); });
const getRecordingStream = async (recordingName, range) => {
const key = RECORDINGS_PATH + recordingName;
const size = await s3Service.getObjectSize(key);
// Get the requested range
const parts = range?.replace(/bytes=/, "").split("-");
const start = range ? parseInt(parts[0], 10) : 0;
const endRange = parts[1] ? parseInt(parts[1], 10) : start + RECORDING_FILE_PORTION_SIZE;
const end = Math.min(endRange, size - 1);
const stream = await s3Service.getObject(key, { start, end });
return { stream, size, start, end };
};
app.delete("/recordings/:recordingName", async (req, res) => { app.delete("/recordings/:recordingName", async (req, res) => {
const { recordingName } = req.params; const { recordingName } = req.params;
const key = RECORDINGS_PATH + recordingName; const key = RECORDINGS_PATH + recordingName;

View File

@ -54,22 +54,23 @@ export class S3Service {
} }
async getObjectSize(key) { async getObjectSize(key) {
const { ContentLength } = await this.headObject(key); const { ContentLength: size } = await this.headObject(key);
return ContentLength; return size;
} }
async getObject(key) { async getObject(key, range) {
const params = { const params = {
Bucket: S3_BUCKET, Bucket: S3_BUCKET,
Key: key Key: key,
Range: range ? `bytes=${range.start}-${range.end}` : undefined
}; };
const command = new GetObjectCommand(params); const command = new GetObjectCommand(params);
const { Body: body, ContentLength: size } = await this.run(command); const { Body: body } = await this.run(command);
return { body, size }; return body;
} }
async getObjectAsJson(key) { async getObjectAsJson(key) {
const { body } = await this.getObject(key); const body = await this.getObject(key);
const stringifiedData = await body.transformToString(); const stringifiedData = await body.transformToString();
return JSON.parse(stringifiedData); return JSON.parse(stringifiedData);
} }
@ -83,11 +84,7 @@ export class S3Service {
const { Contents: objects } = await this.run(command); const { Contents: objects } = await this.run(command);
// Filter objects by regex and return the keys // Filter objects by regex and return the keys
return ( return objects?.filter((object) => regex.test(object.Key)).map((payload) => payload.Key) ?? [];
objects
?.filter((object) => regex.test(object.Key))
.map((payload) => payload.Key) ?? []
);
} }
async deleteObject(key) { async deleteObject(key) {