67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
import { createReadStream, promises } from "node:fs";
|
|
import type { Context } from "koa";
|
|
import Router from "koa-router";
|
|
import type { ContentContext } from "./context.ts";
|
|
|
|
export async function renderVideo(ctx: Context, path: string) {
|
|
const ext = path.trim().split(".").pop();
|
|
if (ext === undefined) {
|
|
// ctx.status = 404;
|
|
console.error(`${path}:${ext}`);
|
|
return;
|
|
}
|
|
ctx.response.type = ext;
|
|
const range_text = ctx.request.get("range");
|
|
const stat = await promises.stat(path);
|
|
let start = 0;
|
|
let end = 0;
|
|
ctx.set("Last-Modified", new Date(stat.mtime).toUTCString());
|
|
ctx.set("Date", new Date().toUTCString());
|
|
ctx.set("Accept-Ranges", "bytes");
|
|
if (range_text === "") {
|
|
end = 1024 * 512;
|
|
end = Math.min(end, stat.size - 1);
|
|
if (start > end) {
|
|
ctx.status = 416;
|
|
return;
|
|
}
|
|
ctx.status = 200;
|
|
ctx.length = stat.size;
|
|
const stream = createReadStream(path);
|
|
ctx.body = stream;
|
|
} else {
|
|
const m = range_text.match(/^bytes=(\d+)-(\d*)/);
|
|
if (m === null) {
|
|
ctx.status = 416;
|
|
return;
|
|
}
|
|
start = Number.parseInt(m[1]);
|
|
end = m[2].length > 0 ? Number.parseInt(m[2]) : start + 1024 * 1024;
|
|
end = Math.min(end, stat.size - 1);
|
|
if (start > end) {
|
|
ctx.status = 416;
|
|
return;
|
|
}
|
|
ctx.status = 206;
|
|
ctx.length = end - start + 1;
|
|
ctx.response.set("Content-Range", `bytes ${start}-${end}/${stat.size}`);
|
|
ctx.body = createReadStream(path, {
|
|
start: start,
|
|
end: end,
|
|
}); // inclusive range.
|
|
}
|
|
}
|
|
|
|
export class VideoRouter extends Router<ContentContext> {
|
|
constructor() {
|
|
super();
|
|
this.get("/", async (ctx, next) => {
|
|
await renderVideo(ctx, ctx.state.location.path);
|
|
});
|
|
this.get("/thumbnail", async (ctx, next) => {
|
|
await renderVideo(ctx, ctx.state.location.path);
|
|
});
|
|
}
|
|
}
|
|
|
|
export default VideoRouter;
|