Keep mobile controls visible and make recorded VOD remux seekable
This commit is contained in:
@ -217,3 +217,63 @@ test('legacy case-only accounts retain exact logins and reject ambiguous case va
|
||||
const resolved = await fixture.request('/api/auth/login', post({ username: 'TWIN', password: 'first-password' }));
|
||||
assert.equal(resolved.status, 200); assert.equal(resolved.body.user.username, 'Twin');
|
||||
});
|
||||
|
||||
test('recorded TS movies and series use copy sessions, preserve totals and seek on a provider without ranges',
|
||||
{ skip: process.env.NODECAST_BROWSER_TESTS === '1' ? false : 'Browser tests enabled in CI', timeout: 90000 }, async t => {
|
||||
const { chromium }=require('playwright');
|
||||
const fixture=await serverFixture(t);
|
||||
const binary=spawnSync('ffmpeg',['-version'],{stdio:'ignore'}).status===0?'ffmpeg':require('ffmpeg-static');
|
||||
const recorded=path.join(fixture.dir,'recorded.ts');
|
||||
const generated=spawnSync(binary,['-hide_banner','-loglevel','error','-y',
|
||||
'-f','lavfi','-i','color=c=red:s=160x90:r=10:d=12','-f','lavfi','-i','color=c=blue:s=160x90:r=10:d=12',
|
||||
'-f','lavfi','-i','sine=frequency=1000:sample_rate=48000:duration=24',
|
||||
'-filter_complex','[0:v][1:v]concat=n=2:v=1:a=0[v]','-map','[v]','-map','2:a',
|
||||
'-c:v','libx264','-threads','1','-preset','ultrafast','-g','10','-c:a','aac','-ac','2','-f','mpegts',recorded],{encoding:'utf8',timeout:15000});
|
||||
assert.equal(generated.status,0,generated.stderr);
|
||||
const media=fs.readFileSync(recorded),provider=http.createServer((req,res)=>{
|
||||
// This origin always ignores Range and sends the full recording progressively.
|
||||
res.writeHead(200,{'Content-Type':'video/mp2t'});res.write(media);res.end();
|
||||
});
|
||||
await new Promise(resolve=>provider.listen(0,'127.0.0.1',resolve));
|
||||
t.after(()=>new Promise(resolve=>{provider.close(resolve);provider.closeAllConnections();}));
|
||||
const source=`http://127.0.0.1:${provider.address().port}/recorded.ts`;
|
||||
const admin=await fixture.request('/api/auth/setup',post({username:'Owner',password:'owner-password'}));assert.equal(admin.status,201);
|
||||
const cookie=admin.cookie.split(';')[0],headers={Cookie:cookie};
|
||||
const browser=await chromium.launch({headless:true,executablePath:process.env.NODECAST_BROWSER_EXECUTABLE||undefined});t.after(()=>browser.close());
|
||||
const context=await browser.newContext();const split=cookie.indexOf('=');
|
||||
await context.addCookies([{name:cookie.slice(0,split),value:cookie.slice(split+1),url:fixture.base}]);
|
||||
const page=await context.newPage(),requests=[],errors=[];
|
||||
page.on('pageerror',error=>errors.push(error.message));
|
||||
page.on('request',request=>{if(request.url().includes('/api/transcode/session'))requests.push(JSON.parse(request.postData()));});
|
||||
await page.route('https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js',route=>route.fulfill({contentType:'application/javascript',body:fs.readFileSync(require.resolve('hls.js/dist/hls.min.js'))}));
|
||||
await page.route('https://fonts.**',route=>route.abort());
|
||||
await page.route('**/api/probe?**',async route=>{const response=await route.fetch();const info=await response.json();await route.fulfill({response,json:{...info,duration:0}});});
|
||||
await page.goto(fixture.base);await page.waitForFunction(()=>window.app?.pages?.watch);
|
||||
for(const [type,settings]of [['movie',{autoTranscode:true,forceRemux:false}],['series',{autoTranscode:false,forceRemux:true}]]){
|
||||
await fixture.request('/api/settings',{method:'PUT',headers,body:JSON.stringify(settings)});
|
||||
await page.evaluate(async({source,type})=>{
|
||||
const watch=window.app.pages.watch;window.app.navigateTo('watch');
|
||||
watch.content={type};watch.resumeTime=0;await watch.loadVideo(source);
|
||||
},{source,type});
|
||||
await page.waitForFunction(()=>window.app.pages.watch.currentSessionId&&window.app.pages.watch.video.readyState>=2&&window.app.pages.watch.sourceDuration>20);
|
||||
const original=await page.evaluate(()=>({session:window.app.pages.watch.currentSessionId,total:window.app.pages.watch.getPlaybackDuration()}));
|
||||
assert.ok(original.total>23&&original.total<26,JSON.stringify(original));
|
||||
assert.equal(requests.at(-1).videoMode,'copy');assert.equal(requests.at(-1).isLive,false);
|
||||
const pixel=()=>page.evaluate(()=>{const canvas=document.createElement('canvas');canvas.width=canvas.height=1;canvas.getContext('2d').drawImage(window.app.pages.watch.video,0,0,1,1);return Array.from(canvas.getContext('2d').getImageData(0,0,1,1).data);});
|
||||
let color=await pixel();assert.ok(color[0]>color[2]+100,JSON.stringify(color));
|
||||
await page.evaluate(()=>window.app.pages.watch.seekToContentTime(16));
|
||||
await page.waitForFunction(old=>window.app.pages.watch.currentSessionId!==old&&window.app.pages.watch.video.readyState>=2,original.session);
|
||||
color=await pixel();assert.ok(color[2]>color[0]+100,JSON.stringify(color));
|
||||
assert.equal(requests.at(-1).seekOffset,16);
|
||||
assert.equal(await page.evaluate(()=>window.app.pages.watch.getPlaybackDuration()),original.total);
|
||||
const forward=await page.evaluate(()=>window.app.pages.watch.currentSessionId);
|
||||
assert.equal((await fixture.request(`/api/transcode/${original.session}/stream.m3u8`,{headers})).status,404);
|
||||
await page.evaluate(()=>window.app.pages.watch.seekToContentTime(4));
|
||||
await page.waitForFunction(old=>window.app.pages.watch.currentSessionId!==old&&window.app.pages.watch.video.readyState>=2,forward);
|
||||
color=await pixel();assert.ok(color[0]>color[2]+100,JSON.stringify(color));
|
||||
assert.equal(requests.at(-1).seekOffset,4);
|
||||
assert.equal(await page.evaluate(()=>window.app.pages.watch.getPlaybackDuration()),original.total);
|
||||
await page.evaluate(()=>window.app.pages.watch.stop());
|
||||
}
|
||||
assert.deepEqual(errors,[]);await page.close();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user