From 8730281d739d9ed1d6f2d366772582eea8ba0294 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 21 Aug 2026 13:52:11 +0200 Subject: feat: LAN Wi-Fi casting to Chromecast via local HTTP relay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-serve decrypted fMP4 video over HTTP on the LAN so a Chromecast can play the stream. The relay runs in the Electron main process — same trust boundary as downloads and MSE playback. - cast-relay.js: HTTP server with BoxAccumulator (reassembles WebRTC chunks into moof+mdat pairs), ring buffer, backpressure, finish() for clean end-of-stream, fixed port range 19550-19553 - cast-chromecast.js: mDNS discovery (bonjour-service) + CASTV2 protocol (castv2-client), connect/reload/disconnect lifecycle - Seek-aware: relay restarts on every seek, Chromecast reloads new URL; generation counter prevents stale async errors from killing active restarts; landingPlayheadRef suppresses programmatic seeking events - Device picker in video top bar with scan, device selection, copy-URL fallback, and cast status indicator - IPC bridge (main/preload/platform) for start/push/stop/finish/status/ discover/chromecastConnect/chromecastReload/chromecastDisconnect - Phase 3 design doc for DLNA/Smart TV in docs/cast-smart-tv.md Co-Authored-By: Claude Opus 4.6 --- .../meshbay-hub/src/meshbay_hub/static/platform.js | 55 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/platform.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js index 02107f5..d400b4e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js @@ -46,6 +46,8 @@ export const capabilities = { localFolders: Boolean(bridge && bridge.capabilities && bridge.capabilities.localFolders), // A real save dialog and a write that does not pass through the page. nativeSave: Boolean(bridge && bridge.capabilities && bridge.capabilities.nativeSave), + // Cast decrypted video to a device on the same LAN via a local HTTP relay. + lanCast: Boolean(bridge && bridge.capabilities && bridge.capabilities.lanCast), }; /** @@ -234,8 +236,56 @@ export const node = { }, }; +/** + * LAN cast relay — re-serve decrypted video segments over HTTP so a + * Chromecast or Smart TV on the same Wi-Fi can play the stream. + * + * Absent in a browser, where the relay cannot run: there is no main process + * to bind a server socket in, and a page cannot open one. + */ +export const cast = { + available: Boolean(bridge && bridge.cast), + async start(opts) { + if (!bridge || !bridge.cast) return null; + return bridge.cast.start(opts); + }, + async push(data) { + if (!bridge || !bridge.cast) return false; + return bridge.cast.push(data); + }, + async stop() { + if (!bridge || !bridge.cast) return false; + return bridge.cast.stop(); + }, + async finish() { + if (!bridge || !bridge.cast) return false; + return bridge.cast.finish(); + }, + async status() { + if (!bridge || !bridge.cast) return null; + return bridge.cast.status(); + }, + async discover() { + if (!bridge || !bridge.cast) return []; + return bridge.cast.discover(); + }, + async chromecastConnect(opts) { + if (!bridge || !bridge.cast) return null; + return bridge.cast.chromecastConnect(opts); + }, + async chromecastReload(opts) { + if (!bridge || !bridge.cast) return null; + return bridge.cast.chromecastReload(opts); + }, + async chromecastDisconnect() { + if (!bridge || !bridge.cast) return false; + return bridge.cast.chromecastDisconnect(); + }, +}; + export default { isNative, hubBase, capabilities, secrets, nativeSave, - apiFetch, device, bridgeMessage, folder, rootPicker, node }; + apiFetch, device, bridgeMessage, folder, rootPicker, node, + cast }; // Also a global, because `transport.js` is loaded as a classic script — it // predates the module graph and exposes `MeshBayTransport` the same way. The @@ -244,5 +294,6 @@ export default { isNative, hubBase, capabilities, secrets, nativeSave, if (typeof window !== 'undefined') { window.MeshBayPlatform = { isNative, hubBase, capabilities, secrets, nativeSave, apiFetch, device, - bridgeMessage, folder, rootPicker, node }; + bridgeMessage, folder, rootPicker, node, + cast }; } -- cgit v1.2.3