diff --git a/public/js/net.js b/public/js/net.js index 08bb1c0..ea53c24 100644 --- a/public/js/net.js +++ b/public/js/net.js @@ -14,11 +14,16 @@ ws.onopen = () => { ready = true; setStatus(true); while (queue.length) ws.send(queue.shift()); }; ws.onclose = () => { ready = false; setStatus(false); setTimeout(connect, 1000); }; // survive server restarts ws.onerror = () => { try { ws.close(); } catch (e) {} }; - ws.onmessage = (ev) => { - let m; try { m = JSON.parse(ev.data); } catch (e) { return; } + const handle = (txt) => { + let m; try { m = JSON.parse(txt); } catch (e) { return; } if (m.ch === 'ctl') ctlCbs.forEach(cb => cb(m.msg)); else if (m.ch === 'rpt') rptCbs.forEach(cb => cb(m.msg)); }; + ws.onmessage = (ev) => { + if (typeof ev.data === 'string') handle(ev.data); + else if (ev.data && typeof ev.data.text === 'function') ev.data.text().then(handle); // Blob + else handle(String(ev.data)); + }; } connect(); diff --git a/public/js/output.js b/public/js/output.js index 232b26a..d2173e0 100644 --- a/public/js/output.js +++ b/public/js/output.js @@ -341,6 +341,9 @@ djv.onControl(async (m) => { djv.report({ type: 'playState', playing: true }); } catch (e) { djv.report({ type: 'error', message: e.message }); } break; + case 'diag': + djv.report({ type: 'diag', stage: 'poll', ctx: audio.ctx.state, mode: audio.mode }); + break; case 'playVideoTrack': try { audio.setTrim(m.start || 0, m.end || 0); diff --git a/scripts/kiosk.sh b/scripts/kiosk.sh index d8dcf33..ae89032 100755 --- a/scripts/kiosk.sh +++ b/scripts/kiosk.sh @@ -11,4 +11,5 @@ exec "$BIN" \ --ozone-platform=wayland \ --ignore-gpu-blocklist \ --test-type \ + --disk-cache-size=1 --disable-back-forward-cache \ "$URL" >/tmp/chromium-kiosk.log 2>&1 diff --git a/server.js b/server.js index 5436945..2b9442b 100644 --- a/server.js +++ b/server.js @@ -30,7 +30,11 @@ function ffmpegPath() { const app = express(); app.use(express.json({ limit: '4mb' })); -app.use(express.static(path.join(ROOT, 'public'))); +// Never cache the app shell (HTML/JS/CSS): on a local appliance the network is +// fast and this guarantees the kiosk/phone always run the latest deployed code. +// Media files (large) stay cacheable. +app.use((req, res, next) => { if (!req.path.startsWith('/media')) res.set('Cache-Control', 'no-store'); next(); }); +app.use(express.static(path.join(ROOT, 'public'), { etag: false, lastModified: false })); app.use('/media', express.static(MEDIA_DIR)); app.use('/svg', express.static(SVG_DIR)); @@ -63,6 +67,8 @@ app.post('/api/upload', upload.array('files'), (req, res) => { res.json({ ok: true, count: (req.files || []).length }); }); +app.get('/api/clients', (_req, res) => { res.json([...wss.clients].map((c) => ({ role: c.role, open: c.readyState === 1 }))); }); + app.get('/api/svgs', (_req, res) => { let files = []; try { files = fs.readdirSync(SVG_DIR).filter(f => /\.svg$/i.test(f)); } catch (e) {} @@ -163,7 +169,10 @@ wss.on('connection', (ws, req) => { } }); function broadcast(role, data) { - wss.clients.forEach(c => { if (c.readyState === 1 && c.role === role) c.send(data); }); + // Always send a TEXT frame: browsers receive binary frames as Blob and would + // fail JSON.parse, so commands would silently never reach the output page. + const txt = typeof data === 'string' ? data : data.toString(); + wss.clients.forEach(c => { if (c.readyState === 1 && c.role === role) c.send(txt); }); } server.listen(PORT, () => {