Skip to content

Preserve Sendspin players queue across reconnects#3340

Draft
maximmaxim345 wants to merge 4 commits intodevfrom
fix/queue-loss-2
Draft

Preserve Sendspin players queue across reconnects#3340
maximmaxim345 wants to merge 4 commits intodevfrom
fix/queue-loss-2

Conversation

@maximmaxim345
Copy link
Member

@maximmaxim345 maximmaxim345 commented Mar 9, 2026

Summary

Do not unregister offline Sendspin players to keep the queue in memory.

With this PR, Sendspin players now remain registered and are only marked as unavailable, preserving the queue.

Description

The Sendspin provider was previously calling unregister whenever a player/client went offline.
This had one major drawback: unregistering a player also deleted that player's queue.
While merged players created a Universal player, which solved this issue for most clients (since they have the type PROTOCOL), Sendspin-only clients are of type PLAYER.
This means that the clients most susceptible to network interruptions are also the ones whose queue gets cleared most often: the Web Player and Mobile Apps.

Alternative Solutions

Alternatively, we could keep Sendspin using unregister for offline players and instead preserve queues after calling PlayerController.unregister(permanent=False).
This would also fix the issue for other providers that unregister players, but it might introduce unexpected issues with dangling queues.

Copilot AI review requested due to automatic review settings March 9, 2026 09:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Keeps Sendspin players registered when clients disconnect so their in-memory queue survives transient network drops, and adds reconnection handling to rebind an existing SendspinPlayer to a new Sendspin client instance.

Changes:

  • Replace Sendspin disconnect handling from unregister() to “mark unavailable” to preserve queues across reconnects.
  • Add reconnect/reattach flow in SendspinPlayer (unsubscribe/resubscribe callbacks, refresh client info, resync group state/membership).
  • Add per-client event versioning in the provider to avoid add/remove race conditions on fast reconnects.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
music_assistant/providers/sendspin/provider.py Tracks client event versions, reattaches existing players on reconnect, and marks players unavailable instead of unregistering on disconnect; unregisters players on provider unload.
music_assistant/providers/sendspin/player.py Adds callback subscribe/unsubscribe helpers, implements mark_unavailable + reattach_client, and refreshes device/client info on reconnect.

Comment on lines +229 to +234
# Add player_id as MAC identifier for protocol linking (if it's a valid MAC)
# This enables linking with bridged players (e.g., AirPlay via Sendspin bridge)
if _mac := mac_from_bridge_client_id(player_id):
if _mac := mac_from_bridge_client_id(self.player_id):
self._attr_device_info.add_identifier(IdentifierType.MAC_ADDRESS, _mac)
elif is_valid_mac_address(player_id):
self._attr_device_info.add_identifier(IdentifierType.MAC_ADDRESS, player_id)
elif is_valid_mac_address(self.player_id):
self._attr_device_info.add_identifier(IdentifierType.MAC_ADDRESS, self.player_id)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_refresh_client_info re-adds preserved identifiers and then unconditionally calls add_identifier(IdentifierType.MAC_ADDRESS, ...) based on self.player_id, which can overwrite an ARP-enriched/cached MAC already present in preserved_identifiers (used for protocol-linking/universal-player matching). Prefer only setting the MAC from player_id when no MAC is already present, or only when the derived MAC is more reliable than the preserved value.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 9, 2026 10:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment on lines +472 to 476
if protocol_info.model:
device_info.model = protocol_info.model

# Update manufacturer if universal player has generic value
if device_info.manufacturer in (None, "Music Assistant") and protocol_info.manufacturer:
if protocol_info.manufacturer:
device_info.manufacturer = protocol_info.manufacturer
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_update_universal_device_info now overwrites the universal player’s model/manufacturer whenever the protocol player provides any truthy value, which can regress good device info to generic placeholders (e.g. Sendspin falls back to "Unknown model" / "Unknown Manufacturer" in SendspinPlayer._refresh_client_info). Consider only updating when the universal values are still defaults ("Universal Player"/"Music Assistant") or when the incoming values are known-non-generic, to avoid incorrect metadata and unnecessary config writes.

Copilot uses AI. Check for mistakes.
Comment on lines +1376 to +1379
# Re-evaluate protocol links on protocol player updates so parent wrappers
# can refresh metadata (e.g. reconnect with updated device info).
if player.state.type == PlayerType.PROTOCOL:
self._evaluate_protocol_links(player)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

register_or_update now triggers protocol-link evaluation on updates of PROTOCOL players; please add/adjust tests (see tests/core/test_protocol_linking.py) to cover the reconnect/update path and assert it doesn’t create duplicate universal players or mutate existing links unexpectedly.

Copilot generated this review using guidance from repository custom instructions.
@marcelveldt-traveling
Copy link

There seems to be a number of unrelated commits in the PR - was that intentional ?
If so, please update the PR description to match what was changed.

I have these 2 comments:

  1. in general, a Queue should always be restored when a player reconnects again, this is done in the on_player_register callback from Queue controller, where it restores the queue from cache. If that is not the case, then we have some other challenge to worry about or we accidentally permanently remove the player from the player controller ?

  2. By only setting the available flag, it keeps a player forever loaded in the player controller, even if a player is indeed unavailable for a longer period of time. Would it make sense to schedule a unregister after e.g. 120 seconds ? So player goes offline --> mark unavailable --> it returns within 120 seconds --> mark available again. If it doesn't return within 120 seconds, unregister from player controller.

@MarvinSchenkel
Copy link
Contributor

Related issue: music-assistant/support#5049

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants