// Coordinated production trial. No tuning, restart, recording or configuration writes. // Invoke only AFTER the receiver task explicitly announces its test window. const fs = require('node:fs'); const { execFileSync } = require('node:child_process'); const { performance } = require('node:perf_hooks'); const { chromium } = require('C:/Users/michal/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright'); const option = (name, fallback) => process.argv.find(a => a.startsWith('--' + name + '='))?.split('=').slice(1).join('=') ?? fallback; const duration = Number(option('duration', '900')), joinAt = Number(option('join', '300')), leaveAt = Number(option('leave', '600')); const channel = option('channel', 'TVP 1 HD'); const reportPath = option('report', 'reception-trial.json'); const bridgeFile = option('bridge-file', null); const frameFile = option('frame', null); if (!process.argv.includes('--coordinated') || !Number.isFinite(duration) || !Number.isFinite(joinAt) || !Number.isFinite(leaveAt) || !(0 < joinAt && joinAt < leaveAt && leaveAt < duration)) { console.error('Requires --coordinated after explicit start signal, and 0 < join < leave < duration (seconds).'); process.exit(2); } const origin = 'http://192.168.100.191:8096', web = origin + '/web/index.html'; const safe = value => String(value).replace(/(api_key|access_token|Token|password)([=:\s]+)[^\s,"&]+/gi, '$1$2').replace(/(https?:\/\/[^\s?"']+)\?[^\s"']*/g, '$1?').slice(0, 500); const report = { started_utc: new Date().toISOString(), channel, relay_id: null, isolated_bridge_override: Boolean(bridgeFile), schedule_seconds: { duration, join: joinAt, leave: leaveAt }, checks: [], events: [], snapshots: [], audio_scope: 'Decoded byte counter when available; no human audible verification.' }; fs.writeFileSync(reportPath + '.jsonl', ''); const record = (type, data) => { const row = { utc: new Date().toISOString(), type, ...data }; fs.appendFileSync(reportPath + '.jsonl', JSON.stringify(row) + '\n'); return row; }; const check = (name, ok, evidence) => { const row = { name, status: ok ? 'PASS' : 'FAIL', evidence }; report.checks.push(row); console.log(name + ': ' + row.status); record('check', row); }; const admin = `import json,sqlite3,urllib.request,pathlib,secrets\nc=sqlite3.connect('file:/var/lib/jellyfin/data/jellyfin.db?mode=ro',uri=True);k=c.execute('SELECT AccessToken FROM ApiKeys LIMIT 1').fetchone()[0];c.close()\ndef api(p,d=None,method=None):\n r=urllib.request.Request('http://127.0.0.1:8096'+p,headers={'Authorization':'MediaBrowser Token="'+k+'", Client="ReceptionTrial", Device="Maintenance", DeviceId="decoder-reception-trial", Version="1"','Content-Type':'application/json'},data=json.dumps(d).encode() if d is not None else None,method=method)\n b=urllib.request.urlopen(r,timeout=10).read();return json.loads(b) if b else None\n`; const ssh = code => JSON.parse(execFileSync('ssh', ['-o', 'BatchMode=yes', 'root@192.168.100.25', 'pct exec 100191 -- python3 -'], { input: admin + code, encoding: 'utf8', timeout: 20000 }).trim().split('\n').at(-1)); let browser, started, serviceReference, baseline, testedChannelId, stopRequested = false; process.on('SIGINT', () => { stopRequested = true; }); process.on('SIGTERM', () => { stopRequested = true; }); const accounts = [], viewers = []; function elapsed() { return started === undefined ? null : Number(((performance.now() - started) / 1000).toFixed(2)); } async function makeViewer(index) { const account = ssh(`n='decoder-reception-'+secrets.token_hex(4);p=secrets.token_urlsafe(24);u=api('/Users/New',{'Name':n});print(json.dumps({'id':u['Id'],'name':n,'password':p}))`); accounts.push(account); ssh(`api('/Users/${account.id}/Password',{'CurrentPw':'','NewPw':${JSON.stringify(account.password)}});print('{}')`); const context = await browser.newContext({ viewport: { width: 1280, height: 900 } }), page = await context.newPage(); if (bridgeFile) { // A/B evaluation only: replace the plugin JS in this isolated browser, never on the server. await context.route('**/decoder-channel-playback.js*', route => { record('isolated_bridge_served', { viewer: index }); return route.fulfill({ contentType: 'application/javascript', body: fs.readFileSync(bridgeFile, 'utf8') }); }); await context.route('**/DecoderIntegration/WebPlaybackPolicy?*', route => { const requested = new URL(route.request().url()).searchParams.get('channelId')?.replace(/-/g, '').toLowerCase(); const matched = requested === testedChannelId.replace(/-/g, '').toLowerCase(); record('isolated_policy_request', { viewer: index, matched }); return route.fulfill({ json: { IsEnigmaChannel: matched, PreferH264: true } }); }); } const viewer = { index, context, page, active: false, closing: false, samples: [], errors: [], hls: { manifests: 0, segments: 0, failed: [], request_failed: [] } }; viewers.push(viewer); await page.addInitScript(() => { window.__trialEvents = []; for (const type of ['waiting','stalled','error','ended','playing','pause']) document.addEventListener(type, event => { if (event.target instanceof HTMLVideoElement) window.__trialEvents.push({ type, at: Date.now(), time: event.target.currentTime, error: event.target.error?.code || null }); }, true); }); page.on('pageerror', error => { const value = { elapsed: elapsed(), message: safe(error.message) }; viewer.errors.push(value); record('browser_error', { viewer: index, ...value }); }); page.on('response', response => { const path = new URL(response.url()).pathname; if (!/\.(m3u8|ts|m4s)$/.test(path)) return; if (path.endsWith('.m3u8')) viewer.hls.manifests++; else viewer.hls.segments++; if (response.status() >= 400) { const value = { elapsed: elapsed(), path, status: response.status() }; viewer.hls.failed.push(value); record('hls_error', { viewer: index, ...value }); } }); page.on('requestfailed', request => { const path = new URL(request.url()).pathname; if (/\.(m3u8|ts|m4s)$/.test(path)) { const value = { elapsed: elapsed(), path, message: safe(request.failure()?.errorText), during_close: viewer.closing }; if (!viewer.closing) viewer.hls.request_failed.push(value); record('hls_request_failed', { viewer: index, ...value }); } }); await page.goto(web); await page.locator('#txtManualName').fill(account.name); await page.locator('#txtManualPassword').fill(account.password); await page.getByRole('checkbox').check(); await page.getByRole('button', { name: 'Sign In', exact: true }).click(); await page.waitForURL(/home/, { timeout: 20000 }); await page.waitForFunction(() => JSON.parse(localStorage.getItem('jellyfin_credentials') || '{}').Servers?.some(s => s.AccessToken && s.UserId)); await page.locator('#txtManualPassword:visible').waitFor({ state: 'hidden', timeout: 10000 }); await page.waitForTimeout(1000); return viewer; } async function open(viewer) { const began = performance.now(); await viewer.page.goto(web + '#/livetv'); await viewer.page.waitForURL(/tab=2/, { timeout: 15000 }); const escaped = channel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); await viewer.page.locator('.card[data-type="TvChannel"]').filter({ has: viewer.page.locator('a.textActionButton', { hasText: new RegExp('^\\d+ ' + escaped + '$') }) }).locator('a.cardImageContainer').click({ position: { x: 20, y: 20 } }); viewer.active = true; await viewer.page.waitForFunction(() => [...document.querySelectorAll('video')].some(v => v.currentTime > 0 && v.readyState >= 2 && v.videoWidth > 0 && !v.paused), {}, { timeout: 30000 }); viewer.startup_seconds = Number(((performance.now() - began) / 1000).toFixed(2)); check('viewer ' + viewer.index + ' one-click decoded start', true, { seconds: viewer.startup_seconds }); } async function sample(viewer) { const data = await viewer.page.evaluate(() => { const videos = [...document.querySelectorAll('video')]; const video = videos.find(v => v.currentTime > 0 && v.videoWidth > 0 && !v.paused) || videos.find(v => v.currentTime > 0 && v.videoWidth > 0); const events = window.__trialEvents.splice(0); if (!video) return { video: null, events, route: location.hash }; const quality = video.getVideoPlaybackQuality?.(); const range = [...Array(video.buffered.length).keys()].find(i => video.buffered.start(i) <= video.currentTime && video.buffered.end(i) >= video.currentTime); return { video: { time: video.currentTime, frames: quality?.totalVideoFrames ?? video.webkitDecodedFrameCount ?? null, dropped: quality?.droppedVideoFrames ?? video.webkitDroppedFrameCount ?? null, audio_bytes: video.webkitAudioDecodedByteCount ?? null, width: video.videoWidth, height: video.videoHeight, ready: video.readyState, paused: video.paused, ended: video.ended, error: video.error?.code || null, buffered_ahead: range === undefined ? 0 : video.buffered.end(range) - video.currentTime }, events, route: location.hash }; }); const row = record('video', { viewer: viewer.index, elapsed: elapsed(), ...data }); viewer.samples.push(row); return row; } async function closeViewer(viewer) { if (!viewer.active) return; viewer.closing = true; let normal = false; try { if (await viewer.page.locator('#decoderPlaybackExit').count()) await viewer.page.locator('#decoderPlaybackExit').click(); await viewer.page.waitForURL(/livetv.*tab=2/, { timeout: 15000 }); normal = true; } catch (error) { record('exit_error', { viewer: viewer.index, message: safe(error.message) }); } try { await viewer.context.close(); } finally { viewer.active = false; } check('viewer ' + viewer.index + ' exit to Channels', normal, { elapsed: elapsed() }); } async function serverSnapshot(label) { const server = ssh(`s=api('/DecoderIntegration/Status');t=api('/Sessions');print(json.dumps({'plugin':next(x['Version'] for x in api('/Plugins') if x['Name']=='Enigma2'),'streams':s.get('Streams',{}),'probe_error':s.get('ProbeError'),'sessions':[{'user':x.get('UserName'),'channel':(x.get('NowPlayingItem') or {}).get('Name'),'transcoding':{k:(x.get('TranscodingInfo') or {}).get(k) for k in ['VideoCodec','AudioCodec','IsVideoDirect','IsAudioDirect','Framerate','TranscodeReasons']}} for x in t if x.get('UserName','').startswith('decoder-reception-')]}))`); server.streams.LastErrors = (server.streams.LastErrors || []).map(safe); if (server.probe_error) server.probe_error = safe(server.probe_error); // The receiver endpoint only reports existing clients; it does not open a TV source. let receiver; try { const response = await fetch('http://192.168.10.20/api/about', { signal: AbortSignal.timeout(8000) }); const body = await response.json(); const rows = body.info?.streams || body.streams || []; receiver = { status: response.status, jellyfin_channel_rows: rows.filter(row => row.ip === '192.168.100.191' && row.name === channel).length, rows: rows.map(row => ({ ip: row.ip, name: row.name, ref: row.ref })) }; // OpenWebif versions differ in field names. Preserve only non-sensitive stream fields. receiver.channel_matches = rows.filter(row => JSON.stringify(row).replace(/ /g, '').toLowerCase().includes(channel.replace(/ /g, '').toLowerCase())).length; } catch (error) { receiver = { error: safe(error.message) }; } const row = record('server', { label, elapsed: elapsed(), ...server, receiver }); report.snapshots.push(row); return row; } function identifyRelay(snapshot) { const reference = snapshot.receiver.rows?.find(row => row.ip === '192.168.100.191' && row.name === channel)?.ref; const normalize = text => decodeURIComponent(text || '').replace(/^\//, '').toUpperCase(); const exact = reference && snapshot.streams.Channels?.find(c => normalize(c.ServiceReference) === normalize(reference)); const added = snapshot.streams.Channels?.filter(c => !baseline?.streams.Channels?.some(b => b.Id === c.Id)) || []; const candidate = exact || (added.length === 1 ? added[0] : null); if (candidate) { report.relay_id = candidate.Id; serviceReference = candidate.ServiceReference; report.service_reference = serviceReference; } } (async () => { try { browser = await chromium.launch({ headless: true, channel: 'msedge', args: ['--autoplay-policy=no-user-gesture-required','--disable-background-timer-throttling','--disable-backgrounding-occluded-windows','--disable-renderer-backgrounding'] }); testedChannelId = ssh(`c=next(x for x in api('/LiveTv/Channels?Limit=1000')['Items'] if x['Name']==${JSON.stringify(channel)});print(json.dumps(c['Id']))`); const primary = await makeViewer(1), secondary = await makeViewer(2); baseline = await serverSnapshot('before'); await open(primary); started = performance.now(); report.first_video_utc = new Date().toISOString(); const first = await serverSnapshot('primary-started'); identifyRelay(first); if (frameFile) { await primary.page.waitForTimeout(3000); const handle = await primary.page.evaluateHandle(() => [...document.querySelectorAll('video')].find(v => v.currentTime > 0 && v.videoWidth > 0 && !v.paused)); const video = handle.asElement(); if (!video) throw Error('No active video for visual verification'); await video.screenshot({ path: frameFile }); await handle.dispose(); report.frame_artifact = frameFile; record('frame_saved', { path: frameFile, elapsed: elapsed() }); } let joined = false, left = false, nextServer = 30, nextProgress = 60; while (!stopRequested && elapsed() < duration) { if (!joined && elapsed() >= joinAt) { joined = true; report.secondary_request_seconds = elapsed(); await open(secondary); report.secondary_video_seconds = elapsed(); await serverSnapshot('secondary-joined'); } if (joined && !left && elapsed() >= leaveAt) { left = true; report.secondary_stop_seconds = elapsed(); await closeViewer(secondary); await serverSnapshot('secondary-left'); } for (const viewer of viewers.filter(v => v.active)) await sample(viewer); if (elapsed() >= nextServer) { await serverSnapshot('periodic'); nextServer = elapsed() + 30; } if (elapsed() >= nextProgress) { console.log(JSON.stringify({ elapsed: elapsed(), viewers: viewers.filter(v => v.active).map(v => ({ viewer: v.index, ...v.samples.at(-1)?.video })) })); nextProgress = elapsed() + 60; } await new Promise(resolve => setTimeout(resolve, 5000)); } report.playback_elapsed_seconds = elapsed(); report.primary_stop_seconds = elapsed(); await closeViewer(primary); check('scheduled duration completed', !stopRequested && report.playback_elapsed_seconds >= duration, { seconds: report.playback_elapsed_seconds, interrupted: stopRequested }); check('second viewer phase completed', joined && left, { joinAt, leaveAt }); } catch (error) { check('trial execution', false, { elapsed: elapsed(), error: safe(error.message) }); } finally { for (const viewer of viewers) try { await closeViewer(viewer); } catch (error) { check('viewer cleanup', false, { viewer: viewer.index, error: safe(error.message) }); } // Obtain final metrics before closing the browser API context; never close other users' sessions. if (browser && viewers.length) { await new Promise(resolve => setTimeout(resolve, 20000)); try { const after = await serverSnapshot('after-cleanup'); if (!report.relay_id) identifyRelay(after); const own = after.streams.Channels?.find(c => c.Id === report.relay_id), before = baseline?.streams.Channels?.find(c => c.Id === report.relay_id); const released = before?.Active ? own?.Consumers <= before.Consumers : !own || !own.Active && own.Consumers === 0; check('own relay consumers released after last viewer', Boolean(report.relay_id) && released, { relay_id: report.relay_id, active: own?.Active ?? false, consumers: own?.Consumers ?? 0, baseline_active: before?.Active ?? false, baseline_consumers: before?.Consumers ?? 0 }); } catch (error) { check('relay cleanup verification', false, { error: safe(error.message) }); } } if (browser) try { await browser.close(); } catch (error) { check('browser cleanup', false, { error: safe(error.message) }); } for (const account of accounts) try { ssh(`api('/Users/${account.id}',method='DELETE');pathlib.Path('/var/lib/jellyfin/decoder-integration/users/${account.id.replace(/-/g,'')}.json').unlink(missing_ok=True);print('{}')`); } catch (error) { check('test account cleanup', false, { error: safe(error.message) }); } for (const viewer of viewers) { let stalls = 0, audioIntervals = 0, frameIntervals = 0, previous; for (const row of viewer.samples) { if (!row.video) { stalls++; previous = null; continue; } if (previous) { if (row.video.time <= previous.time || row.video.frames !== null && previous.frames !== null && row.video.frames <= previous.frames) stalls++; else frameIntervals++; if (row.video.audio_bytes !== null && previous.audio_bytes !== null && row.video.audio_bytes > previous.audio_bytes) audioIntervals++; } previous = row.video; } check('viewer ' + viewer.index + ' continuous decoded progress', stalls === 0 && frameIntervals > 0, { samples: viewer.samples.length, progressing_intervals: frameIntervals, stalled_intervals: stalls, audio_progress_intervals: audioIntervals }); check('viewer ' + viewer.index + ' browser and HLS responses', viewer.errors.length === 0 && viewer.hls.failed.length === 0 && viewer.hls.request_failed.length === 0, { browser_errors: viewer.errors, hls: viewer.hls }); const available = viewer.samples.some(row => row.video?.audio_bytes !== null && row.video?.audio_bytes !== undefined); if (available) check('viewer ' + viewer.index + ' audio decoding progresses', audioIntervals > 0, { progressing_intervals: audioIntervals }); else { const row = { name: 'viewer ' + viewer.index + ' audio decoding progresses', status: 'UNVERIFIED', evidence: 'Browser does not expose decoded audio byte counter.' }; report.checks.push(row); record('check', row); } } const during = report.snapshots.filter(s => s.label === 'secondary-joined' || s.label === 'periodic' && s.elapsed >= joinAt && s.elapsed <= leaveAt); const beforeJoin = report.snapshots.filter(s => s.elapsed !== null && s.elapsed < joinAt).at(-1); check('sharing uses one physical receiver stream', during.length > 0 && beforeJoin && beforeJoin.receiver.jellyfin_channel_rows === 1 && during.every(s => s.receiver.jellyfin_channel_rows === 1 && s.streams.Channels?.some(c => c.Id === report.relay_id && c.Active)), { before_opened: beforeJoin?.streams.UpstreamConnectionsOpened, during: during.map(s => ({ elapsed: s.elapsed, opened: s.streams.UpstreamConnectionsOpened, physical_rows: s.receiver.jellyfin_channel_rows, consumers: s.streams.Channels?.find(c => c.Id === report.relay_id)?.Consumers })) }); check('global receiver upstream counter stable during sharing', during.length > 0 && beforeJoin && during.every(s => s.streams.UpstreamConnectionsOpened === beforeJoin.streams.UpstreamConnectionsOpened), { before: beforeJoin?.streams.UpstreamConnectionsOpened, during: during.map(s => s.streams.UpstreamConnectionsOpened), scope: 'Global plugin counter; correlate a failure with other users and channel snapshots.' }); report.finished_utc = new Date().toISOString(); report.pass = report.checks.filter(c => c.status === 'PASS').length; report.fail = report.checks.filter(c => c.status === 'FAIL').length; report.unverified = report.checks.filter(c => c.status === 'UNVERIFIED').length; report.viewers = viewers.map(v => ({ viewer: v.index, startup_seconds: v.startup_seconds, samples: v.samples, errors: v.errors, hls: v.hls })); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify({ report: reportPath, pass: report.pass, fail: report.fail, finished_utc: report.finished_utc })); process.exitCode = report.fail ? 1 : 0; } })();