view mrjunejune/test/hls_player_test.js @ 270:358dd5950985

sync production config on every deploy Always install the ignored repository .config into /etc/mrjunejune before promoting the new bundle. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 13:16:47 -0700
parents 543df0fe7168
children
line wrap: on
line source

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;
});