Spaces:
Paused
Paused
| import { request } from "undici"; | |
| import { Readable } from "node:stream"; | |
| import { closeRequest, getHeaders, pipe } from "./shared.js"; | |
| import { handleHlsPlaylist, isHlsRequest } from "./internal-hls.js"; | |
| const CHUNK_SIZE = BigInt(8e6); // 8 MB | |
| const min = (a, b) => a < b ? a : b; | |
| async function* readChunks(streamInfo, size) { | |
| let read = 0n; | |
| while (read < size) { | |
| if (streamInfo.controller.signal.aborted) { | |
| throw new Error("controller aborted"); | |
| } | |
| const chunk = await request(streamInfo.url, { | |
| headers: { | |
| ...getHeaders('youtube'), | |
| Range: `bytes=${read}-${read + CHUNK_SIZE}` | |
| }, | |
| dispatcher: streamInfo.dispatcher, | |
| signal: streamInfo.controller.signal | |
| }); | |
| const expected = min(CHUNK_SIZE, size - read); | |
| const received = BigInt(chunk.headers['content-length']); | |
| if (received < expected / 2n) { | |
| closeRequest(streamInfo.controller); | |
| } | |
| for await (const data of chunk.body) { | |
| yield data; | |
| } | |
| read += received; | |
| } | |
| } | |
| async function handleYoutubeStream(streamInfo, res) { | |
| const { signal } = streamInfo.controller; | |
| const cleanup = () => (res.end(), closeRequest(streamInfo.controller)); | |
| try { | |
| const req = await fetch(streamInfo.url, { | |
| headers: getHeaders('youtube'), | |
| method: 'HEAD', | |
| dispatcher: streamInfo.dispatcher, | |
| signal | |
| }); | |
| streamInfo.url = req.url; | |
| const size = BigInt(req.headers.get('content-length')); | |
| if (req.status !== 200 || !size) { | |
| return cleanup(); | |
| } | |
| const generator = readChunks(streamInfo, size); | |
| const abortGenerator = () => { | |
| generator.return(); | |
| signal.removeEventListener('abort', abortGenerator); | |
| } | |
| signal.addEventListener('abort', abortGenerator); | |
| const stream = Readable.from(generator); | |
| for (const headerName of ['content-type', 'content-length']) { | |
| const headerValue = req.headers.get(headerName); | |
| if (headerValue) res.setHeader(headerName, headerValue); | |
| } | |
| pipe(stream, res, cleanup); | |
| } catch { | |
| cleanup(); | |
| } | |
| } | |
| async function handleGenericStream(streamInfo, res) { | |
| const { signal } = streamInfo.controller; | |
| const cleanup = () => res.end(); | |
| try { | |
| const req = await request(streamInfo.url, { | |
| headers: { | |
| ...Object.fromEntries(streamInfo.headers), | |
| host: undefined | |
| }, | |
| dispatcher: streamInfo.dispatcher, | |
| signal, | |
| maxRedirections: 16 | |
| }); | |
| res.status(req.statusCode); | |
| req.body.on('error', () => {}); | |
| for (const [ name, value ] of Object.entries(req.headers)) | |
| res.setHeader(name, value) | |
| if (req.statusCode < 200 || req.statusCode > 299) | |
| return cleanup(); | |
| if (isHlsRequest(req)) { | |
| await handleHlsPlaylist(streamInfo, req, res); | |
| } else { | |
| pipe(req.body, res, cleanup); | |
| } | |
| } catch { | |
| closeRequest(streamInfo.controller); | |
| cleanup(); | |
| } | |
| } | |
| export function internalStream(streamInfo, res) { | |
| if (streamInfo.service === 'youtube') { | |
| return handleYoutubeStream(streamInfo, res); | |
| } | |
| return handleGenericStream(streamInfo, res); | |
| } | |