57 lines
3.1 KiB
JavaScript
57 lines
3.1 KiB
JavaScript
import {test} from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {readFile} from 'node:fs/promises';
|
|
import vm from 'node:vm';
|
|
|
|
const fullBuild=await readFile(new URL('../node_modules/hls.js/dist/hls.js',import.meta.url),'utf8');
|
|
const variants=[
|
|
{codec:'avc1.42E01E',path:'h264.m3u8'},
|
|
{codec:'hvc1.1.6.L93.B0',path:'h265-hvc1.m3u8'},
|
|
{codec:'hev1.1.6.L93.B0',path:'h265-hev1.m3u8'},
|
|
];
|
|
|
|
// Run the real bundled hls.js parser/controller with deterministic platform capabilities.
|
|
// The loader only returns this local manifest; no network or browser session is used.
|
|
async function parseManifest(supported,items=variants){
|
|
const manifest='#EXTM3U\n'+items.map((item,index)=>`#EXT-X-STREAM-INF:BANDWIDTH=${(index+1)*500000},CODECS="${item.codec},mp4a.40.2"\n${item.path}\n`).join('');
|
|
const sandbox={module:{exports:{}},exports:{},navigator:{userAgent:'Chrome/130'},performance,URL,URLSearchParams,TextDecoder,TextEncoder,setTimeout,clearTimeout,setInterval,clearInterval,queueMicrotask};
|
|
sandbox.self=sandbox;sandbox.window=sandbox;sandbox.location={href:'https://example.test/'};
|
|
sandbox.MediaSource=class {
|
|
static isTypeSupported(type){
|
|
const codecs=type.match(/codecs=["']?([^"']+)/)?.[1]?.split(',')||[];
|
|
return codecs.length>0&&codecs.every(codec=>codec.startsWith('mp4a')||supported.some(prefix=>codec.startsWith(prefix)));
|
|
}
|
|
};
|
|
vm.runInNewContext(fullBuild,sandbox,{timeout:1000});
|
|
const Hls=sandbox.module.exports;
|
|
class ManifestLoader {
|
|
constructor(){this.stats={aborted:false,loaded:manifest.length,total:manifest.length,retry:0,chunkCount:1,loading:{start:1,first:2,end:3},parsing:{start:0,end:0},buffering:{start:0,first:0,end:0}};}
|
|
load(context,_config,callbacks){
|
|
assert.equal(context.url,'https://example.test/master.m3u8');
|
|
this.context=context;
|
|
queueMicrotask(()=>callbacks.onSuccess({url:context.url,data:manifest},this.stats,context,null));
|
|
}
|
|
abort(){}destroy(){}getCacheAge(){return 0;}
|
|
}
|
|
const hls=new Hls({autoStartLoad:false,enableWorker:false,pLoader:ManifestLoader});
|
|
const done=new Promise(resolve=>{
|
|
hls.on(Hls.Events.MANIFEST_PARSED,(_event,data)=>resolve({codecs:Array.from(data.levels,level=>level.videoCodec)}));
|
|
hls.on(Hls.Events.ERROR,(_event,data)=>{if(data.fatal)resolve({error:data.details});});
|
|
});
|
|
hls.loadSource('https://example.test/master.m3u8');
|
|
try{return await done;}finally{hls.destroy();}
|
|
}
|
|
|
|
test('real HLS manifest filtering retains H.264 when a device cannot decode HEVC',{timeout:2000},async()=>{
|
|
assert.deepEqual(await parseManifest(['avc1']),{codecs:['avc1.42E01E']});
|
|
});
|
|
|
|
test('real HLS manifest filtering retains AVC, hvc1 and hev1 when all are supported',{timeout:2000},async()=>{
|
|
assert.deepEqual(await parseManifest(['avc1','hvc1','hev1']),{codecs:variants.map(item=>item.codec)});
|
|
});
|
|
|
|
test('HEVC-only playlists are accepted on capable devices and report incompatibility elsewhere',{timeout:2000},async()=>{
|
|
assert.deepEqual(await parseManifest(['hvc1'],[variants[1]]),{codecs:['hvc1.1.6.L93.B0']});
|
|
assert.deepEqual(await parseManifest(['avc1'],[variants[1]]),{error:'manifestIncompatibleCodecsError'});
|
|
});
|