'use strict'; // BrightSign's SyncManager is frame-accurate but it has one property that turns a correct-looking // integration into a visible fault: synchronize() REPEATS AT 1Hz, so a player powered on late can // still join the session. Acting on every repeat reloads the video once a second, forever — on // screen that reads as a stutter or a restart loop, not as a sync bug, which is exactly the kind of // thing that gets misdiagnosed for days. Both cookbook examples carry the same warning comment. // // The other half is the leader. Ours is leaderless; this protocol is not. The leader receives its // OWN broadcast and starts from it, which is what stops it running ahead of its followers by the // width of the network — so "leader ignores its own event" would be a subtle desync, not a // harmless optimisation. // // Run against a fake @brightsign/syncmanager, so the protocol contract is checked without hardware. const { test } = require('node:test'); const assert = require('node:assert/strict'); const vm = require('node:vm'); const fs = require('node:fs'); const path = require('node:path'); const SRC = fs.readFileSync(path.join(__dirname, '..', '..', 'brightsign', 'st-sync.js'), 'utf8'); function load({ withModule = true } = {}) { const instances = []; const sandbox = { console: { log() {}, warn() {}, error() {} }, Date, String, __instances: instances, }; sandbox.window = sandbox; if (withModule) { sandbox.require = (name) => { if (name !== '@brightsign/syncmanager') throw new Error('no such module ' + name); return function (iface, domain, addr, port) { const self = { args: { iface, domain, addr, port }, leader: undefined, encrypted: undefined, listeners: {}, announced: [], closed: false, addEventListener(evt, fn) { (self.listeners[evt] = self.listeners[evt] || []).push(fn); }, synchronize(id, msDelay) { self.announced.push({ id, msDelay }); }, close() { self.closed = true; }, // test helper: deliver a multicast event to this member emit(e) { (self.listeners.syncevent || []).forEach((fn) => fn(e)); }, }; instances.push(self); return self; }; }; } vm.createContext(sandbox); vm.runInContext(SRC, sandbox); return { api: sandbox.ScreenTinkerBSSync, instances }; } /** A fake BrightSign