Skip to content

feat: add connection lifecycle events and auto-resume for Sendspin players#4719

Draft
foobarth wants to merge 4 commits into
music-assistant:devfrom
foobarth:pr/connection-resilience
Draft

feat: add connection lifecycle events and auto-resume for Sendspin players#4719
foobarth wants to merge 4 commits into
music-assistant:devfrom
foobarth:pr/connection-resilience

Conversation

@foobarth

@foobarth foobarth commented Jul 10, 2026

Copy link
Copy Markdown

What does this implement/fix?

Problem

When an iPhone (or other mobile Sendspin client) loses its network
connection (tunnel dropout, airplane mode, elevator, WiFi handoff),
the MA server detects the WebSocket disconnect and stops playback
immediately. When the client reconnects within the cleanup window
(300s, up from upstream's 30s), the player is already in "stopped"
state, the user has to manually press play.

For unintended drops shorter than 5 minutes there is no reason to force a
manual resume; the server should recognize the reconnect and
automatically continue playback, if it was playing before the dropout
and the disconnect reason was clearly an unintended network issue.

In the area where i live, 30s is sometimes just not enough and having
to grab the phone while driving, opening the app and restarting playback
is both dangerous and unexpected from a "proper" music player these days.

Solution

Two layers of changes. This PR (MA server) and a companion PR on
aiosendspin (Sendspin/aiosendspin#…):

Sendspin/aiosendspin companion PR#288

  • ClientConnectedEvent, ClientDisconnectedEvent fire on every
    connection establish / teardown (not just first-add)
  • ClientReconnectedEvent fires when a known client reconnects
    with unchanged hello payload (gap in upstream: ClientUpdatedEvent
    only fires on hello change, so 99% of reconnects were silent)
  • GoodbyeReason.CONNECTION_LOST distinguishes unexpected network
    drops from intentional shutdowns
  • CLIENT_CLEANUP_DELAY 30s → 300s gives mobile clients a longer
    window to reconnect before being evicted from the registry

This PR (MA server)

New event handlers in Sendspin provider:

  • _handle_client_disconnected immediately captures _was_playing
    state and marks player as unavailable for the UI, instead of
    waiting 300s for ClientRemovedEvent
  • _handle_client_connected refreshes player info on every
    transport connect (existing player or fresh reconnect)
  • _handle_client_reconnected triggers auto-resume via the
    existing _auto_resume_after_reconnect() in player.py

Player enhancements:

  • _was_playing flag survives across WS disconnects; set True on
    play_media(), cleared only after successful resume()
  • disconnect_behaviour set to UNGROUP (keep player alive during
    drop, avoid cancelling the session)
  • Guard _on_group_stopped to not cancel the playback session
    during the reconnect window (race condition with aiosendspin's
    automatic ControllerStopEvent after reconnect)
  • GoodbyeReason.CONNECTION_LOST is handled as a warm disconnect
    (roles preserved, cleanup scheduled with delay)

Behaviour

Drop duration Before After
<= 300 s Playback stops, manual resume needed Auto-resume on reconnect
> 300 s Client evicted, manual start needed Manual play (expected — session timed out)

Testing

Live-tested on K8s cluster with custom CI image. Scenarios:

  • 3 minute airplane mode -> auto-resume within seconds of reconnect
  • 1 minute tunnel dropout -> same
  • 5+ minute dropout -> client reconnects but stays paused (expected)

Companion PR

Sendspin/aiosendspin PR#288 must be merged first (or simultaneously)
since the MA code depends on the new events and GoodbyeReason.

Related issue (if applicable):

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • New music/player/metadata/plugin provider — new-provider
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — documentation
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Checklist

  • The code change is tested and works locally.
  • pre-commit run --all-files passes.
  • pytest passes, and tests have been added/updated under tests/ where applicable.
  • For changes to shared models, the companion PR in music-assistant/models is linked.
  • For changes affecting the UI, the companion PR in music-assistant/frontend is linked.
  • I have read and complied with the project's AI Policy for any AI-assisted contributions.
  • I have raised a PR against the documentation repository targeting the main or beta branch as appropriate.

…dspin players

Add event handlers for ClientConnectedEvent, ClientDisconnectedEvent,
and ClientReconnectedEvent in the Sendspin provider:
- _handle_client_connected: refresh player info on every transport connect
- _handle_client_disconnected: immediately capture _was_playing state on
  disconnect (CONNECTION_LOST), mark player unavailable for UI
- _handle_client_reconnected: triggers auto-resume via existing
  _auto_resume_after_reconnect() in player.py

Player enhancements:
- _was_playing flag tracks playback state across WS disconnects
- disconnect_behaviour set to UNGROUP (keep player alive on drop)
- Auto-resume via resume() when client reconnects after network drop
- Guard _on_group_stopped to not cancel session during reconnect window
- GoodbyeReason.CONNECTION_LOST distinguishes network errors from
  intentional disconnects

Tests:
- Unit tests for _was_playing lifecycle, guard logic, auto-resume flow
- Integration flow tests for short and long reconnect scenarios
@foobarth
foobarth force-pushed the pr/connection-resilience branch 2 times, most recently from 1ab745a to 4609f0b Compare July 10, 2026 21:03
@foobarth
foobarth marked this pull request as ready for review July 10, 2026 21:04
@foobarth
foobarth force-pushed the pr/connection-resilience branch from 4609f0b to 1ab745a Compare July 11, 2026 07:14
@foobarth

Copy link
Copy Markdown
Author

Lint / test issues are expected due to the required companion PR for Sendspin/aiosendspin#288

@OzGav

OzGav commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Keeping this as draft until the tests can pass.

@OzGav
OzGav marked this pull request as draft July 12, 2026 11:24
@OzGav
OzGav requested a review from maximmaxim345 July 12, 2026 11:25
bofh and others added 2 commits July 20, 2026 17:01
ClientReconnectedEvent was removed from aiosendspin. The auto-resume
mechanism already uses _was_playing as the guard, so switching to
ClientConnectedEvent is transparent — the first connect has _was_playing
=False (no resume), every reconnect has the correct state.

- Remove ClientReconnectedEvent import and handler case
- Remove _handle_client_reconnected method (its logic is already
  covered by _handle_client_connected + _refresh_client_info)
- Update test comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants