diff mrjunejune/test/hls_player_test.js @ 242:543df0fe7168

[tools] Add full HLS player support
author MrJuneJune <me@mrjunejune.com>
date Mon, 03 Aug 2026 13:14:41 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/hls_player_test.js	Mon Aug 03 13:14:41 2026 -0700
@@ -0,0 +1,86 @@
+const assert = require('node:assert/strict');
+const {
+  chooseVariant,
+  parseAttributeList,
+  parseMasterPlaylist,
+  parseMediaPlaylist,
+  normalizePlaylistUrl,
+  localRelativePath,
+  rewritePlaylistUris,
+} = require('../src/tools/hls_player/hls-player.js');
+
+const attributes = parseAttributeList(
+  'BANDWIDTH=500000,RESOLUTION=640x360,CODECS="avc1.42c01e,mp4a.40.2"',
+);
+assert.equal(attributes.BANDWIDTH, '500000');
+assert.equal(attributes.RESOLUTION, '640x360');
+assert.equal(attributes.CODECS, 'avc1.42c01e,mp4a.40.2');
+
+const variants = parseMasterPlaylist(
+  '#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=100\nlow.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1.42c01e"\nhigh.m3u8\n',
+  'https://example.com/master.m3u8',
+);
+assert.equal(variants.length, 2);
+assert.equal(chooseVariant(variants).url, 'https://example.com/high.m3u8');
+assert.equal(
+  chooseVariant(variants, variant => variant.bandwidth < 200).url,
+  'https://example.com/low.m3u8',
+);
+assert.equal(chooseVariant(variants, () => false), null);
+
+const media = parseMediaPlaylist(
+  '#EXTM3U\n#EXT-X-MAP:URI="init.mp4"\n#EXTINF:1.5,\nseg0.m4s\n#EXTINF:2.0,\nseg1.m4s\n#EXT-X-ENDLIST\n',
+  'https://example.com/path/stream.m3u8',
+);
+assert.equal(media.initSegment, 'https://example.com/path/init.mp4');
+assert.equal(media.segments.length, 2);
+assert.equal(media.segments[1].url, 'https://example.com/path/seg1.m4s');
+assert.equal(media.totalDuration, 3.5);
+assert.equal(media.endList, true);
+
+assert.equal(
+  normalizePlaylistUrl('/stream.m3u8', 'https://example.com/tool'),
+  'https://example.com/stream.m3u8',
+);
+assert.throws(
+  () => normalizePlaylistUrl('file:///tmp/stream.m3u8', 'https://example.com'),
+  /HTTP, HTTPS, or a local file/,
+);
+assert.equal(
+  localRelativePath('folder/master.m3u8', 'video/stream.m3u8'),
+  'folder/video/stream.m3u8',
+);
+assert.equal(
+  localRelativePath('folder/video/stream.m3u8', '../init.mp4'),
+  'folder/init.mp4',
+);
+assert.equal(
+  localRelativePath('folder/video/stream.m3u8', '/init.mp4'),
+  'init.mp4',
+);
+assert.throws(
+  () => localRelativePath('master.m3u8', '../outside.m4s'),
+  /escapes/,
+);
+
+assert.throws(
+  () => parseMediaPlaylist(
+    '#EXTM3U\n#EXT-X-KEY:METHOD=AES-128,URI="key"\n#EXTINF:1,\nseg.m4s\n',
+    'https://example.com/stream.m3u8',
+  ),
+  /Encrypted HLS/,
+);
+
+(async () => {
+  const rewritten = await rewritePlaylistUris(
+    '#EXTM3U\n#EXT-X-MEDIA:TYPE=AUDIO,URI="audio.m3u8"\n#EXT-X-MAP:URI="init.mp4"\n#EXT-X-KEY:METHOD=AES-128,URI="key.bin"\n#EXTINF:1,\nseg.m4s\n',
+    async reference => `blob:${reference}`,
+  );
+  assert.match(rewritten, /URI="blob:audio\.m3u8"/);
+  assert.match(rewritten, /URI="blob:init\.mp4"/);
+  assert.match(rewritten, /URI="blob:key\.bin"/);
+  assert.match(rewritten, /blob:seg\.m4s/);
+})().catch(error => {
+  console.error(error);
+  process.exitCode = 1;
+});