await player.on_config_updated()
player.update_state()
# if the PlayerQueue was playing, restart playback
- # TODO: add property to ConfigEntry if it requires a restart of playback on change
+ # TODO: add restart_stream property to ConfigEntry and use that instead of immediate_apply
+ # to check if we need to restart playback
if not player_disabled and resume_queue and resume_queue.state == PlaybackState.PLAYING:
+ config_entries = await player.get_config_entries()
+ has_value_changes = False
+ all_immediate_apply = True
+ for key in changed_keys:
+ if not key.startswith("values/"):
+ continue # skip root values like "enabled", "name"
+ has_value_changes = True
+ actual_key = key.removeprefix("values/")
+ entry = next((e for e in config_entries if e.key == actual_key), None)
+ if entry is None or not entry.immediate_apply:
+ all_immediate_apply = False
+ break
+
+ if has_value_changes and all_immediate_apply:
+ # All changed config entries have immediate_apply=True, so no need to restart
+ # the playback
+ return
# always stop first to ensure the player uses the new config
await self.mass.player_queues.stop(resume_queue.queue_id)
self.mass.call_later(1, self.mass.player_queues.resume, resume_queue.queue_id, False)
)
)
if self._last_sent_sync_delay != current_sync_delay:
- self.mass.create_task(self._send_sendspin_server_url())
+ # Update immediately to prevent duplicate sends from concurrent events
+ self._last_sent_sync_delay = current_sync_delay
+ self.mass.create_task(self._send_sendspin_sync_delay(current_sync_delay))
async def get_config_entries(
self,
label="Sendspin sync delay (ms)",
description="Static delay in milliseconds to adjust audio synchronization. "
"Positive values delay playback, negative values advance it. "
- "Use this to compensate for device-specific audio latency.",
+ "Use this to compensate for device-specific audio latency. "
+ "Changes take effect immediately.",
required=False,
default_value=DEFAULT_SENDSPIN_SYNC_DELAY,
range=(-1000, 1000),
hidden=not sendspin_available or self.type == PlayerType.GROUP,
+ immediate_apply=True,
)
# Codec config entry (only visible when sendspin provider is available)
)
)
- # Resend if either value changed
- if (
- self._last_sent_sync_delay != current_sync_delay
- or self._last_sent_codec != current_codec
- ):
- self.logger.debug(
- "Sendspin config changed, resending (sync_delay: %s -> %s, codec: %s -> %s)",
- self._last_sent_sync_delay,
- current_sync_delay,
- self._last_sent_codec,
- current_codec,
- )
+ sync_delay_changed = self._last_sent_sync_delay != current_sync_delay
+ codec_changed = self._last_sent_codec != current_codec
+
+ if sync_delay_changed or codec_changed:
+ # Store old values for logging before updating state
+ old_codec = self._last_sent_codec
+ # Update immediately to prevent duplicate sends from concurrent events
+ self._last_sent_sync_delay = current_sync_delay
+ self._last_sent_codec = current_codec
try:
- await self._send_sendspin_server_url()
+ if codec_changed:
+ # Codec changed - need full reconnection
+ self.logger.debug(
+ "Sendspin codec changed (%s -> %s), sending full config",
+ old_codec,
+ current_codec,
+ )
+ await self._send_sendspin_server_url()
+ else:
+ # Only sync delay changed, don't reconnect, just send updated delay
+ await self._send_sendspin_sync_delay(current_sync_delay)
except Exception as err:
self.logger.warning("Failed to send updated Sendspin config to Chromecast: %s", err)
self._last_sent_sync_delay = sync_delay
self._last_sent_codec = codec
+ async def _send_sendspin_sync_delay(self, sync_delay: int) -> None:
+ """Send only the sync delay update to the Cast receiver (no reconnection)."""
+
+ def send_message() -> None:
+ self.cc.socket_client.send_app_message(
+ SENDSPIN_CAST_NAMESPACE,
+ {"syncDelay": sync_delay},
+ )
+
+ self.logger.debug(
+ "Sending Sendspin sync delay update to Cast receiver: syncDelay=%dms",
+ sync_delay,
+ )
+ await self.mass.loop.run_in_executor(None, send_message)
+ self._last_sent_sync_delay = sync_delay
+
async def _wait_for_sendspin_player(self, timeout: float = 15.0) -> Player | None:
"""Wait for the Sendspin player to connect and become available."""
start_time = time.time()