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 { 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;