import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
function freshStats() {
return {
thumbnailMissRequests: 0,
originalVideoRequests: 0,
originalVideoBytes: 0,
generatedThumbnailPuts: 0,
generatedThumbnailBytes: 0,
};
}
function pageHtml() {
return [
"",
"
thumbnail perf",
"",
].join("\n");
}
function parseRange(header, size) {
const match = /^bytes=(\d+)-(\d*)$/.exec(header ?? "");
if (!match) return null;
const start = Number(match[1]);
const end = match[2] ? Math.min(Number(match[2]), size - 1) : size - 1;
if (!Number.isSafeInteger(start) || start < 0 || start > end || start >= size)
return null;
return { start, end };
}
export async function startVideoThumbnailServer({ fixturePath }) {
const video = await readFile(fixturePath);
const html = Buffer.from(pageHtml());
let stats = freshStats();
const server = createServer(async (request, response) => {
try {
const url = new URL(request.url ?? "/", "http://127.0.0.1");
if (request.method === "GET" && url.pathname === "/") {
response.writeHead(200, {
"content-type": "text/html; charset=utf-8",
"content-length": html.byteLength,
"cache-control": "no-store",
});
response.end(html);
return;
}
if (request.method === "POST" && url.pathname === "/__reset") {
stats = freshStats();
response.writeHead(204, { "cache-control": "no-store" });
response.end();
return;
}
if (request.method === "GET" && url.pathname === "/__stats") {
const body = Buffer.from(JSON.stringify(stats));
response.writeHead(200, {
"content-type": "application/json",
"content-length": body.byteLength,
"cache-control": "no-store",
});
response.end(body);
return;
}
if (request.method === "GET" && url.pathname.startsWith("/thumbnail/")) {
stats.thumbnailMissRequests++;
response.writeHead(204, { "cache-control": "no-store" });
response.end();
return;
}
if (
request.method === "GET" &&
/^\/api\/files\/video-\d+$/.test(url.pathname)
) {
stats.originalVideoRequests++;
const range = parseRange(request.headers.range, video.byteLength);
const start = range?.start ?? 0;
const end = range?.end ?? video.byteLength - 1;
const body = video.subarray(start, end + 1);
stats.originalVideoBytes += body.byteLength;
response.writeHead(range ? 206 : 200, {
"content-type": "video/webm",
"content-length": body.byteLength,
"accept-ranges": "bytes",
"cache-control": "no-store",
...(range
? {
"content-range":
"bytes " + start + "-" + end + "/" + video.byteLength,
}
: {}),
});
response.end(body);
return;
}
if (
request.method === "PUT" &&
/^\/api\/files\/video-\d+\/thumbnail\/(icon|preview|large)$/.test(
url.pathname,
)
) {
let bytes = 0;
for await (const chunk of request) bytes += chunk.length;
stats.generatedThumbnailPuts++;
stats.generatedThumbnailBytes += bytes;
response.writeHead(201, { "content-length": "0" });
response.end();
return;
}
response.writeHead(404, { "content-length": "0" });
response.end();
} catch (error) {
response.writeHead(500, { "content-type": "text/plain" });
response.end(error instanceof Error ? error.message : String(error));
}
});
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
if (!address || typeof address === "string")
throw new Error("No loopback server address");
return {
url: "http://127.0.0.1:" + address.port + "/",
fixtureBytes: video.byteLength,
resetStats() {
stats = freshStats();
},
snapshot() {
return { ...stats };
},
async close() {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
},
};
}