Control Yandex Music directly from your Stream Deck
Fast • Convenient • No hassle
The plugin was developed and tested on Stream Deck alternatives: Mirabox, Ajazz (AKP153) and similar devices that use the Ajazz Dock/Stream Dock application.
Original Elgato Stream Deck: the plugin launches and displays information correctly (tested on Windows 11, Elgato Stream Deck version 7.0.3). Full testing of all features hasn't been performed yet — if you find bugs, please open an issue.
The plugin works on v2 and v3 versions of the software. To download the latest v3 version:
| Device | Windows | macOS |
|---|---|---|
| Ajazz | ajazz.key123.vip/win | ajazz.key123.vip/mac |
| Mirabox / others | key123.vip/win | key123.vip/mac |
Which software to choose?
- If you have Ajazz — download from links with
ajazzin the URL - If you have Mirabox — download Stream Dock (links without
ajazz) - Stream Dock from Mirabox technically works with Ajazz too:
- On Windows — detects the device normally
- On macOS — sees the device but doesn't connect properly
This happens because Ajazz AKP153 is hardware-wise a clone of Mirabox StreamDock 293S, and the system sees it under that name.
- Play/Pause — pause and resume playback
- Next/Previous — switch tracks
- Like/Dislike — influence "My Vibe" recommendations
- Mute — mute audio without losing volume level
- Volume +/- — adjust volume by 5% per click
- Volume indicator — dedicated button showing current level
Long press: smooth adjustment on button hold is implemented in code, but may not work on some Stream Deck alternatives. This appears to be a hardware limitation — the press event only fires when the button is released. On original Stream Deck it should work fine, but hasn't been tested yet.
- Cover + title + artist — all on one button
- Scrolling text — long titles automatically scroll
- Copy to clipboard — click the cover button to copy "Artist - Track" to clipboard
- Progress bar — shows remaining time in the track
- Event-Driven architecture — instant response, zero CPU load when idle
- Update-resistant —
data-test-idselectors instead of fragile CSS classes - Standalone binary — PyInstaller, no Python or Node.js required
Get the latest release from Releases and unpack the archive.
Place the com.judd1.yandex_music.sdPlugin folder in the plugins directory.
Press Win + R, paste this path and press Enter:
%AppData%\HotSpot\StreamDock\plugins
Place the com.judd1.yandex_music.sdPlugin folder in the plugins directory.
In Finder press Cmd + Shift + G and paste:
~/Library/Application Support/HotSpot/StreamDock/plugins
⚠️ Important: Since the plugin is not signed with an Apple Developer ID, macOS puts it in quarantine. After copying, run this command in Terminal:xattr -cr ~/Library/Application\ Support/HotSpot/StreamDock/plugins/com.judd1.yandex_music.sdPluginWithout this, the plugin fail to start and show "App is damaged" error.
This is crucial. The client must be launched with the --remote-debugging-port=9222 parameter. Without it, the plugin won't be able to see the application.
Why? Yandex Music client is an Electron app under the hood (essentially a browser). The debug flag opens a port through which the plugin can "communicate" with the app and control it.
— create a special shortcut
-
Find Yandex Music in the Start menu
-
Right-click → Open file location
-
Right-click on the file → Create shortcut
-
Open shortcut properties (right-click → Properties)
-
In the Target field, add at the very end (after the closing quote, with a space):
--remote-debugging-port=9222It should look something like:
"C:\Users\...\Yandex Music.exe" --remote-debugging-port=9222 -
Click OK and pin this shortcut wherever convenient
From now on, launch music only through this shortcut. Regular launch from Start menu won't work — this is important.
— create a wrapper app
You could open Terminal every time and enter the command, but that gets old fast. Better to create a launcher app once.
- Open Script Editor — find it via Spotlight
- Paste:
do shell script "open -a '/Applications/Yandex Music.app' --args --remote-debugging-port=9222"If your app is named differently (e.g., "Яндекс Музыка.app"), adjust the path.
- File → Export...
- Name:
Yandex Music Debug(or whatever you prefer) - Where: Applications
- Format: Application
- Uncheck all checkboxes
- Save
Now a new launcher will appear in the Applications folder. Launch music through it.
The new app will have a default script icon. To restore the original Yandex Music logo:
- Find the original Yandex Music.app in Applications
Cmd + I→ click on the icon in the top-left corner of the window →Cmd + C- Find your Yandex Music Debug.app
Cmd + I→ click on the icon →Cmd + V
Done, now the launcher looks like the original and works as intended.
- Open Stream Deck application (Ajazz Dock/Stream Dock)
- Find the Yandex Music category in the actions list on the right
- Drag the buttons you need onto the panel
- Click on any button — the settings panel at the bottom will show connection status
If the status shows "Connected" — everything works. If not — check that the Yandex Music client is running through the special shortcut/launcher.
Each button's settings panel allows you to change:
| Parameter | Description |
|---|---|
| Control type | Local (PC client) or Ynison (cloud, beta) |
| Port | Connection port to the client (default 9222) |
| Button style | Appearance |
| Display elements | What to show: cover, title, artist |
⚠️ This is experimental stuff for those who like to tinker
Ynison is Yandex's internal protocol for playback synchronization between devices. In theory, it allows controlling music on your phone, Yandex.Station or TV directly from Stream Deck.
In practice, it's more complicated:
- Yandex Music PC client completely blocks control via Ynison. There's a patch that partially solves the problem, but it still doesn't work fully. When I tested, the track queue updated, next track displayed correctly, but the actual track switch on PC didn't happen.
- Mobile clients (iOS, Android) work normally — Yandex has a reference implementation there.
Essentially, this mode is a demonstration that it's technically possible. When Yandex finishes their protocol, everything will work properly. For now — it's a toy for enthusiasts.
- Start the local API server (
api_for_plugin) - Enter the authorization token in plugin settings
☁️ Ynison API Server
This is a separate FastAPI server that acts as a proxy between the plugin and the Ynison protocol. The plugin communicates with it via WebSocket and HTTP, and the server maintains the connection with Yandex.
┌─────────────────────┐ WebSocket ┌───────────────────────┐
│ Stream Deck │ ◀──────────────────▶ │ api_for_plugin │
│ Plugin │ /ws │ (FastAPI) │
└─────────────────────┘ └───────────────────────┘
│
┌──────────────────┴──────────────────┐
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────┐
│ Ynison WebSocket │ │ Yandex Music REST API │
│ wss://ynison.music. │ │ api.music.yandex.net │
│ yandex.ru │ │ (likes, metadata) │
└────────────────────────┘ └──────────────────────────┘
| File | Purpose |
|---|---|
main.py |
FastAPI application, endpoints /ws, /control/{action}, /check_token |
manager.py |
Session manager. SessionManager holds active YnisonSession for each token |
yandex_api.py |
REST client for Yandex Music: likes, dislikes, track metadata |
ynison/player.py |
Ynison player implementation: connection, commands, state processing |
ynison/client.py |
Low-level WebSocket client for Ynison |
ynison/models/ |
Pydantic models for serializing all protocol messages |
utils/auth.py |
Token and device_id storage for authentication |
WebSocket /ws
- Header
Authorization: <token> - Automatically starts Ynison session for this token on connection
- Receives real-time player state updates (JSON)
POST /control/{action}
- Actions:
play_pause,next,prev,like,dislike - Header
Authorization: Bearer <token>orAuthorization: <token> - Returns
{"status": "ok"}
GET /check_token
- Validates token
- Returns
{"valid": true}or{"valid": false}
- Multi-user mode — one server for multiple users, lazy sessions
- Metadata enrichment — Ynison only provides ID, server fetches covers and names via REST
- Like synchronization — liked/disliked lists are loaded on session start
- Fault tolerance — auto-reconnect, timeouts, graceful shutdown
- Pydantic models — entire protocol is typed and validated
cd api_for_plugin
pip install -r requirements.txt
python main.pyServer will start on http://0.0.0.0:8000
🖥️ How Local Mode Works (CDP)
Local mode uses Chrome DevTools Protocol to control the Yandex Music client directly, without third-party servers.
┌─────────────────────┐ ┌───────────────────────────────────────┐
│ │ │ Yandex Music (Electron) │
│ Stream Deck │ │ │
│ Plugin │ │ ┌───────────────────────────────┐ │
│ (Python) │ WebSocket │ │ injected_api.js │ │
│ │ ◀─────────────────▶ │ │ (injected script) │ │
│ │ ws://localhost: │ │ │ │
│ CDPMediaController │ .../devtools/page │ │ window.sdNotify() ──────▶│───│──▶ Runtime.bindingCalled
│ │ │ │ (callback) │ │
└─────────────────────┘ │ └───────────────────────────────┘ │
│ │ │
│ HTTP GET │ CDP Debug Port :9222 │
└─────────────────────────────────▶│ (--remote-debugging-port) │
/json (get WS URL) └───────────────────────────────────────┘
-
Connection:
- Plugin requests
http://localhost:9222/jsonto get WebSocket URL - Opens WebSocket connection to the page via CDP
- Calls
Runtime.addBinding("sdNotify")to register callback
- Plugin requests
-
Script injection:
- Plugin injects
injected_api.jsviaRuntime.evaluate - Script creates
window._PyYMControllerobject - Script starts observing via
MutationObserver
- Plugin injects
-
Receiving updates (event-driven):
- When player state changes, script calls
window.sdNotify(JSON) - CDP delivers this via
Runtime.bindingCalledevent - Plugin parses payload and updates UI
- When player state changes, script calls
-
Sending commands:
- Plugin calls
Runtime.evaluatewith controller method - For example:
_PyYMController.togglePlayPause() - Script finds the right button and emulates click
- Plugin calls
| File | Purpose |
|---|---|
src/core/cdp.py |
Singleton CDPMediaController: connection, RPC, event handling |
src/core/scripts/injected_api.js |
JS controller: DOM observation, delta updates, commands |
src/core/schemas/states.py |
dataclass(slots=True): MediaState, TrackData, PlaybackData — for speed |
src/core/schemas/events.py |
Pydantic models for Stream Deck events (JSON validation needed) |
src/actions/*.py |
Buttons: subscribe to events via register_observer |
data-test-idselectors — stable between Yandex Music versions- Fallback chains — if main selector not found, try alternatives
- Delta updates — only changed fields are transmitted, not entire state
- Optimistic UI — buttons update immediately on press
- Auto-reconnect — plugin reconnects automatically on connection loss
If you just want to test or poke around the code — building a binary isn't necessary. Just run the plugin via script:
- macOS:
run.sh - Windows:
run.bat
In manifest.json, specify the corresponding file in CodePathMac / CodePathWin fields.
git clone https://github.com/Judd1zzz/yandex-music-streamdeck.git
cd yandex-music-streamdeck
python -m venv env
source env/bin/activate # Windows: env\Scripts\activate
pip install -r requirements.txt
pip install pyinstaller
python tools/build.pyThe finished plugin will appear in dist/com.judd1.yandex_music.sdPlugin.
| Symptom | Solution |
|---|---|
| Buttons don't respond | Check that the client is running with --remote-debugging-port=9222 flag |
| Endless "Loading..." | Port 9222 is probably occupied by something else, or client isn't running |
| Purple icons | Restart Stream Deck |
| Long press doesn't work | Probably a limitation of your device (see "Volume" section) |
MIT. Do whatever you want.
- Yandex-Music-Ajazz-Plugin — thanks to the author for the idea of using
--remote-debugging-portto connect to the client via CDP. - YandexMusicModPatcher — patch for Ynison support on desktop client.
