#!/usr/bin/env node /* Format selection, tested without a browser. * * node tests/unit_formats.js * * Picking a rendition is pure: a list of formats in, one URL out. That makes it * the one part of playback that can be checked in a second, with no server, no * Chromium and no network -- which matters, because the height cap a split * reels panel applies is the difference between decoding four 1080p streams and * four 480p ones. */ const fs = require('fs'); const path = require('path'); const vm = require('vm'); // Enough of a browser for videos.js to finish loading. It builds a few // IntersectionObservers and reads matchMedia at module scope; nothing below // touches the DOM. const noop = () => {}; const element = () => ({ style: { setProperty: noop, removeProperty: noop }, classList: { add: noop, remove: noop, toggle: noop, contains: () => false }, dataset: {}, querySelector: () => null, querySelectorAll: () => [], addEventListener: noop, removeEventListener: noop, appendChild: noop, removeChild: noop, remove: noop, getBoundingClientRect: () => ({ width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0 }), setAttribute: noop, removeAttribute: noop, getAttribute: () => null, cloneNode: element, content: { firstElementChild: { cloneNode: element } }, children: [], childElementCount: 0, }); const sandbox = { console, setTimeout, clearTimeout, URL, Image: function () { return element(); }, IntersectionObserver: function () { return { observe: noop, unobserve: noop, disconnect: noop }; }, requestAnimationFrame: noop, requestIdleCallback: noop, performance: { now: () => 0 }, localStorage: { getItem: () => null, setItem: noop, removeItem: noop }, }; sandbox.addEventListener = noop; sandbox.removeEventListener = noop; sandbox.window = sandbox; sandbox.self = sandbox; sandbox.globalThis = sandbox; sandbox.document = { getElementById: () => null, createElement: element, querySelectorAll: () => [], addEventListener: noop, documentElement: element(), body: element(), head: element(), }; sandbox.window.matchMedia = () => ({ matches: false, addEventListener: noop }); sandbox.window.location = { href: 'http://localhost/' }; const context = vm.createContext(sandbox); const load = (file) => vm.runInContext( fs.readFileSync(path.join(__dirname, '..', 'frontend', 'js', file), 'utf8'), context, file); // videos.js reaches for these siblings when a card is built; none of the // functions under test do. sandbox.App = { state: {}, constants: {}, favorites: { getKey: () => null, has: () => false, setButtonState: noop }, storage: { getPreferredQuality: () => 'auto' } }; load('videos.js'); const { rankFormats, resolveStreamSource, resolveStreamSources } = sandbox.App.videos; let failed = 0; const ok = (label, cond, detail) => { if (!cond) failed++; console.log(` [${cond ? 'PASS' : 'FAIL'}] ${label}` + (!cond && detail ? ` -- ${detail}` : '')); }; const formats = [ { url: 'u240', height: 240, vcodec: 'avc1' }, { url: 'u480', height: 480, vcodec: 'avc1' }, { url: 'u720', height: 720, vcodec: 'avc1' }, { url: 'u1080', height: 1080, vcodec: 'avc1' }, ]; const video = { id: 'v1', url: 'https://example.com/watch', meta: { formats: formats } }; const heightOf = (src) => (formats.find((f) => f.url === src.url) || {}).height; console.log('\nranking'); ok('no ceiling takes the best', rankFormats(formats, null)[0].height === 1080); ok('a ceiling takes the best at or below it', rankFormats(formats, 720)[0].height === 720); ok('an exact ceiling is allowed', rankFormats(formats, 480)[0].height === 480); ok('below every rendition still returns one', rankFormats(formats, 100)[0].height === 240, String(rankFormats(formats, 100)[0].height)); ok('everything stays reachable as fallback', rankFormats(formats, 480).length === formats.length); console.log('\nthe cap a split panel applies'); sandbox.App.storage.getPreferredQuality = () => 'auto'; ok('uncapped panel gets the best', heightOf(resolveStreamSource(video)) === 1080); ok('a quarter-screen panel gets a quarter-screen rendition', heightOf(resolveStreamSource(video, { maxHeight: 480 })) === 480); ok('the cap survives into the fallback order', heightOf(resolveStreamSources(video, { maxHeight: 480 })[0]) === 480); console.log('\nthe cap and the quality preference are both ceilings'); sandbox.App.storage.getPreferredQuality = () => '720'; ok('preference alone caps at 720', heightOf(resolveStreamSource(video)) === 720); ok('the tighter of the two wins (panel)', heightOf(resolveStreamSource(video, { maxHeight: 480 })) === 480); sandbox.App.storage.getPreferredQuality = () => '480'; ok('the tighter of the two wins (preference)', heightOf(resolveStreamSource(video, { maxHeight: 720 })) === 480); ok('a cap never raises the preference', heightOf(resolveStreamSource(video, { maxHeight: 2160 })) === 480); console.log('\nranking by what it costs to decode'); // Same picture, four ways of arriving at it. const mixed = [ { url: 'hls720', height: 720, vcodec: 'avc1', protocol: 'm3u8_native', fps: 30 }, { url: 'av1720', height: 720, vcodec: 'av01.0.05M.08', protocol: 'https', ext: 'mp4', fps: 30 }, { url: 'avc720', height: 720, vcodec: 'avc1', protocol: 'https', ext: 'mp4', fps: 30, tbr: 900 }, { url: 'avc720p60', height: 720, vcodec: 'avc1', protocol: 'https', ext: 'mp4', fps: 60, tbr: 2000 }, ]; const mixedVideo = { id: 'v2', url: 'https://example.com/w2', meta: { formats: mixed } }; sandbox.App.storage.getPreferredQuality = () => 'auto'; const best = (opts) => resolveStreamSource(mixedVideo, opts).url; ok('without the flag, bitrate still wins', best({}) === 'avc720p60', best({})); ok('cheapest avoids HLS demuxing in JS', best({ cheapest: true }) !== 'hls720'); ok('cheapest avoids software-decoded AV1', best({ cheapest: true }) !== 'av1720'); ok('cheapest avoids 60fps', best({ cheapest: true }) !== 'avc720p60'); ok('cheapest picks progressive H.264 at 30fps', best({ cheapest: true }) === 'avc720', best({ cheapest: true })); // Cheapness must not override the size ceiling, or a split panel would get a // bigger picture than it can afford just because it is cheap per pixel. const tall = [ { url: 'hls480', height: 480, vcodec: 'avc1', protocol: 'm3u8_native' }, { url: 'mp4_1080', height: 1080, vcodec: 'avc1', protocol: 'https', ext: 'mp4' }, ]; const tallVideo = { id: 'v3', url: 'https://example.com/w3', meta: { formats: tall } }; ok('the height ceiling still comes first', resolveStreamSource(tallVideo, { maxHeight: 480, cheapest: true }).url === 'hls480'); ok('every format stays reachable as fallback', rankFormats(mixed, null, { cheapest: true }).length === mixed.length); console.log(`\n${failed ? 'FAILED' : 'OK'}: ${failed} check(s) failed`); process.exit(failed ? 1 : 0);