import type { Express } from “express”;
import { Readable } from “node:stream”;
import { pipeline } from “node:stream/promises”;
import type { ReadableStream as NodeReadableStream } from “node:stream/web”;
const MPEG_TS_CONTENT_TYPE = “video/mp2t”;
/**
* Streams one server-configured live source through the site’s own HTTPS origin.
* The source URL is never accepted from request parameters and must be stored in
* the LIVE_STREAM_URL server environment variable.
*/
export function registerLiveProxy(app: Express) {
app.get(“/api/live”, async (_req, res) => {
const sourceUrl = process.env.LIVE_STREAM_URL?.trim();
if (!sourceUrl) {
res.status(503).json({ error: “Live stream source is not configured.” });
return;
}
let parsedSource: URL;
try {
parsedSource = new URL(sourceUrl);
} catch {
res.status(500).json({ error: “Live stream source configuration is invalid.” });
return;
}
if (parsedSource.protocol !== “http:” && parsedSource.protocol !== “https:”) {
res.status(500).json({ error: “Live stream source must use HTTP or HTTPS.” });
return;
}
const controller = new AbortController();
res.on(“close”, () => {
if (!res.writableEnded) controller.abort();
});
try {
const upstream = await fetch(parsedSource, {
method: “GET”,
redirect: “follow”,
signal: controller.signal,
headers: {
accept: “*/*”,
“user-agent”: “Mozilla/5.0”,
},
});
if (!upstream.ok || !upstream.body) {
await upstream.body?.cancel().catch(() => undefined);
if (!res.destroyed) {
res.status(502).json({ error: “The live stream source is unavailable.” });
}
return;
}
const upstreamType = upstream.headers.get(“content-type”)?.toLowerCase() ?? “”;
if (
upstreamType &&
!upstreamType.includes(“mp2t”) &&
!upstreamType.includes(“mpeg”) &&
!upstreamType.includes(“octet-stream”)
) {
await upstream.body.cancel().catch(() => undefined);
res.status(502).json({ error: “The upstream did not return an MPEG-TS stream.” });
return;
}
res.status(200);
res.setHeader(“Content-Type”, MPEG_TS_CONTENT_TYPE);
res.setHeader(“Cache-Control”, “no-store, no-transform”);
res.setHeader(“X-Accel-Buffering”, “no”);
res.flushHeaders();
await pipeline(
Readable.fromWeb(upstream.body as unknown as NodeReadableStream
res,
);
} catch {
if (controller.signal.aborted || res.destroyed) return;
// Never log the upstream URL: stream URLs commonly contain credentials.
console.error(“[LiveProxy] The upstream stream request failed.”);
if (!res.headersSent) {
res.status(502).json({ error: “The live stream could not be opened.” });
} else {
res.destroy();
}
}
});
}

Deixe um comentário