From aeb3fdd637b9080b0989bc320bd3999450c7eb14 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 28 Apr 2026 11:27:16 -0400 Subject: [PATCH] osd(media): workaround for firefox reporting youtube thumbnails as players fixes #2298 --- quickshell/Modules/DankBar/Widgets/Media.qml | 50 +++++++++++++++----- quickshell/Modules/OSD/MediaPlaybackOSD.qml | 13 ++++- quickshell/Services/MprisController.qml | 10 ++++ 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/quickshell/Modules/DankBar/Widgets/Media.qml b/quickshell/Modules/DankBar/Widgets/Media.qml index 6007427e..a1f7f05e 100644 --- a/quickshell/Modules/DankBar/Widgets/Media.qml +++ b/quickshell/Modules/DankBar/Widgets/Media.qml @@ -10,6 +10,36 @@ BasePill { readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property bool playerAvailable: activePlayer !== null + readonly property bool _hoverPreview: MprisController.isFirefoxYoutubeHoverPreview(activePlayer) + readonly property bool _isPlaying: !!activePlayer && activePlayer.playbackState === 1 && !_hoverPreview + + property string _stableTitle: "" + property string _stableArtist: "" + + Connections { + target: root.activePlayer + function onTrackTitleChanged() { + root._syncMeta(); + } + function onTrackArtistChanged() { + root._syncMeta(); + } + } + + onActivePlayerChanged: _syncMeta() + + function _syncMeta() { + if (!activePlayer) { + _stableTitle = ""; + _stableArtist = ""; + return; + } + if (MprisController.isFirefoxYoutubeHoverPreview(activePlayer)) + return; + _stableTitle = activePlayer.trackTitle || ""; + _stableArtist = activePlayer.trackArtist || ""; + } + readonly property bool __isChromeBrowser: { if (!activePlayer?.identity) return false; @@ -212,15 +242,15 @@ BasePill { height: 24 radius: 12 anchors.horizontalCenter: parent.horizontalCenter - color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover + color: root._isPlaying ? Theme.primary : Theme.primaryHover visible: root.playerAvailable opacity: activePlayer ? 1 : 0.3 DankIcon { anchors.centerIn: parent - name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow" + name: root._isPlaying ? "pause" : "play_arrow" size: 14 - color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary + color: root._isPlaying ? Theme.background : Theme.primary } MouseArea { @@ -279,12 +309,10 @@ BasePill { readonly property bool isWebMedia: lowerIdentity.includes("firefox") || lowerIdentity.includes("chrome") || lowerIdentity.includes("chromium") || lowerIdentity.includes("edge") || lowerIdentity.includes("safari") property string displayText: { - if (!activePlayer || !activePlayer.trackTitle) { + if (!activePlayer || !root._stableTitle) return ""; - } - - const title = isWebMedia ? activePlayer.trackTitle : (activePlayer.trackTitle || "Unknown Track"); - const subtitle = isWebMedia ? (activePlayer.trackArtist || cachedIdentity) : (activePlayer.trackArtist || ""); + const title = isWebMedia ? root._stableTitle : (root._stableTitle || "Unknown Track"); + const subtitle = isWebMedia ? (root._stableArtist || cachedIdentity) : (root._stableArtist || ""); return subtitle.length > 0 ? title + " • " + subtitle : title; } @@ -444,15 +472,15 @@ BasePill { height: 24 radius: 12 anchors.verticalCenter: parent.verticalCenter - color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Theme.primaryHover + color: root._isPlaying ? Theme.primary : Theme.primaryHover visible: root.playerAvailable opacity: activePlayer ? 1 : 0.3 DankIcon { anchors.centerIn: parent - name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow" + name: root._isPlaying ? "pause" : "play_arrow" size: 14 - color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary + color: root._isPlaying ? Theme.background : Theme.primary } MouseArea { diff --git a/quickshell/Modules/OSD/MediaPlaybackOSD.qml b/quickshell/Modules/OSD/MediaPlaybackOSD.qml index 270a9d0a..2f06627f 100644 --- a/quickshell/Modules/OSD/MediaPlaybackOSD.qml +++ b/quickshell/Modules/OSD/MediaPlaybackOSD.qml @@ -47,6 +47,9 @@ DankOSD { } property bool _pendingShow: false + property string _displayTitle: "" + property string _displayArtist: "" + property string _displayAlbum: "" Timer { id: iconDebounce @@ -105,6 +108,12 @@ DankOSD { return; if (!SettingsData.osdMediaPlaybackEnabled) return; + if (MprisController.isFirefoxYoutubeHoverPreview(player)) + return; + + root._displayTitle = player.trackTitle || ""; + root._displayArtist = player.trackArtist || ""; + root._displayAlbum = player.trackAlbum || ""; root.updatePlaybackIcon(); TrackArtService.loadArtwork(player.trackArtUrl); @@ -254,7 +263,7 @@ DankOSD { StyledText { id: topText width: parent.width - text: player ? `${player.trackTitle || I18n.tr("Unknown Title")}` : "" + text: player ? (root._displayTitle || I18n.tr("Unknown Title")) : "" font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText @@ -265,7 +274,7 @@ DankOSD { StyledText { id: bottomText width: parent.width - text: player ? ((player.trackArtist || I18n.tr("Unknown Artist")) + (player.trackAlbum ? ` • ${player.trackAlbum}` : "")) : "" + text: player ? ((root._displayArtist || I18n.tr("Unknown Artist")) + (root._displayAlbum ? ` • ${root._displayAlbum}` : "")) : "" font.pixelSize: Theme.fontSizeSmall font.weight: Font.Light color: Theme.surfaceText diff --git a/quickshell/Services/MprisController.qml b/quickshell/Services/MprisController.qml index 44ad1358..9737fd4d 100644 --- a/quickshell/Services/MprisController.qml +++ b/quickshell/Services/MprisController.qml @@ -67,6 +67,16 @@ Singleton { onTriggered: root.activePlayer?.positionChanged() } + function isFirefoxYoutubeHoverPreview(player: MprisPlayer): bool { + if (!player) + return false; + const id = (player.identity || "").toLowerCase(); + if (!id.includes("firefox")) + return false; + const url = (player.metadata?.["xesam:url"] || "").toString(); + return /^https?:\/\/(www\.)?youtube\.com\/?($|\?|#)/i.test(url); + } + function previousOrRewind(): void { if (!activePlayer) return;