From 19d919ed5cf68e18dd1c06fb891b838c0c039929 Mon Sep 17 00:00:00 2001 From: purian23 Date: Sun, 2 Aug 2026 18:09:18 -0400 Subject: [PATCH] refactor(mpris): consolidate equivalent-player resolution & update track matching Port 1.5 --- quickshell/Modules/OSD/MediaPlaybackOSD.qml | 7 ++-- quickshell/Services/MprisController.qml | 37 +++++++++++++-------- quickshell/Services/TrackArtService.qml | 10 ++---- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/quickshell/Modules/OSD/MediaPlaybackOSD.qml b/quickshell/Modules/OSD/MediaPlaybackOSD.qml index 10d969dbd..ddce20a66 100644 --- a/quickshell/Modules/OSD/MediaPlaybackOSD.qml +++ b/quickshell/Modules/OSD/MediaPlaybackOSD.qml @@ -156,9 +156,10 @@ DankOSD { if (MprisController.isFirefoxYoutubeHoverPreview(player)) return; - const newTitle = player.trackTitle || ""; - const newArtist = player.trackArtist || ""; - const newAlbum = player.trackAlbum || ""; + const metaPlayer = MprisController.bestMetadataPlayer(player); + const newTitle = MprisController.displayTrackTitle(metaPlayer); + const newArtist = metaPlayer.trackArtist || ""; + const newAlbum = metaPlayer.trackAlbum || ""; const trackChanged = newTitle !== root._displayTitle || newArtist !== root._displayArtist || newAlbum !== root._displayAlbum; root._displayTitle = newTitle; diff --git a/quickshell/Services/MprisController.qml b/quickshell/Services/MprisController.qml index b3cc0bdcb..88a6ccb55 100644 --- a/quickshell/Services/MprisController.qml +++ b/quickshell/Services/MprisController.qml @@ -93,7 +93,7 @@ Singleton { } if (isFirefoxYoutubeHoverPreview(p)) return; - const metadataPlayer = _bestMetadataPlayer(p); + const metadataPlayer = bestMetadataPlayer(p); const nextTitle = displayTrackTitle(metadataPlayer); const trackChanged = nextTitle && stableTitle && nextTitle.toLowerCase() !== stableTitle.toLowerCase(); if (trackChanged) { @@ -171,8 +171,7 @@ Singleton { return player && player.playbackState === MprisPlaybackState.Stopped && !player.trackTitle && !player.trackArtist; } - // App-name suffixes that browsers/integrations append as " | <App>". - // Only these are stripped for track matching; display always keeps the full title. + // Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title readonly property var _appTitleSuffixes: ["youtube", "youtube music", "soundcloud", "spotify", "chrome", "chromium", "firefox", "brave", "vivaldi", "twitch"] function _stripAppTitleSuffix(title: string): string { @@ -183,12 +182,10 @@ Singleton { return _appTitleSuffixes.indexOf(suffix) !== -1 ? title.substring(0, idx).trim() : title; } - // Matching key: strip only known app suffixes so equivalent players line up. function normalizedTrackTitle(player: MprisPlayer): string { return _stripAppTitleSuffix((player?.trackTitle || "").trim()).toLowerCase(); } - // Display: never strip — a generic " | " cut would mangle legitimate titles. function displayTrackTitle(player: MprisPlayer): string { return (player?.trackTitle || "").trim(); } @@ -197,14 +194,24 @@ Singleton { return (player?.trackArtist || "").trim().toLowerCase(); } + // Artist missing on either side: fall back to URL, then album, before trusting a title-only match function isSameTrack(first: MprisPlayer, second: MprisPlayer): bool { const firstTitle = normalizedTrackTitle(first); - const secondTitle = normalizedTrackTitle(second); - if (!firstTitle || firstTitle !== secondTitle) + if (!firstTitle || firstTitle !== normalizedTrackTitle(second)) return false; const firstArtist = normalizedTrackArtist(first); const secondArtist = normalizedTrackArtist(second); - return !firstArtist || !secondArtist || firstArtist === secondArtist; + if (firstArtist && secondArtist) + return firstArtist === secondArtist; + const firstUrl = (first?.metadata?.["xesam:url"] || "").toString(); + const secondUrl = (second?.metadata?.["xesam:url"] || "").toString(); + if (firstUrl && secondUrl) + return firstUrl === secondUrl; + const firstAlbum = (first?.trackAlbum || "").trim().toLowerCase(); + const secondAlbum = (second?.trackAlbum || "").trim().toLowerCase(); + if (firstAlbum && secondAlbum) + return firstAlbum === secondAlbum; + return true; } function metadataQuality(player: MprisPlayer): int { @@ -218,10 +225,16 @@ Singleton { return quality; } - function _bestMetadataPlayer(player: MprisPlayer): MprisPlayer { - const equivalents = availablePlayers.filter(candidate => { + function equivalentPlayers(player: MprisPlayer): var { + if (!player) + return []; + return availablePlayers.filter(candidate => { return candidate.playbackState !== MprisPlaybackState.Stopped && isSameTrack(player, candidate); }); + } + + function bestMetadataPlayer(player: MprisPlayer): MprisPlayer { + const equivalents = equivalentPlayers(player); if (equivalents.length === 0) return player; return equivalents.reduce((best, candidate) => { @@ -238,9 +251,7 @@ Singleton { if (activePlayer?.isPlaying) { if (activePlayer.canControl || controllable.length === 0) return activePlayer; - // Active source is playing but not controllable: only hand ownership to a - // controllable *equivalent* peer (same track). Never let an unrelated - // player steal the active source while it is still playing. + // Playing but not controllable: only a same-track controllable peer may take over const mirror = controllable.find(player => isSameTrack(activePlayer, player)); return mirror || activePlayer; } diff --git a/quickshell/Services/TrackArtService.qml b/quickshell/Services/TrackArtService.qml index 98bd3aa4b..e40d6c5ea 100644 --- a/quickshell/Services/TrackArtService.qml +++ b/quickshell/Services/TrackArtService.qml @@ -59,11 +59,8 @@ Singleton { if (directUrl !== "") return directUrl; - const equivalent = MprisController.availablePlayers.find(candidate => { - return candidate !== player - && candidate.playbackState !== MprisPlaybackState.Stopped - && MprisController.isSameTrack(player, candidate) - && _directArtworkUrl(candidate) !== ""; + const equivalent = MprisController.equivalentPlayers(player).find(candidate => { + return candidate !== player && _directArtworkUrl(candidate) !== ""; }); return _directArtworkUrl(equivalent); } @@ -229,8 +226,7 @@ Singleton { } _pendingArtKey = key; const url = getArtworkUrl(activePlayer); - // Ignore duplicate notifications, but allow a richer peer to replace - // the artwork for the same canonical track. + // Ignore duplicate notifications, but let a richer peer replace same-track art if (key !== "" && key === _committedArtKey && url === _committedSrcUrl) return; if (key !== "" && url !== "" && url === _committedSrcUrl) {