Skip to content

Buffering #78

@cdome

Description

@cdome

Hey, this app looks really cool and really would love to use it more, given all the awesome features it already has, but i'm often listening music outside of my home (vpn connection). While my home connection is reasonably fast and stable, I cannot say the same about cellular connection :)

Given this app has a Android Auto support, I expect this app is not intended for usage only at the warmth of our homes.

But the problem with this app is the buffering part, which seems quite inadequate for this use case - what I understood from asking claude, this app uses only 200ms native buffer (+ 10s max additional buffer), which is not much, given long reconnection times.

My experience confirms this - when in my car, the music is dropping almost all the time, often just stops and doesn't seem to continue.

Below goes what Claude told me (just copy pasting it for completeness, might be useful or pile of garbage, don't kill me for that, please ;) ).


⏺ You're right to flag this — it's a real vulnerability. Here's the honest picture:

What exists for resilience

API WebSocket (control plane):

  • Heartbeat that detects stale connections (10s timeout)
  • Auto-reconnect on disconnect/error (with a delay via Timings.reconnectDelay)
  • Cancels all pending requests on disconnect to prevent memory leaks
  • Guards against duplicate reconnection attempts
  • RetryHelper with exponential backoff (2s → 4s → 8s → 16s) for API calls like library fetches

Sendspin WebSocket (audio stream):

  • Reconnect after 5 seconds on disconnect
  • 5-second timeout per connection attempt
  • Fallback chain: external WSS → local WS → WebRTC

Where it breaks down

The PCM streaming path is fragile. With only ~200ms in the native buffer and ~10s max in the app buffer, a network hiccup of a few
seconds means:

  • The buffer drains completely
  • Audio cuts out
  • stream/end or a socket close fires
  • Reconnect takes 5 seconds minimum
  • Handshake + auth + new stream/start adds more latency
  • No resume from position — the stream restarts

No offline capability. There's no local caching of audio content. If the WebSocket drops, playback stops. Period.

Sendspin reconnect is simplistic — flat 5-second retry, not exponential backoff. No retry count limit either, so it'll keep hammering
on a dead connection.

The control WebSocket and audio WebSocket are independent. You could have the API reconnect successfully but Sendspin still be down,
or vice versa. There's no coordinated recovery.

The saving grace

This is designed as a home network app — you're streaming from your own Music Assistant server, typically on the same LAN. The
architecture makes more sense in that context:

  • Low-latency LAN = small buffers are fine
  • Stable local network = reconnection is rare
  • Server is always "nearby" = 5s reconnect is acceptable

But yes, if you take this on mobile data, through a VPN, or on flaky WiFi, the experience will be rough — frequent audio dropouts
with no graceful degradation. The just_audio URL path would fare better (ExoPlayer has its own robust buffering), but the Sendspin
PCM path has no real buffering safety net for unreliable connections.

❯ But then, this app has also Android auto support

⏺ Exactly — that's a real contradiction. In a car you're on mobile data, going through tunnels, switching cell towers. Let me check how
Android Auto playback actually works here.

⏺ Searched for 3 patterns, read 1 file (ctrl+o to expand)

⏺ You've spotted a fundamental design tension. Here's the problem:

Android Auto forces the worst-case scenario

When Android Auto triggers playback (playFromMediaId), it:

  1. Auto-switches to the builtin (local) player (line 510-518) — meaning Sendspin PCM streaming
  2. Sends play commands through the WebSocket API to Music Assistant
  3. Music Assistant streams raw PCM back through the Sendspin WebSocket

So in a car, you get the most fragile playback path (raw PCM over WebSocket with ~200ms buffer) in the least reliable network
environment (mobile data).

The specific failure modes in a car

  • Tunnel/dead zone: Both WebSockets drop. Audio dies instantly (200ms buffer). Sendspin tries to reconnect after 5 seconds — but you
    need both the control WebSocket and the Sendspin WebSocket back, independently.
  • Cell tower handoff: Brief packet loss. Even a 1-2 second gap drains the PCM buffer. You hear silence or a glitch.
  • Browsing while driving: getChildren fires a non-blocking reconnect (line 389-393) and serves cached data — this part is actually
    okay. But if cache is cold, you get empty lists.
  • API disconnected mid-playback: playFromMediaId calls checkAndReconnect() but if that fails, it just logs and returns silently. No
    user feedback on the car screen.

What would make this viable

The app would need:

  • Significantly larger buffers for the Sendspin path — 30-60 seconds instead of 10
  • Adaptive buffering that detects mobile data vs WiFi and adjusts
  • Graceful degradation — fall back to the URL-based just_audio path (which has ExoPlayer's built-in adaptive buffering) instead of
    raw PCM
  • Stream resume from position — currently a reconnect restarts the stream from scratch
  • Coordinated reconnection between the two independent WebSockets

As it stands, Android Auto on mobile data with a self-hosted server (likely accessed via VPN or port forwarding) is going to be a
pretty rough experience. It works well if your car is on your home WiFi, but that somewhat defeats the purpose.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions