From f6460d50024d06e449db37d5267d6b9d0c01beb4 Mon Sep 17 00:00:00 2001 From: mikhail Date: Thu, 30 May 2024 17:14:28 +0500 Subject: [PATCH] wip: master playlist support for internal hls streams for some reason vimeo still doesn't work --- src/modules/stream/internal-hls.js | 57 ++++++++++++++++++++++++++++++ src/modules/stream/internal.js | 33 ++--------------- 2 files changed, 59 insertions(+), 31 deletions(-) create mode 100644 src/modules/stream/internal-hls.js diff --git a/src/modules/stream/internal-hls.js b/src/modules/stream/internal-hls.js new file mode 100644 index 00000000..3ab6da06 --- /dev/null +++ b/src/modules/stream/internal-hls.js @@ -0,0 +1,57 @@ +import { createInternalStream } from './manage.js'; +import HLS from 'hls-parser'; +import path from "node:path"; + +function transformObject(streamInfo, hlsObject) { + if (hlsObject === undefined) { + return (hlsObject) => transformObject(streamInfo, hlsObject); + } + + const fullUrl = hlsObject.uri.startsWith("/") + ? new URL(url, base).toString() + : new URL(path.join(streamInfo.url, "/../", hlsObject.uri)).toString(); + hlsObject.uri = createInternalStream(fullUrl, streamInfo); + + return hlsObject; +} + +function transformMasterPlaylist(streamInfo, hlsPlaylist) { + const makeInternalStream = transformObject(streamInfo); + + const makeInternalVariants = (variant) => { + variant = transformObject(streamInfo, variant); + variant.video = variant.video.map(makeInternalStream); + variant.audio = variant.audio.map(makeInternalStream); + return variant; + }; + hlsPlaylist.variants = hlsPlaylist.variants.map(makeInternalVariants); + + return hlsPlaylist; +} + +function transformMediaPlaylist(streamInfo, hlsPlaylist) { + const makeInternalSegments = transformObject(streamInfo); + hlsPlaylist.segments = hlsPlaylist.segments.map(makeInternalSegments); + hlsPlaylist.prefetchSegments = hlsPlaylist.prefetchSegments.map(makeInternalSegments); + return hlsPlaylist; +} + +const HLS_MIME_TYPES = ["application/vnd.apple.mpegurl", "audio/mpegurl", "application/x-mpegURL"]; + +export function isHLSRequest (req) { + return HLS_MIME_TYPES.includes(req.headers['content-type']); +} + +export async function handleHlsPlaylist(streamInfo, req, res) { + let hlsPlaylist = await req.body.text(); + hlsPlaylist = HLS.parse(hlsPlaylist); + + hlsPlaylist = hlsPlaylist.isMasterPlaylist + ? transformMasterPlaylist(streamInfo, hlsPlaylist) + : transformMediaPlaylist(streamInfo, hlsPlaylist); + + hlsPlaylist = HLS.stringify(hlsPlaylist); + + res.write(hlsPlaylist); + res.end(); +} diff --git a/src/modules/stream/internal.js b/src/modules/stream/internal.js index 00eec9cb..42c3137b 100644 --- a/src/modules/stream/internal.js +++ b/src/modules/stream/internal.js @@ -1,9 +1,8 @@ import { request } from 'undici'; import { Readable } from 'node:stream'; import { assert } from 'console'; -import { createInternalStream } from './manage.js'; import { getHeaders } from './shared.js'; -import HLS from 'hls-parser'; +import { handleHlsPlaylist, isHLSRequest } from './internal-hls.js'; const CHUNK_SIZE = BigInt(8e6); // 8 MB const min = (a, b) => a < b ? a : b; @@ -75,34 +74,6 @@ async function handleYoutubeStream(streamInfo, res) { } } -function transformHlsMediaPlaylist(streamInfo, hlsPlaylist) { - const makeInternalSegments = (segment) => { - const fullUri = new URL(segment.uri, streamInfo.url).toString(); - segment.uri = createInternalStream(fullUri, streamInfo); - - return segment; - } - - hlsPlaylist.segments = hlsPlaylist.segments.map(makeInternalSegments); - hlsPlaylist.prefetchSegments = hlsPlaylist.prefetchSegments.map(makeInternalSegments); - - return hlsPlaylist; -} - -async function handleHlsPlaylist(streamInfo, req, res) { - let hlsPlaylist = await req.body.text(); - hlsPlaylist = HLS.parse(hlsPlaylist); - - // NOTE no processing module is passing the master playlist - assert(!hlsPlaylist.isMasterPlaylist); - - hlsPlaylist = transformHlsMediaPlaylist(streamInfo, hlsPlaylist); - hlsPlaylist = HLS.stringify(hlsPlaylist); - - res.write(hlsPlaylist); - res.end(); -} - const HLS_MIME_TYPES = ["application/vnd.apple.mpegurl", "audio/mpegurl", "application/x-mpegURL"]; export async function internalStream(streamInfo, res) { @@ -129,7 +100,7 @@ export async function internalStream(streamInfo, res) { if (req.statusCode < 200 || req.statusCode > 299) return res.end(); - if (HLS_MIME_TYPES.includes(req.headers['content-type'])) { + if (isHLSRequest(req)) { await handleHlsPlaylist(streamInfo, req, res); } else { req.body.pipe(res);