openvidu-recording-basic: Improve video seeking
This commit is contained in:
parent
081e45bc9e
commit
4f75e00f9d
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user