Share the K1 live scene template and preserve idle media channels
This commit is contained in:
@@ -2,9 +2,11 @@ import assert from 'node:assert/strict';
|
||||
import {before,after,test} from 'node:test';
|
||||
import {execFileSync} from 'node:child_process';
|
||||
import {createServer} from 'vite';
|
||||
let server,previewFrames,assertRrd;
|
||||
let server,previewFrames,assertRrd,previewFreshness,previewCamera;
|
||||
before(async()=>{
|
||||
server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});
|
||||
({previewCamera}=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/previewCamera.ts'));
|
||||
({previewFreshness}=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/previewFreshness.ts'));
|
||||
({previewFrames,assertRrd}=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/previewFrames.ts'));
|
||||
});
|
||||
after(async()=>{await server?.close();});
|
||||
@@ -41,3 +43,38 @@ test('partial, oversized, unframed and invalid records never enter the decoder',
|
||||
receiver.push(header(12));assert.throws(()=>receiver.push(array(new Uint8Array(12))));
|
||||
assert.equal(calls,0);
|
||||
});
|
||||
|
||||
test('source age expires even when metadata repeats, old frames never look live',()=>{
|
||||
let now=10000;const gate=previewFreshness(()=>now);
|
||||
assert.equal(gate.fresh,false);
|
||||
gate.receive({type:'lidar-state',sequence:1,age_ms:250,points:3});
|
||||
assert.equal(gate.fresh,true);assert.equal(gate.points,3);
|
||||
now+=1800;gate.receive({type:'lidar-state',sequence:1,age_ms:0,points:3});
|
||||
assert.equal(gate.fresh,false);
|
||||
gate.receive({type:'lidar-state',sequence:2,age_ms:9000,points:3});
|
||||
assert.equal(gate.fresh,false);
|
||||
gate.receive({type:'lidar-state',sequence:3,age_ms:0,points:4});
|
||||
assert.equal(gate.fresh,true);assert.equal(gate.points,4);
|
||||
assert.throws(()=>gate.receive({type:'lidar-state',sequence:4,age_ms:-1,points:4}));
|
||||
});
|
||||
|
||||
test('onboard camera admits immediate sourceopen and ignores callbacks after close',()=>{
|
||||
const saved={media:globalThis.MediaSource,create:URL.createObjectURL,revoke:URL.revokeObjectURL};
|
||||
let source,buffer,appended=0,ready=0,failed=0;
|
||||
class Buffer extends EventTarget {updating=false;buffered={length:0};appendBuffer(){appended++;}}
|
||||
class Media extends EventTarget {
|
||||
readyState='open';static isTypeSupported(){return true;}
|
||||
constructor(){super();source=this;}
|
||||
addSourceBuffer(){buffer=new Buffer();return buffer;}
|
||||
}
|
||||
class Video extends EventTarget {currentTime=0;set src(_){source.dispatchEvent(new Event('sourceopen'));}pause(){}load(){}removeAttribute(){}play(){return Promise.resolve();}}
|
||||
globalThis.MediaSource=Media;URL.createObjectURL=()=> 'blob:synthetic';URL.revokeObjectURL=()=>{};
|
||||
try{
|
||||
const video=new Video(),decoder=previewCamera(video,()=>ready++,()=>failed++);
|
||||
decoder.open('video/mp4');decoder.push(new Uint8Array([1,2,3]));
|
||||
assert.equal(appended,1);
|
||||
video.dispatchEvent(new Event('loadeddata'));assert.equal(ready,1);
|
||||
decoder.close();video.dispatchEvent(new Event('loadeddata'));buffer.dispatchEvent(new Event('error'));
|
||||
assert.equal(ready,1);assert.equal(failed,0);
|
||||
}finally{globalThis.MediaSource=saved.media;URL.createObjectURL=saved.create;URL.revokeObjectURL=saved.revoke;}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user