From 0d56cd33b975afe7648dd6f6380a30a7b5230c90 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 18 Sep 2026 11:10:00 +0200 Subject: fix(cast): put the relay's progress log behind the same switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most of what the relay says repeats without bound — a line every fiftieth fragment for the length of a film, one per dropped fragment whenever a client falls behind — and it lands in the terminal the app was started from. Those go through `debuglog` now, restored with: NODE_DEBUG=cast-relay npm start The loss of fMP4 framing stays on console.warn. It recovers by rescanning, so nothing throws and no other part of the system hears about it; that line is the only trace that the picture on the television is missing a piece. Every other failure here throws, and the renderer already reports the rejection. The logging guard now covers both cast modules, each with the one line it is meant to keep audible. Also corrects the header comment, which still claimed CORS was granted on the subtitle path only. It has been on both since the stream needed it too. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UGY17EPph5LsLzePPXhUVc --- packages/meshbay-client/src/cast-relay.js | 53 ++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 15 deletions(-) (limited to 'packages/meshbay-client') diff --git a/packages/meshbay-client/src/cast-relay.js b/packages/meshbay-client/src/cast-relay.js index 71c07a4..58eb9ac 100644 --- a/packages/meshbay-client/src/cast-relay.js +++ b/packages/meshbay-client/src/cast-relay.js @@ -19,7 +19,7 @@ * · Bind to the LAN interface, never 0.0.0.0 * · Fixed port range (19550-19553), opened only during active cast * · Unguessable token in the URL (32 hex chars) - * · CORS is granted on the subtitle path only, and still behind the token + * · CORS is granted on both served paths, and both stay behind the token * · Cache-Control: no-store on every response * · Server destroyed when playback stops — zero residual surface */ @@ -29,6 +29,25 @@ const crypto = require('node:crypto'); const http = require('node:http'); const os = require('node:os'); +const util = require('node:util'); + +/** + * Progress logging, off unless asked for: + * + * NODE_DEBUG=cast-relay npm start + * + * Most of what this class has to say repeats without bound — a line every + * fiftieth fragment for the length of a film, one per dropped fragment + * whenever a client falls behind — and it lands in the terminal the app was + * started from. Node's own `debuglog` is used rather than a flag of our own: + * it costs nothing when disabled, since the message is never even formatted. + * + * Nothing that stops a cast is hidden behind it. Every hard failure here + * throws — no free port, no such file — and the renderer already reports the + * rejection. The one fault that neither throws nor reaches anybody else is a + * loss of fMP4 framing, and that one stays on `console.warn`. + */ +const debug = util.debuglog('cast-relay'); const RING_CAP = 64; const BACKPRESSURE_HIGH = 8 * 1024 * 1024; @@ -71,7 +90,7 @@ class BoxAccumulator { if (!this._synced) { const idx = this._findMoof(); if (idx === -1) return fragments; - console.log(`[cast-relay] box sync: found first moof at byte offset ${idx}, discarded ${idx} bytes`); + debug(`[cast-relay] box sync: found first moof at byte offset ${idx}, discarded ${idx} bytes`); this._buf = this._buf.subarray(idx); this._synced = true; } @@ -81,7 +100,11 @@ class BoxAccumulator { const type = this._buf.readUInt32BE(4); if (size < 8) { - console.log(`[cast-relay] box sync lost: invalid size ${size}, rescanning`); + // Not `debug`: the byte stream stopped being framed fMP4. It recovers + // by rescanning, so nothing throws and nobody else ever hears about + // it — this line is the only trace that the picture on the television + // is missing a piece. + console.warn(`[cast-relay] box sync lost: invalid size ${size}, rescanning`); this._synced = false; const idx = this._findMoof(); if (idx === -1) return fragments; @@ -182,7 +205,7 @@ class CastRelay { if (!sub || !sub.vtt) { this._subtitle = null; this._subtitleVersion++; - console.log('[cast-relay] subtitle cleared'); + debug('[cast-relay] subtitle cleared'); return null; } this._subtitle = { @@ -191,7 +214,7 @@ class CastRelay { label: sub.label || '', }; this._subtitleVersion++; - console.log(`[cast-relay] subtitle set: ${this._subtitle.vtt.length} bytes` + debug(`[cast-relay] subtitle set: ${this._subtitle.vtt.length} bytes` + `, lang "${this._subtitle.language}", v${this._subtitleVersion}`); return this.subtitle; } @@ -232,14 +255,14 @@ class CastRelay { bound = true; } catch (err) { if (err.code !== 'EADDRINUSE') throw err; - console.log(`[cast-relay] port ${port} busy, trying next`); + debug(`[cast-relay] port ${port} busy, trying next`); } } if (!bound) throw new Error('All cast relay ports are in use'); this._server = server; - console.log(`[cast-relay] started on ${this.url}`); - console.log(`[cast-relay] init segment: ${this._initSegment ? this._initSegment.length + ' bytes' : 'none'}`); + debug(`[cast-relay] started on ${this.url}`); + debug(`[cast-relay] init segment: ${this._initSegment ? this._initSegment.length + ' bytes' : 'none'}`); return { url: this.url, port: this._port, @@ -257,7 +280,7 @@ class CastRelay { for (const frag of fragments) { this._fragCount++; if (this._fragCount <= 3 || this._fragCount % 50 === 0) { - console.log(`[cast-relay] fragment #${this._fragCount}: ${frag.length} bytes (from ${this._chunkCount} chunks), ${this._clients.size} client(s)`); + debug(`[cast-relay] fragment #${this._fragCount}: ${frag.length} bytes (from ${this._chunkCount} chunks), ${this._clients.size} client(s)`); } if (this._ring.length >= RING_CAP) { @@ -267,7 +290,7 @@ class CastRelay { for (const res of this._clients) { if (res.writableLength > BACKPRESSURE_HIGH) { - console.log(`[cast-relay] backpressure: dropping fragment for slow client`); + debug(`[cast-relay] backpressure: dropping fragment for slow client`); continue; } res.write(frag); @@ -277,14 +300,14 @@ class CastRelay { } finish() { - console.log('[cast-relay] finishing stream'); + debug('[cast-relay] finishing stream'); for (const res of this._clients) { try { res.end(); } catch { /* already closed */ } } } async stop() { - console.log(`[cast-relay] stopping — ${this._chunkCount} chunks, ${this._fragCount} fragments, ${(this._bytesSent / 1048576).toFixed(1)} MB sent`); + debug(`[cast-relay] stopping — ${this._chunkCount} chunks, ${this._fragCount} fragments, ${(this._bytesSent / 1048576).toFixed(1)} MB sent`); for (const res of this._clients) { try { res.end(); } catch { /* already closed */ } } @@ -372,12 +395,12 @@ class CastRelay { sent += frag.length; } - console.log(`[cast-relay] client connected from ${req.socket.remoteAddress} — sent init + ${this._ring.length} fragments (${(sent / 1024).toFixed(0)} KB)`); + debug(`[cast-relay] client connected from ${req.socket.remoteAddress} — sent init + ${this._ring.length} fragments (${(sent / 1024).toFixed(0)} KB)`); this._clients.add(res); req.on('close', () => { this._clients.delete(res); - console.log(`[cast-relay] client disconnected, ${this._clients.size} remaining`); + debug(`[cast-relay] client disconnected, ${this._clients.size} remaining`); }); } @@ -394,7 +417,7 @@ class CastRelay { 'Cache-Control': 'no-store', }); res.end(this._subtitle.vtt); - console.log(`[cast-relay] subtitle served: ${this._subtitle.vtt.length} bytes`); + debug(`[cast-relay] subtitle served: ${this._subtitle.vtt.length} bytes`); } } -- cgit v1.2.3