improvement: HLS support for internal streams

This commit is contained in:
mikhail 2024-05-26 23:41:56 +05:00
parent abd9f2eb87
commit 9d457d8022
3 changed files with 40 additions and 10 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "cobalt", "name": "cobalt",
"version": "7.14.1", "version": "7.14.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cobalt", "name": "cobalt",
"version": "7.14.1", "version": "7.14.2",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"content-disposition-header": "0.6.0", "content-disposition-header": "0.6.0",

View File

@ -1,7 +1,9 @@
import { request } from 'undici'; import { request } from 'undici';
import { Readable } from 'node:stream'; import { Readable } from 'node:stream';
import { assert } from 'console'; import { assert } from 'console';
import { createInternalStream } from './manage.js';
import { getHeaders } from './shared.js'; import { getHeaders } from './shared.js';
import HLS from 'hls-parser';
const CHUNK_SIZE = BigInt(8e6); // 8 MB const CHUNK_SIZE = BigInt(8e6); // 8 MB
const min = (a, b) => a < b ? a : b; const min = (a, b) => a < b ? a : b;
@ -73,6 +75,36 @@ async function handleYoutubeStream(streamInfo, res) {
} }
} }
function transformHLSMediaPlaylist(streamInfo, hlsPlaylist) {
function generateInternalStreamsOfSegments(segment) {
const fullUri = new URL(segment.uri, streamInfo.url).toString();
segment.uri = createInternalStream(fullUri, streamInfo);
return segment;
}
hlsPlaylist.segments =
hlsPlaylist.segments.map(generateInternalStreamsOfSegments);
hlsPlaylist.prefetchSegments =
hlsPlaylist.prefetchSegments.map(generateInternalStreamsOfSegments);
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();
}
export async function internalStream(streamInfo, res) { export async function internalStream(streamInfo, res) {
if (streamInfo.service === 'youtube') { if (streamInfo.service === 'youtube') {
return handleYoutubeStream(streamInfo, res); return handleYoutubeStream(streamInfo, res);
@ -97,8 +129,12 @@ export async function internalStream(streamInfo, res) {
if (req.statusCode < 200 || req.statusCode > 299) if (req.statusCode < 200 || req.statusCode > 299)
return res.end(); return res.end();
if (["application/vnd.apple.mpegurl", "audio/mpegurl"].includes(req.headers['content-type'])) {
await handleHLSPlaylist(streamInfo, req, res);
} else {
req.body.pipe(res); req.body.pipe(res);
req.body.on('error', () => res.end()); req.body.on('error', () => res.end());
}
} catch { } catch {
streamInfo.controller.abort(); streamInfo.controller.abort();
} }

View File

@ -108,12 +108,6 @@ export function destroyInternalStream(url) {
} }
function wrapStream(streamInfo) { function wrapStream(streamInfo) {
/* m3u8 links are currently not supported
* for internal streams, skip them */
if (M3U_SERVICES.includes(streamInfo.service)) {
return streamInfo;
}
const url = streamInfo.urls; const url = streamInfo.urls;
if (typeof url === 'string') { if (typeof url === 'string') {