Skip to content

v1.5.7 — hotfix: guarantee MSE fallback (1.5.6 silent freeze)

Choose a tag to compare

@Kepners Kepners released this 11 May 15:15

PC Nest Speaker 1.5.7 — hotfix

1.5.6 introduced a regression: on Cast Audio, await fetch() can hang forever without ever resolving headers (the historical Cast Audio Chromium quirk noted in CLAUDE.md as "request opens, headers never resolve to JS"). The MSE path's first-byte watchdog was armed after fetch() returned, so when fetch hung the watchdog never armed, the function awaited indefinitely, the caller's catch never ran, and the <audio> fallback never fired → user got no audio at all.

Server-side log confirmed bytes were flowing on the socket (453 B init segment sent, bitrate climbed to ~228 kbps), but the JS fetch() Promise never resolved with the response object.

What 1.5.7 changes (receiver only)

Two layers of watchdog so the fallback to plain <audio> always fires:

  1. Inner — race fetch() against a 2.5 s timeout that calls pcmAbort.abort() and rejects.
  2. Outer — master watchdog wraps the whole MSE attempt at 4 s. Even if reader.read(), appendBuffer(), or sourceopen were the hanging step instead of fetch(), the master watchdog still trips fallback.

Even on a Cast Audio runtime where AbortController.abort() is itself broken, Promise.race rejects on schedule, the caller's catch block runs, disconnectHttpPcm() cleans MSE state, and the plain <audio> path takes over. Audio is restored within ~5 s of pressing Play in the worst case, vs. silence forever in 1.5.6.

What you'll see in the log

If MSE works on this runtime (best case):

mse-fetch-start → mse-fetch-headers → mse-first-byte → mse-canplay → mse-playing
mse-seek-probe { seek_works: true|false }

If MSE hangs (the 1.5.6 silent-failure case, now non-silent):

mse-fetch-start
mse-fallback-to-audio reason=fetch watchdog 2500ms     ← NEW
audio-canplay → audio-playing                          ← same path as 1.5.5, audio plays

The reason= field tells us whether fetch() hangs, the master watchdog tripped, or some other step is the blocker. That's the diagnostic signal that replaces the silent freeze.

Plan B if MSE is fundamentally broken on Cast Audio

Replace fetch().body.getReader() with XMLHttpRequest + progress events. XHR2 has been on Chromium longer and the Cast Audio runtime is more likely to honor it. Will only attempt this if 1.5.7 logs confirm fetch() is the specific blocker (not the master watchdog tripping on something later).

Files changed

  • pcnestspeaker-receiver/receiver-audio.html — fetch race + master watchdog
  • package.json — version bump

Desktop binaries unchanged from 1.5.6 functionally; rebuild for version-string parity.