summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src/main.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-21 13:52:11 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-21 13:52:11 +0200
commit8730281d739d9ed1d6f2d366772582eea8ba0294 (patch)
tree4df2f3c8d42dc17147bcdec23109aa9c0f1b5b35 /packages/meshbay-client/src/main.js
parentae52b69b14a6997d01aad66f49fbea7b5ca2dfe6 (diff)
downloadmeshbay-8730281d739d9ed1d6f2d366772582eea8ba0294.tar.gz
feat: LAN Wi-Fi casting to Chromecast via local HTTP relay
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-client/src/main.js')
-rw-r--r--packages/meshbay-client/src/main.js59
1 files changed, 59 insertions, 0 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index f2ec645..45e8462 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -351,6 +351,11 @@ function createWindow() {
// renderer parses decrypted content from nodes, which is attacker-controlled
// input, so it is treated as hostile even though it is our own code.
+const CastRelay = require('./cast-relay.js');
+const castRelay = new CastRelay();
+const CastChromecast = require('./cast-chromecast.js');
+const castChromecast = new CastChromecast();
+
function registerBridge() {
ipcMain.handle('hub:set', async (_e, base) => {
const url = String(base || '').trim().replace(/\/+$/, '');
@@ -738,6 +743,60 @@ function registerBridge() {
_nodePairingCode = code || null;
return true;
});
+
+ // ── LAN cast relay ──────────────────────────────────────────────────────
+ //
+ // A local HTTP server that re-serves decrypted fMP4 segments so a
+ // Chromecast or Smart TV on the same Wi-Fi can stream the video. The
+ // renderer feeds it segments via IPC; the relay serves them over HTTP.
+ // Same trust boundary as the MSE player and the download-to-disk path.
+
+ ipcMain.handle('cast:start', async (_e, opts) => {
+ return castRelay.start({
+ codec: opts.codec,
+ initSegment: opts.initSegment ? Buffer.from(opts.initSegment) : null,
+ });
+ });
+
+ ipcMain.handle('cast:push', async (_e, data) => {
+ castRelay.pushSegment(Buffer.from(data));
+ return true;
+ });
+
+ ipcMain.handle('cast:stop', async () => {
+ await castRelay.stop();
+ return true;
+ });
+
+ ipcMain.handle('cast:finish', async () => {
+ castRelay.finish();
+ return true;
+ });
+
+ ipcMain.handle('cast:status', async () => ({
+ active: castRelay.active,
+ url: castRelay.url,
+ chromecast: castChromecast.getStatus(),
+ }));
+
+ // ── Chromecast discovery + control ──────────────────────────────────────
+
+ ipcMain.handle('cast:discover', async () => {
+ return castChromecast.discover();
+ });
+
+ ipcMain.handle('cast:chromecast:connect', async (_e, { deviceId, mediaUrl }) => {
+ return castChromecast.connect(deviceId, mediaUrl);
+ });
+
+ ipcMain.handle('cast:chromecast:reload', async (_e, { mediaUrl }) => {
+ return castChromecast.reload(mediaUrl);
+ });
+
+ ipcMain.handle('cast:chromecast:disconnect', async () => {
+ await castChromecast.disconnect();
+ return true;
+ });
}
/**