summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-player.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js43
2 files changed, 38 insertions, 14 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
index 7a3978c..73cae27 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
@@ -218,7 +218,14 @@ function MusicPlayerBar({ transportRef, gekRef, queue, onClose }) {
const cached = blobCacheRef.current.get(entry.id);
if (cached) return cached.url;
const transport = transportRef.current;
- if (!transport || !transport.connected) throw new Error(t('music.err_transport'));
+ if (!transport) throw new Error(t('music.err_transport'));
+ // A track ending (or "next") right after a screen-lock reconnect started
+ // is exactly when this used to throw: `connected` was still false because
+ // the reconnect it only had to wait a few seconds for hadn't landed yet.
+ // waitForReconnect is a no-op when nothing is in flight, so this costs
+ // nothing on the ordinary path.
+ if (!transport.connected) await transport.waitForReconnect();
+ if (!transport.connected) throw new Error(t('music.err_transport'));
let downloadId = entry.id;
let downloadSize = entry.size;
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 56ec2c7..4eb7ac2 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -1727,6 +1727,30 @@ class MeshBayTransport {
get gekRaw() { return this._gekRaw; }
+ /**
+ * Give an automatic reconnect already in progress (see _reconnectLoop) a
+ * bounded chance to land before giving up.
+ *
+ * _sendAndWait does this internally for every request that goes through
+ * it, so most callers never need this directly. It exists for the ones
+ * that check `transport.connected` themselves before doing anything else —
+ * music-player.js's fetchTrackBlob is the one this was written for: found
+ * live throwing "Transport not connected" on the track *after* a
+ * screen-lock reconnect had already been under way for a while, because
+ * that check ran, saw `connected` still false, and threw before the
+ * reconnect it only had to wait a few seconds for got the chance to finish.
+ * A no-op — returns immediately — when nothing is being reconnected,
+ * including once one has already succeeded, so it is safe to call
+ * unconditionally ahead of such a check.
+ */
+ async waitForReconnect(timeoutMs = 6000) {
+ if (!this._reconnectPromise) return;
+ await Promise.race([
+ this._reconnectPromise.catch(() => {}),
+ new Promise((r) => setTimeout(r, timeoutMs)),
+ ]);
+ }
+
close() {
// Must be set before pc.close() below: that close() itself can drive the
// pc to "closed" synchronously, and the connectionstatechange handler
@@ -1745,19 +1769,12 @@ class MeshBayTransport {
async _sendAndWait(obj, timeoutMs = 30000) {
// A reconnect already in flight (see _reconnectLoop) means the channel
- // this would send on is the one just declared dead. Waiting here, bounded
- // rather than open-ended — the loop can be backing off for up to 30s
- // between attempts, and a caller (in particular file-utils.js's
- // pipelinedDownload, which retries a lost chunk itself) should get its
- // own timeout rather than sit through someone else's backoff — gives a
- // fresh handshake a real chance to land before this request is even
- // attempted, instead of guaranteeing it dies with the old one.
- if (this._reconnectPromise && !this._inReconnectAttempt) {
- await Promise.race([
- this._reconnectPromise.catch(() => {}),
- new Promise((r) => setTimeout(r, 6000)),
- ]);
- }
+ // this would send on is the one just declared dead. `_inReconnectAttempt`
+ // excludes the handshake connect() itself makes while reconnecting — that
+ // call runs *inside* this same _reconnectPromise, which cannot resolve
+ // until it returns, so waiting on it here would just be waiting on
+ // itself for the full 6s, on every step of the handshake, every time.
+ if (!this._inReconnectAttempt) await this.waitForReconnect(6000);
return new Promise((resolve, reject) => {
const id = this._seqId++;
const timeout = setTimeout(() => {