aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-02 08:39:56 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-02 08:39:56 +0200
commite941cc4c39c38a12220153ea572bd4c7bb92fde0 (patch)
treeb7a0c1f04eb642f4aa050508b3e24bb4cf66a67a /packages/meshbay-client
parentfee265e0046e85c3db0e5bb9608b666d2e9547ab (diff)
downloadmeshbay-e941cc4c39c38a12220153ea572bd4c7bb92fde0.tar.gz
feat(client): list cast receivers as they answer
The scan still runs six seconds, but the picker polls what it has found and shows each receiver immediately. A rescan no longer has its timer cut short by the scan it replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-client')
-rw-r--r--packages/meshbay-client/src/cast-chromecast.js71
-rw-r--r--packages/meshbay-client/src/main.js7
-rw-r--r--packages/meshbay-client/src/preload.js3
3 files changed, 50 insertions, 31 deletions
diff --git a/packages/meshbay-client/src/cast-chromecast.js b/packages/meshbay-client/src/cast-chromecast.js
index 1355cdd..7fc8079 100644
--- a/packages/meshbay-client/src/cast-chromecast.js
+++ b/packages/meshbay-client/src/cast-chromecast.js
@@ -78,19 +78,25 @@ class CastChromecast {
constructor() {
this._bonjour = null;
this._browser = null;
+ this._scanTimer = null;
this._devices = new Map();
this._client = null;
this._player = null;
this._connectedDevice = null;
}
- async discover() {
+ /**
+ * Start a scan and return at once; `devices()` reads what it has found so far.
+ *
+ * The scan runs its full length because a receiver coming back from a reset
+ * can take several seconds to answer, but most answer within two, and a
+ * picker that waits the full length to show any of them is a picker that is
+ * slow every single time. So the page polls and lists each one as it lands.
+ * A scan started over an unfinished one replaces it, timer included.
+ */
+ startScan() {
this._devices.clear();
-
- if (this._browser) {
- this._browser.stop();
- this._browser = null;
- }
+ this._stopScan();
if (!this._bonjour) {
this._bonjour = new Bonjour();
@@ -98,30 +104,39 @@ class CastChromecast {
debug('[cast-chromecast] scanning for devices...');
- return new Promise((resolve) => {
- this._browser = this._bonjour.find({ type: 'googlecast' }, (service) => {
- const id = service.txt?.id || service.name;
- const name = service.txt?.fn || service.name;
- const host = service.addresses?.find((a) => /^\d+\.\d+\.\d+\.\d+$/.test(a))
- || (service.referer && service.referer.address);
- const port = service.port || 8009;
+ this._browser = this._bonjour.find({ type: 'googlecast' }, (service) => {
+ const id = service.txt?.id || service.name;
+ const name = service.txt?.fn || service.name;
+ const host = service.addresses?.find((a) => /^\d+\.\d+\.\d+\.\d+$/.test(a))
+ || (service.referer && service.referer.address);
+ const port = service.port || 8009;
- if (host && id) {
- this._devices.set(id, { id, name, host, port });
- debug(`[cast-chromecast] discovered: "${name}" at ${host}:${port}`);
- }
- });
-
- setTimeout(() => {
- if (this._browser) {
- this._browser.stop();
- this._browser = null;
- }
- const devices = Array.from(this._devices.values());
- debug(`[cast-chromecast] scan complete: ${devices.length} device(s)`);
- resolve(devices);
- }, SCAN_DURATION_MS);
+ if (host && id) {
+ this._devices.set(id, { id, name, host, port });
+ debug(`[cast-chromecast] discovered: "${name}" at ${host}:${port}`);
+ }
});
+
+ this._scanTimer = setTimeout(() => {
+ this._stopScan();
+ debug(`[cast-chromecast] scan complete: ${this._devices.size} device(s)`);
+ }, SCAN_DURATION_MS);
+ }
+
+ devices() {
+ return {
+ devices: Array.from(this._devices.values()),
+ scanning: this._browser !== null,
+ };
+ }
+
+ _stopScan() {
+ clearTimeout(this._scanTimer);
+ this._scanTimer = null;
+ if (this._browser) {
+ this._browser.stop();
+ this._browser = null;
+ }
}
async connect(deviceId, mediaUrl, subtitle) {
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index c1ff540..cbaabc4 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -2330,10 +2330,13 @@ function registerBridge() {
// ── Chromecast discovery + control ──────────────────────────────────────
- handle('cast:discover', async () => {
- return castChromecast.discover();
+ handle('cast:scan', async () => {
+ castChromecast.startScan();
+ return true;
});
+ handle('cast:devices', async () => castChromecast.devices());
+
handle('cast:chromecast:connect', async (_e, { deviceId, mediaUrl, subtitle }) => {
return castChromecast.connect(deviceId, mediaUrl,
subtitle === undefined ? castRelay.subtitle : subtitle);
diff --git a/packages/meshbay-client/src/preload.js b/packages/meshbay-client/src/preload.js
index e9c34d2..665ec1f 100644
--- a/packages/meshbay-client/src/preload.js
+++ b/packages/meshbay-client/src/preload.js
@@ -175,7 +175,8 @@ contextBridge.exposeInMainWorld('meshbay', {
subtitle: (sub) => ipcRenderer.invoke('cast:subtitle', sub),
finish: () => ipcRenderer.invoke('cast:finish'),
status: () => ipcRenderer.invoke('cast:status'),
- discover: () => ipcRenderer.invoke('cast:discover'),
+ scan: () => ipcRenderer.invoke('cast:scan'),
+ devices: () => ipcRenderer.invoke('cast:devices'),
chromecastConnect: (opts) => ipcRenderer.invoke('cast:chromecast:connect', opts),
chromecastReload: (opts) => ipcRenderer.invoke('cast:chromecast:reload', opts),
chromecastDisconnect: () => ipcRenderer.invoke('cast:chromecast:disconnect'),