Bump yandex_ynison to v2.2.9: handoff state-sync FSM and opt-in stream-mode UI integration#3856
Bump yandex_ynison to v2.2.9: handoff state-sync FSM and opt-in stream-mode UI integration#3856trudenboy wants to merge 14 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the yandex_ynison provider to upstream v2.2.0, bringing in a handoff-mode FSM rewrite, improved echo/reconnect handling in the Ynison client, and an opt-in stream-mode UI integration that publishes a frontend-only “fake queue” for richer UI rendering.
Changes:
- Reworks echo detection and reconnect behavior in
YnisonClient(AND-based author checks + post-reconnect settle window + session params update). - Adds a handoff playback mode with explicit FSM bookkeeping, heartbeat progress reporting, and idempotency/REPLACE handling.
- Adds opt-in stream-mode UI integration (fake queue events + output format stamping) and format prefetch/hinting to improve output format accuracy.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
music_assistant/providers/yandex_ynison/__init__.py |
Adds playback mode selection and UI integration / handoff heartbeat config entries; features vary by mode. |
music_assistant/providers/yandex_ynison/constants.py |
Introduces new config keys and handoff tuning constants. |
music_assistant/providers/yandex_ynison/protocols.py |
Extends the Yandex Music provider protocol with instance_id for instance-scoped URIs. |
music_assistant/providers/yandex_ynison/provider.py |
Implements handoff FSM + heartbeat, stream-mode UI integration (fake queue), and format prefetch/hints. |
music_assistant/providers/yandex_ynison/streaming.py |
Adjusts default lossless PCM params to 44.1kHz. |
music_assistant/providers/yandex_ynison/ynison_client.py |
Adds reconnect settle logic, session param updates, and AND-based echo classification. |
tests/providers/yandex_ynison/test_provider.py |
Updates/extends provider tests for prefetch + normalized format behavior. |
tests/providers/yandex_ynison/test_provider_handoff.py |
New comprehensive test suite for handoff mode FSM/heartbeat/idempotency behavior. |
tests/providers/yandex_ynison/test_streaming.py |
Updates streaming tests for 44.1kHz lossless defaults. |
tests/providers/yandex_ynison/test_ynison_client.py |
Updates Ynison client tests for new echo + reconnect semantics. |
|
Could you please resolve the conflicts / copilot comments. Will have a look after that |
@MarvinSchenkel Done |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
music_assistant/providers/yandex_ynison/provider.py:1386
- [PROBLEM]
QUEUE_TIME_UPDATEDis emitted withobject_id=self.instance_id, which can be filtered out by the websocket server for users withplayer_filterrestrictions (it expects player_ids as object_id for QUEUE_* events). To keep the seek bar/progress updating for restricted users, consider usingobject_id=player_id(and keep the fake queue id in the payload) or another approach that survives the server-side filter.
if self._ui_integration_active:
self.mass.signal_event(
EventType.QUEUE_TIME_UPDATED,
object_id=self.instance_id,
data=elapsed_ms // 1000,
)
Summary
Bump
yandex_ynisonplugin provider to v2.2.9. The diff againstdevintroduces a real two-way state-sync layer for the experimentalhandoffplayback mode and adds an opt-in stream-mode UI integration so MA's player card renders fully while aPluginSourceis active.Source: trudenboy/ma-provider-yandex-ynison@v2.2.9.
Highlights
Handoff state-sync
Two-way sync between the Yandex Music app and MA's player queue is driven by an explicit
HandoffPhaseFSM (IDLE/ACTIVATING/PLAYING/PAUSED/ENDING)._handoff_activate→_apply_track_change/_apply_idle_resume/_apply_same_track_sync._cancel_pending_play_media()cancels the in-flightplay_mediatask before issuing a new one. The task is created viaself.mass.create_task(...)so it is tracked by the MusicAssistant instance and auto-cancelled on stop/reload.cmd_pause/cmd_play(avoids the queue's_watch_pausewatchdog dropping to IDLE on long pauses). The same-URI-pausedcmd_playresume snapshots_drift_suppress_until/_expected_phasebefore the await and rolls them back onException, symmetric with the REPLACE-issuing branches; a failed resume no longer leaves the session stuck inACTIVATING.play_media(REPLACE) + seekwrapped incmd_pause/seek/cmd_play(avoids the audible 0-then-jump)._prefetch_format_for_trackruns in the background (mass.create_task(...)) on track change instead of blockingplay_media— the prefetch is cosmetic in handoff (only primes MA's stream-detail cache;_normalized_formatis never used here), so the up-to-2.5 s latency per track change is gone.PLAYING → IDLEqueue-state transition, gated on_expected_phase == PLAYINGand_is_at_natural_end_of_track. Heartbeat-side polling (≤ 5 s) as a fallback when MA's event bus drops the transition.queue.version.device_idandstatus.version.device_id. Lamport-style watermarks are intentionally not used — Ynison documentsversion.versionasrandom(int64)and re-stamps it._connect_statealways sends a fresh initial state._classify_drift(queue-rebuild guard, prevents stream mode from treating a Ynisonprogress=0echo as a seek),_pick_resume_position._handoff_pauseechoespaused=Trueto Ynison immediately and bumps the heartbeat watermark so the next tick doesn't race the user's pause.Stream-mode MA UI integration (opt-in)
New advanced config —
enable_ui_integration(default off) — publishes a frontend-only fake queue under the provider'sinstance_id, so MA's UI renders the seek bar, signal-chain panel, and quality indicator while aPluginSourceis active._register_plugin_queuefiresQUEUE_ADDED/QUEUE_UPDATEDevents without touchingplayer_queues._queues— backend command routing stays on thePluginSourcecallbacks._set_player_output_formatstamps a realAudioFormatinstance (not a plain dict) onplayer.extra_data["output_format"], matching MA core's contract (controllers/streams/audio.py:get_player_filter_paramswritesAudioFormat;DSPDetails.output_format: AudioFormat | None). Cleared inunload()and_clear_active_player._signal_seek_to_frontendsnaps the frontend bar via a publicQUEUE_TIME_UPDATEDevent (no private-attribute writes).statederived from the liveYnisonState.is_pausedflag so pause / resume reflect immediately.controllers/webserver/websocket_client.py:444-454) dropsQUEUE_*events whoseevent.object_idis not in the user'splayer_filter. Our events useinstance_id(matchingplayer.active_sourcefor the frontend's queue lookup), so users with a restrictedplayer_filtersee an empty player card. Switchingobject_idtoplayer_idwould pass the filter but break the lookup for everyone. A clean fix needs an MA-core carve-out for plugin source ids (mirroring the existing_sendspin_player_idexception) or a frontend change to routeQUEUE_*bydata.queue_id— both out of scope for this PR. Admin / unrestricted users are unaffected; the integration is opt-in.Stream-mode reconnect resilience
_send_progress_to_ynisonshort-circuits whenpaused=Falseand the server-side queue is empty (current_track_id is None). Without this, every 5 s_sync_progresstick after a reconnect that lands on a stale empty-queue state firedupdate_playing_status(paused=False), which Ynison rejects with error 400030001 ("Player is not paused, but queue is probably empty") and closes the WebSocket — ping-ponging through reconnects (sometimes escalating into a 300100002 rebalance loop) for minutes. Observed live and root-caused.paused=Trueis still sent (one-sided guard; the server accepts that combination).Config UX
playback_modeandDevice name in Yandex Music(publish_name) moved out of the advanced section.enable_ui_integration(hidden in handoff mode).Stats
10 files changed, ≈3000 insertions(+), ≈90 deletions(-)(vsdev). One new test file:tests/providers/yandex_ynison/test_provider_handoff.py(≈1000 lines). 285 unit tests,ruffandmypyclean on the source repo.Test plan
playback_mode: streamandplayback_mode: handoff: track change, pause / resume (short and long), next / prev, seek (forward and backward), mid-track handoff from the Yandex Music app, natural-end auto-advance.CI sync
Published via
trudenboy/ma-provider-tools/reusable-sync-to-fork.yml(targetupstream/yandex_ynison).