feat(mpris): pin preferred media source - #3150
Conversation
Add a minimal "pin media source" option so the bar media widget and OSD keep showing a preferred player (e.g. a music player) even when another source (e.g. YouTube in the browser) starts playing. - MprisController: new pinnedIdentity property + setPinned(identity); _bestPlayingPlayer() prefers a playing pinned player (exact identity match, then case-insensitive substring fallback). Existing behavior is unchanged when no pin is set. - Persistence via SessionData.pinnedPlayerIdentity (spec entry added so SessionData.set() accepts the key); empty string clears the pin. - MediaDropdownOverlay (Media Players panel): each player row gets a theme-colored circle indicator on the right. Empty circle = no pin (initial state). Right-click a row to make it the default source (circle fills); right-click another row to move the pin; right-click the pinned row again to clear (back to empty). Left-click behavior unchanged (selects the player).
|
/claude review |
Claude reviewOne minor issue; the feature itself looks sound and existing selection behavior is preserved when no pin is set.
Checked: pin resolution order in |
Concur with Claude's suggestion. Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
|
/claude review |
| if (pinnedIdentity !== "") { | ||
| const pinLower = pinnedIdentity.toLowerCase(); | ||
| const pinned = playing.find(player => player.identity === pinnedIdentity) ?? playing.find(player => (player.identity || "").toLowerCase().includes(pinLower)); | ||
| if (pinned) | ||
| return pinned; | ||
| } |
There was a problem hiding this comment.
The pin branch returns before every canControl check the rest of this function is built around, so a pinned player that is playing but not controllable becomes activePlayer and the transport controls go dead — the existing code deliberately swaps to a same-track controllable mirror in that case (line 263).
This is reachable because identity is not unique: browsers register one MPRIS service per tab under the same identity ("Chromium", "Mozilla Firefox"), and find takes whichever comes first, which may be the uncontrollable instance.
Prefer a controllable match before falling back:
| if (pinnedIdentity !== "") { | |
| const pinLower = pinnedIdentity.toLowerCase(); | |
| const pinned = playing.find(player => player.identity === pinnedIdentity) ?? playing.find(player => (player.identity || "").toLowerCase().includes(pinLower)); | |
| if (pinned) | |
| return pinned; | |
| } | |
| if (pinnedIdentity !== "") { | |
| const pinLower = pinnedIdentity.toLowerCase(); | |
| const exact = playing.filter(player => player.identity === pinnedIdentity); | |
| const matches = exact.length > 0 ? exact : playing.filter(player => (player.identity || "").toLowerCase().includes(pinLower)); | |
| const pinned = matches.find(player => player.canControl) ?? matches[0]; | |
| if (pinned) | |
| return pinned; | |
| } |
Claude reviewOne finding; the previously-flagged binding-destroying assignment in
Checked: pin resolution order and interaction with the existing mirror/paused paths, |
Description
Add a minimal "pin media source" option so the bar media widget and OSD keep showing a preferred player (e.g. a music player) even when another source (e.g. YouTube in the browser) starts playing.
Type of change
Related issues
Screenshots / video
Checklist
I18n.tr()with translator context, reusing existing terms where possiblemake fmt, added/updated tests,make testpasses, andgo mod tidyis cleanmake lint-qmlwith no new warningsi'll do that last one next