87 lines
3.5 KiB
JavaScript
87 lines
3.5 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const {test} = require('node:test');
|
|
const createLoader = require('./player-media.js');
|
|
|
|
function fixture() {
|
|
class Video extends EventTarget {
|
|
src = '';
|
|
error = null;
|
|
canPlayType() {return 'probably';}
|
|
load() {}
|
|
}
|
|
const instances = [], failures = [];
|
|
class Hls {
|
|
static Events = {ERROR:'error',MEDIA_ATTACHED:'attached'};
|
|
static ErrorTypes = {NETWORK_ERROR:'network'};
|
|
static isSupported() {return true;}
|
|
handlers = new Map();
|
|
destroyed = 0;
|
|
constructor(options) {this.options=options;instances.push(this);}
|
|
on(type,handler) {this.handlers.set(type,handler);}
|
|
attachMedia(video) {this.video=video;}
|
|
loadSource(url) {this.url=url;}
|
|
destroy() {this.destroyed++;}
|
|
emit(type,data) {this.handlers.get(type)?.(type,data);}
|
|
}
|
|
const video=new Video();
|
|
return {Hls,video,instances,failures,loader:createLoader({Hls},video,error=>failures.push(error))};
|
|
}
|
|
|
|
test('changing the media cancels a pending load and ignores the old HLS callbacks', async () => {
|
|
const f=fixture(), signal=new AbortController().signal;
|
|
const first=f.loader.load({player:'hls',url:'/old.m3u8'},false,signal);
|
|
const canceled=assert.rejects(first,{name:'AbortError'});
|
|
const old=f.instances[0];
|
|
const next=f.loader.load({player:'hls',url:'/new.m3u8'},false,signal,7.25);
|
|
old.emit('error',{fatal:true,type:'network'});
|
|
old.emit('attached');
|
|
const current=f.instances[1];
|
|
current.emit('attached');
|
|
assert.equal(current.url,'/new.m3u8');
|
|
assert.equal(current.options.startPosition,7.25);
|
|
assert.equal(old.url,undefined);
|
|
f.video.dispatchEvent(new Event('loadedmetadata'));
|
|
await Promise.all([next,canceled]);
|
|
assert.equal(old.destroyed,1);
|
|
assert.deepEqual(f.failures,[]);
|
|
f.loader.destroy();
|
|
});
|
|
|
|
test('synchronous HLS initialization failures clean up and a later load can succeed', async () => {
|
|
const f=fixture(), signal=new AbortController().signal;
|
|
f.Hls.prototype.attachMedia=function(){throw new Error('fixture attach failure');};
|
|
await assert.rejects(f.loader.load({player:'hls',url:'/failed.m3u8'},false,signal),error=>error.playbackDecode===true);
|
|
assert.equal(f.instances[0].destroyed,1);
|
|
const next=f.loader.load({player:'mp4',url:'/ready.mp4',mime:'video/mp4'},false,signal);
|
|
f.video.dispatchEvent(new Event('loadedmetadata'));
|
|
await next;
|
|
assert.equal(f.video.src,'/ready.mp4');
|
|
f.loader.destroy();
|
|
});
|
|
|
|
test('aborting an in-flight load destroys its HLS session without reporting a playback failure', async () => {
|
|
const f=fixture(), abort=new AbortController();
|
|
const pending=f.loader.load({player:'hls',url:'/canceled.m3u8'},false,abort.signal);
|
|
const rejected=assert.rejects(pending,{name:'AbortError'});
|
|
abort.abort();
|
|
await rejected;
|
|
const old=f.instances[0];
|
|
old.emit('error',{fatal:true,type:'network'});
|
|
assert.equal(old.destroyed,1);
|
|
assert.deepEqual(f.failures,[]);
|
|
f.loader.destroy();
|
|
});
|
|
|
|
test('fatal HLS errors after metadata retain the network versus decode distinction', async () => {
|
|
const f=fixture();
|
|
const pending=f.loader.load({player:'hls',url:'/ready.m3u8'},false,new AbortController().signal);
|
|
f.video.dispatchEvent(new Event('loadedmetadata'));
|
|
await pending;
|
|
f.instances[0].emit('error',{fatal:false,type:'network'});
|
|
assert.deepEqual(f.failures,[]);
|
|
f.instances[0].emit('error',{fatal:true,type:'network'});
|
|
assert.equal(f.failures[0].playbackNetwork,true);
|
|
assert.equal(f.failures[0].playbackDecode,false);
|
|
f.loader.destroy();
|
|
});
|