Skip to content

Yandex Music: rotor session API, Wave Modes, user presets, library sync improvements#3606

Merged
MarvinSchenkel merged 82 commits intomusic-assistant:devfrom
trudenboy:upstream/yandex_music
Apr 28, 2026
Merged

Yandex Music: rotor session API, Wave Modes, user presets, library sync improvements#3606
MarvinSchenkel merged 82 commits intomusic-assistant:devfrom
trudenboy:upstream/yandex_music

Conversation

@trudenboy
Copy link
Copy Markdown
Contributor

@trudenboy trudenboy commented Apr 7, 2026

Syncs the Yandex Music provider in MA core with the out-of-tree provider repo trudenboy/ma-provider-yandex-music through v3.4.0.

The last in-tree version was pre-v3. This PR folds in several release lines worth of functional, infrastructure and UX work.

Highlights

v3.4.0 — rotor session API + Wave Modes + user presets (#118)

Rotor session API migration. My Wave, tagged stations (genre:rock / mood:calm / activity:workout / epoch:*) and track-seed similar-tracks all moved from the legacy /rotor/station/{id}/* endpoint to Yandex's newer /rotor/session/{new,tracks,feedback}. The server returns a long-lived radioSessionId on POST /rotor/session/new (together with includeWaveModel=true + interactive=true flags) that anchors all subsequent feedback, so wave state now survives across browse / recommendations / play entry points instead of losing a per-request batch_id on every entry.

Wave Modes browse folder (11 playable presets). diversity (Discover / Favorites / Popular), moodEnergy (Calm / Active / Fun / Sad) and language (Russian / Non-Russian / Without Words). Each is a separate playable BrowseFolder that creates a rotor session with the matching seed setting. Composite item_ids encode the preset, so rotor feedback lands on the right session.

User-defined wave presets — advanced settings expose a small builder: name + three dropdowns + Save / Delete action buttons. Presets are persisted in a hidden JSON config key and surface as playable folders under Radio → My Presets. Unlimited count, no hand-editing of JSON.

Rotor likes / prefetch. library_add on a wave track also fires a rotor like event on the active session (so this session adapts, not just the next one). on_played kicks off a background prefetch of the next wave batch into wave.prefetched so MA's DSTM refill (if the user enables it) gets Yandex-curated tracks without an extra round-trip. The provider does not toggle DSTM — that stays a user decision.

is_dynamic on autoplaylists. Feed-generated playlists (Playlist of the Day / DejaVu / Premiere / Missed Likes) now carry is_dynamic=True so MA doesn't long-cache their content — Yandex regenerates them on a schedule.

Browse fixes that landed in the same release.

  • Collection child paths are nested (<prov>://collection/tracks) so the back button returns to the listing instead of the provider root; dispatcher still delegates collection/<sub> to core's default library handler.
  • Wave Modes / My Presets back-navigation — same dual-form path contract (item_id underscore form so play-time reconstruction works, path slash form so back-nav works).
  • Listening History was empty because MarshalX stopped populating full_model — switched to collecting track_ids from item_id and batch-hydrating via get_tracks. 42 tracks returned where before 0.
  • AI Wave Sets / Featured Waves rendered empty sub-folders because the payload uses camelCase (stationId, compactImageUrl) while the code asked for snake_case. Both forms now accepted.

v3.3.1 — search fix

fix(search): apply limit per bucket after classify_album (#116). Album/audiobook split now respects the requested limit per media-type bucket, not per raw API page.

v3.3.0 — audiobook progress sync & discovery (#115)

  • Progress sync to Yandex. on_played / on_streamed push chapter position via a new play_audio API wrapper so the user's Yandex apps stay in sync with MA's resume point. parse_audiobook propagates album.listening_finishedAudiobook.fully_played.
  • Audiobook search. MediaType.AUDIOBOOK maps to Yandex album search; results split by classify_album into albums / audiobooks. Dedup so ALBUM + AUDIOBOOK issues a single API call.
  • Collection browse. My Audiobooks / My Podcasts sub-folders (RU + EN) routed through base MusicProvider.browse → existing get_library_audiobooks / get_library_podcasts generators.
  • Robustness. Advisory progress path swallows any non-CancelledError failure from chapter-map resolution; _audiobook_progress_point helper handles EOF (reports end of last chapter, not start) and clamps track_length_seconds ≥ 1 / offset into [0, track_length]. Caches (_audiobook_chapter_cache, _audiobook_play_ids) cleared on on_streamed and unload.

v3.2.x — audiobook streaming

  • fix(audiobook): wire seek through can_seek and bound chapter failures (v3.2.1).
  • feat(audiobook): stream audiobooks via chapter concatenation (v3.2.0) — CUSTOM StreamType iterating per-chapter byte streams with seek translation + failure tolerance.

v3.1.x — library classifier + auth fixes

  • Podcast + audiobook classifier (Phase 1): liked albums routed to music / podcast / audiobook via meta_type / type.
  • Short-lived _get_liked_albums_cached dedupes the three library syncs.
  • fix(auth) series: clear stale refresh_token on QR re-auth, wrap transient Passport errors, don't clear creds on transient failures.

v3.0.x — yandex-music v3 migration

  • yandex-music[async] v2 → v3.0.0, parsers updated for stricter typing.
  • Device Flow auth added alongside QR; auto-refresh via ya-passport-auth v1.3.0.
  • /get-file-info unified for all quality tiers with native byte-range seek.
  • Pinned items + Listening History browse folders.

Dependencies

  • yandex-music==3.0.0
  • ya-passport-auth==1.3.0

Documentation

Docs PR adding the v3.4 feature coverage to music-assistant.io (Wave Modes / My Presets / Device Flow auth, podcast & audiobook support, Known Issues note about Listening History): music-assistant/music-assistant.io#629.

Review history on the upstream repo

PR #118 went through three rounds of Copilot review that caught:

  • rotor session request swallowing NetworkError before _call_with_retry could see it (transient retry never fired);
  • _fetch_similar_tracks_for_seed creating an unbounded _wave_states entry per seed (cached, but stateful) — now stateless;
  • _prefetch_rotor_session mutating shared state between lock phases — now direct rotor_session_tracks without going through the session helper;
  • locking gaps in _get_my_wave_recommendations, get_rotor_station_tracks, _prefetch_rotor_session;
  • duplicated JSON preset parser in two files — extracted to provider/presets.py.

All 14 review threads resolved before merge. Also addressed Marvin Schenkel's review on #3234 about providers not toggling queue UX settings on the user's behalf — _ensure_dont_stop_the_music / queue.radio_source writes were removed.

Test plan

  • Provider unit tests green — 254 passed covering rotor session plumbing, wave state machine, preset routing, prefetch, rotor feedback, browse listings, audiobook progress, search routing, auth, parsers.
  • ruff / mypy clean.
  • Live validation against a real Yandex account in Docker — session API round-trips verified for user:onyourwave / track:{id} / genre:rock / mood:calm / activity:workout / epoch:eighties; feedback + pagination; browse back-nav; history hydration.
  • Core-side: provider loads; library sync still works; My Wave plays; Wave Modes / My Presets render and play; audiobook position round-trips with Yandex mobile app; search returns audiobooks under MediaType.AUDIOBOOK.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 7, 2026

🔒 Dependency Security Report

📦 Modified Dependencies

music_assistant/providers/yandex_music/manifest.json

Added:

Removed:


🔍 Vulnerability Scan Results

No known vulnerabilities found

Name Skip Reason
torch Dependency not found on PyPI and could not be audited: torch (2.11.0+cpu)
torchaudio Dependency not found on PyPI and could not be audited: torchaudio (2.11.0+cpu)
✅ No known vulnerabilities found

Automated Security Checks

  • Vulnerability Scan: Passed - No known vulnerabilities
  • Trusted Sources: All packages have verified source repositories
  • Typosquatting Check: No suspicious package names detected
  • License Compatibility: All licenses are OSI-approved and compatible
  • Supply Chain Risk: Passed - packages appear mature and maintained

Manual Review

Maintainer approval required:

  • I have reviewed the changes above and approve these dependency updates

To approve: Comment /approve-dependencies or manually add the dependencies-reviewed label.

@trudenboy trudenboy changed the title feat(yandex_music): add yandex_music provider v2.7.2 feat(yandex_music): add QR Auth by default Apr 7, 2026
@trudenboy trudenboy force-pushed the upstream/yandex_music branch from e858df6 to 06b99c4 Compare April 7, 2026 14:04
@trudenboy trudenboy changed the title feat(yandex_music): add QR Auth by default feat(yandex_music): add QR authentication and token auto-refresh Apr 7, 2026
@trudenboy trudenboy marked this pull request as ready for review April 7, 2026 14:10
Copilot AI review requested due to automatic review settings April 7, 2026 14:10
Copy link
Copy Markdown
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

This PR adds a QR-based authentication flow to the Yandex Music provider and introduces startup-time token validation/refresh using a stored session token (x_token), aiming to reduce manual re-authentication and improve resilience to token expiry.

Changes:

  • Added a new yandex_auth.py module implementing Yandex Passport QR login and x_tokenmusic_token exchange/refresh.
  • Updated provider initialization to attempt music-token login first, then refresh from x_token when needed, clearing invalid credentials appropriately.
  • Adjusted streaming range-window EOF detection logic to account for AES block alignment re-downloads.

Reviewed changes

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

Show a summary per file
File Description
music_assistant/providers/yandex_music/yandex_auth.py Implements QR auth flow and token refresh helper(s) using a dedicated aiohttp session/cookie jar.
music_assistant/providers/yandex_music/__init__.py Adds QR login action, “remember session” toggle, and hidden storage for x_token in config flow.
music_assistant/providers/yandex_music/provider.py Adds cascading token validation and refresh-from-x_token logic during handle_async_init().
music_assistant/providers/yandex_music/streaming.py Fixes premature EOF detection by correcting received byte accounting when reconnecting off AES boundaries.
music_assistant/providers/yandex_music/constants.py Adds new config/action keys for QR auth and session token persistence.
music_assistant/providers/yandex_music/api_client.py Reduces risk of credential leakage by removing exc_info=True in catch-all logging.

Comment thread music_assistant/providers/yandex_music/__init__.py
Comment thread music_assistant/providers/yandex_music/__init__.py
Comment thread music_assistant/providers/yandex_music/provider.py
Comment thread music_assistant/providers/yandex_music/yandex_auth.py Outdated
Add Yandex Passport QR code authentication as the primary login method,
replacing manual token entry (kept as advanced fallback).

- QR auth flow via YandexQRAuth class with dedicated aiohttp session
- Cascading token validation at startup: music_token → x_token refresh → clear
- User-configurable "Remember session" toggle for x_token storage
- Mobile User-Agent for HAOS compatibility with Yandex Passport
- Multi-pattern CSRF extraction for different Yandex page formats
- Remove exc_info=True logging that could leak token values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@trudenboy trudenboy force-pushed the upstream/yandex_music branch from 06b99c4 to 92a6c86 Compare April 7, 2026 14:26
@trudenboy trudenboy changed the title feat(yandex_music): add QR authentication and token auto-refresh Add QR authentication and token auto-refresh to Yandex Music provider Apr 7, 2026
Copilot AI review requested due to automatic review settings April 7, 2026 14:29
Copy link
Copy Markdown
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 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread music_assistant/providers/yandex_music/__init__.py Outdated
@MarvinSchenkel
Copy link
Copy Markdown
Contributor

I see this code is now being duplicated across 3-4 providers. Would it be an idea to create a (QR) Auth package that can be reused?

Copilot AI review requested due to automatic review settings April 9, 2026 11:03
Copy link
Copy Markdown
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 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread music_assistant/providers/yandex_music/streaming.py
Comment thread music_assistant/providers/yandex_music/api_client.py
@trudenboy
Copy link
Copy Markdown
Contributor Author

I see this code is now being duplicated across 3-4 providers. Would it be an idea to create a (QR) Auth package that can be reused?

Good idea, I was actually thinking about the same thing. Here's the plan:

Extract music_assistant/helpers/yandex_auth.py with:

  • YandexQRAuth class (QR session + polling + token exchange)
  • perform_qr_auth() — full flow with AuthenticationHelper
  • refresh_music_token()
  • login_with_cookies() (used by yandex_station)

All 3 Yandex providers (yandex_music, yandex_station, yandex_ynison) would import from there. The QR auth button config entries are also duplicated — could extract a shared helper for those as well.

Would this approach work, or do you have a different structure in mind?

@MarvinSchenkel
Copy link
Copy Markdown
Contributor

I see this code is now being duplicated across 3-4 providers. Would it be an idea to create a (QR) Auth package that can be reused?

Good idea, I was actually thinking about the same thing. Here's the plan:

Extract music_assistant/helpers/yandex_auth.py with:

  • YandexQRAuth class (QR session + polling + token exchange)
  • perform_qr_auth() — full flow with AuthenticationHelper
  • refresh_music_token()
  • login_with_cookies() (used by yandex_station)

All 3 Yandex providers (yandex_music, yandex_station, yandex_ynison) would import from there. The QR auth button config entries are also duplicated — could extract a shared helper for those as well.

Would this approach work, or do you have a different structure in mind?

You probably know this better than I do. Another solution would be to create a Yandex pip package that includes more than just the QR Auth, so we can also get rid of the duplicate logic between the KION / Yandex providers?

What would be a convenient point of extracting logic into a separate package for reuse?

@trudenboy
Copy link
Copy Markdown
Contributor Author

I see this code is now being duplicated across 3-4 providers. Would it be an idea to create a (QR) Auth package that can be reused?

I see this code is now being duplicated across 3-4 providers. Would it be an idea to create a (QR) Auth package that can be reused?

Good idea, I was actually thinking about the same thing. Here's the plan:
Extract music_assistant/helpers/yandex_auth.py with:

  • YandexQRAuth class (QR session + polling + token exchange)
  • perform_qr_auth() — full flow with AuthenticationHelper
  • refresh_music_token()
  • login_with_cookies() (used by yandex_station)

All 3 Yandex providers (yandex_music, yandex_station, yandex_ynison) would import from there. The QR auth button config entries are also duplicated — could extract a shared helper for those as well.
Would this approach work, or do you have a different structure in mind?

You probably know this better than I do. Another solution would be to create a Yandex pip package that includes more than just the QR Auth, so we can also get rid of the duplicate logic between the KION / Yandex providers?

What would be a convenient point of extracting logic into a separate package for reuse?

Good point about a separate package.
I second thought, adding a brand-specific helper directly into MA core isn't the right approach either.
Before going that route though, I think the most logical first step would be to contribute QR auth upstream to yandex-music-api - it's already a dependency for Yandex providers, and the QR flow essentially produces tokens for that very library.
If that doesn't work out (scope mismatch, maintainer preferences, etc.), we can revisit the separate pip package idea.

Copilot AI review requested due to automatic review settings April 9, 2026 11:53
Copy link
Copy Markdown
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 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread music_assistant/providers/yandex_music/streaming.py
Copy link
Copy Markdown
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 23 out of 24 changed files in this pull request and generated no new comments.

@MarvinSchenkel
Copy link
Copy Markdown
Contributor

MarvinSchenkel commented Apr 23, 2026

Could you please fix all the lint / test issue? Please mark as ready when you are done and want me to have another look?

Copy link
Copy Markdown
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 23 out of 24 changed files in this pull request and generated 1 comment.

Comment thread music_assistant/providers/yandex_music/presets.py Outdated
Copy link
Copy Markdown
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 24 out of 25 changed files in this pull request and generated 1 comment.

Comment thread music_assistant/providers/yandex_music/api_client.py
Removed autogenerated warning and updated package list.
trudenboy and others added 3 commits April 23, 2026 18:01
…all.py

Previous commits on this branch stripped the file to 1 line while
addressing the dependency-approval bot, which broke CI — tests fail
with ModuleNotFoundError: No module named 'hass_client' at conftest
import, because hass_client is a core MA dependency shipped via this
file (it is not manually removable without breaking the whole suite).

Regenerated from pyproject.toml + provider manifests using the
canonical scripts/gen_requirements_all.py. yandex-music==3.0.0 and
ya-passport-auth==1.3.0 are now correctly included alongside the rest
of the core dependencies — they are consumed by
music_assistant.providers.yandex_music and must be installable.
Copy link
Copy Markdown
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 24 out of 25 changed files in this pull request and generated 2 comments.

Comment thread requirements_all.txt
Comment thread music_assistant/providers/yandex_music/streaming.py
github-actions Bot and others added 13 commits April 23, 2026 15:36
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…istic output

os.listdir order depends on filesystem (inode order on Linux ext4 vs
alphabetic on macOS APFS), making requirements_all.txt regeneration
non-deterministic across platforms. When two providers declare
different versions of the same package, the order decided which one
wins — leading to CI lint failures that flipped between runs.

Wrapping the outer listdir in sorted() makes the result stable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…generation

Temporary alignment until music-assistant#3804 (sorted gen_requirements_all) lands and
makes the choice deterministic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ain)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies-reviewed Indication that any added or modified/updated dependencies on a PR have been reviewed enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants