Files
ucvl-home-vision/test_live_player.js
T

92 lines
4.3 KiB
JavaScript
Raw Normal View History

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const LivePlayer = require('./web/live-player.js');
function fixture() {
let now = 100, serial = 0;
const timeouts = new Map(), intervals = new Map();
const clock = {
setTimeout(fn, ms) {const id=++serial;timeouts.set(id,{fn,ms});return id;},
clearTimeout(id) {timeouts.delete(id);},
setInterval(fn, ms) {const id=++serial;intervals.set(id,{fn,ms});return id;},
clearInterval(id) {intervals.delete(id);}
};
const listeners = new Map(), indicators = {'.video-empty':{hidden:false},'.video-error':{hidden:true}};
const video = {
currentTime:0,readyState:0,videoWidth:0,paused:false,ended:false,error:null,
addEventListener(e,f) {listeners.set(e,f);},removeEventListener(e) {listeners.delete(e);},
emit(e) {listeners.get(e)?.();},
play() {this.paused=false;this.emit('play');return Promise.resolve();},
pause() {this.paused=true;this.emit('pause');},removeAttribute() {},load() {},
canPlayType() {return '';}
};
const instances=[];
class Hls {
static Events={ERROR:'error',MANIFEST_PARSED:'manifest'};
static isSupported() {return true;}
constructor() {this.events={};instances.push(this);}
on(e,f) {this.events[e]=f;}
loadSource() {} attachMedia() {} destroy() {this.destroyed=true;}
fail() {this.events.error(null,{fatal:true});}
}
const player=new LivePlayer(video,{querySelector:s=>indicators[s]},'/example',{Hls,clock,now:()=>now});
return {player,video,instances,timeouts,intervals,indicators,
advance(ms) {now+=ms;player.watch();},
playing() {video.readyState=4;video.videoWidth=1920;video.emit('playing');},
retry() {const [id,t]=timeouts.entries().next().value;timeouts.delete(id);now+=t.ms;t.fn();}
};
}
test('readiness, recording status and labels do not change playback identity',()=>{
const camera={id:'one',enabled:true,ready:true,name:'A',recordingActive:false};
const original=LivePlayer.key([camera],'hd');
assert.equal(LivePlayer.key([{...camera,ready:false,name:'B',recordingActive:true}],'hd'),original);
assert.notEqual(LivePlayer.key([camera],'compat'),original);
});
test('fatal error reconnects only its own player with bounded backoff',()=>{
const a=fixture(),b=fixture();
for(let i=0;i<8;i++) {
a.instances.at(-1).fail();
assert.equal(a.timeouts.size,1);
assert.ok([...a.timeouts.values()][0].ms<=30000);
a.retry();
}
assert.equal(b.instances.length,1);
a.player.destroy();b.player.destroy();
});
test('duplicate errors schedule only one retry',()=>{
const f=fixture();f.instances[0].fail();f.video.emit('error');
assert.equal(f.timeouts.size,1);f.player.destroy();
});
test('continuous playback does not restart during repeated health checks',()=>{
const f=fixture();f.playing();
for(let i=0;i<72;i++) {f.video.currentTime+=5;f.advance(5000);}
assert.equal(f.instances.length,1);assert.equal(f.timeouts.size,0);f.player.destroy();
});
test('stalled playback automatically reconnects',()=>{
const f=fixture();f.playing();f.advance(30000);
assert.equal(f.timeouts.size,1);f.retry();assert.equal(f.instances.length,2);f.player.destroy();
});
test('startup has enough time to wait for a source',()=>{
const f=fixture();f.advance(60000);assert.equal(f.timeouts.size,0);
f.advance(6000);assert.equal(f.timeouts.size,1);f.player.destroy();
});
test('intentional pause is preserved and resume can recover a failed connection',()=>{
const f=fixture();f.playing();f.video.pause();f.instances[0].fail();f.advance(90000);
assert.equal(f.instances.length,1);assert.equal(f.timeouts.size,0);
f.video.play();assert.equal(f.instances.length,2);f.player.destroy();
});
test('destroy cancels timers and releases the old player',()=>{
const f=fixture();f.instances[0].fail();f.player.destroy();
assert.equal(f.timeouts.size,0);assert.equal(f.intervals.size,0);assert.equal(f.instances[0].destroyed,true);
});
test('recovery before timeout cancels pending retry',()=>{
const f=fixture();f.instances[0].fail();f.playing();
assert.equal(f.timeouts.size,0);assert.equal(f.indicators['.video-error'].hidden,true);f.player.destroy();
});
test('error from an old HLS instance cannot restart its replacement',()=>{
const f=fixture();const old=f.instances[0];old.fail();f.retry();old.fail();
assert.equal(f.timeouts.size,0);assert.equal(f.instances.length,2);f.player.destroy();
});