• v2026.06.07
    android / Build + lint + test (push) Successful in 4m37s
    release / Build signed APK (tag releases only) (push) Successful in 4m21s
    release / Build + push container image (push) Successful in 15s
    Stable

    bvandeusen released this 2026-06-07 19:15:01 -04:00 | 81 commits to main since this release

    Single-fix release on the per-day mutable CalVer scheme. (PR #88.)

    Sonos — a single transport SOAP failure no longer reverts to local (Android)

    The motivating bug: playing to a Sonos speaker, the phone locked, and a single transport command (lock-screen / Bluetooth / watch media button, or a queue-resync play) issued during a WiFi power-save stall flashed "Disconnected from <Sonos>" and dropped playback back to the phone — even though the speaker was perfectly reachable.

    A transport command issued during screen-off WiFi sleep / Doze hit the 2 s connect timeout and threw. handleSoapFailure then cancelled the poll loop and fired onDrop on that single failure, bypassing the poll loop's deliberate 30-consecutive-failure tolerance (DROP_THRESHOLD, bumped from 3 precisely for this case).

    Fix — make the 1 Hz poll loop the sole drop arbiter:

    • Transport SOAP commands now retry transient IO failures (retryTransientIo: 3 attempts, 400 ms backoff) so a brief WiFi stall lands the command once WiFi wakes instead of abandoning it. A SoapFaultException (renderer answered and rejected the action) is not retried — the device is alive.
    • handleTransportFailure no longer cancels the poll loop or fires onDrop; it logs and nudges an immediate poll so the UI reconciles to Sonos's actual state. If the renderer is truly gone, the poll loop trips the drop on its own via DROP_THRESHOLD.
    • retryTransientIo extracted as an internal top-level fn with a unit test (first-success, retry-then-succeed, exhaust-and-rethrow, no-retry-on-SoapFault).

    Verify on device

    • Play to a Sonos speaker, lock the phone, let WiFi sleep, then hit a lock-screen / Bluetooth / watch transport button → the command lands (once WiFi wakes); the app stays routed to Sonos instead of flashing "Disconnected" and reverting to local.
    • Power off the Sonos mid-session → after ~30 s of failed polls the app drops to local on its own (the deliberate tolerance still fires).
    Downloads