mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 12:13:31 -04:00
8a4be4936a
* fix(Mpris): exclude idle players from active-player selection Add MprisController.isIdle() (player Stopped with empty title and artist). _resolveActivePlayer excludes idle players from every selection path, and re-resolves when the active player itself goes idle. Existing triggers (availablePlayers change, isPlaying becoming true) do not fire for a player merely stopping. The fallback now gates on !isIdle(p) instead of p.canPlay. canPlay describes whether Play() would succeed; !isIdle describes whether there is anything to surface. canControl unchanged. When no eligible player remains, activePlayer becomes null and consumers that gate on it unload (the bar media widget via WidgetHost; the dash via the companion fix). * fix(DankDash): show no-player state when active player resolves to null showNoPlayerNow gated on _noneAvailable (player count === 0) or activePlayer being idle. Neither covers activePlayer being null while players remain registered, which is now possible (an always-on player sitting stopped with empty metadata). Key showNoPlayerNow on !activePlayer; drop the unreachable _trulyIdle. * add(MprisController): add track artist change handling to active player resolution ---------
123 lines
4.0 KiB
QML
123 lines
4.0 KiB
QML
pragma Singleton
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Services.Mpris
|
|
import qs.Common
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
|
|
property MprisPlayer activePlayer: null
|
|
property real activePlayerStableLength: 0
|
|
|
|
Connections {
|
|
target: root.activePlayer
|
|
function onTrackTitleChanged() {
|
|
root.activePlayerStableLength = (root.activePlayer && root.activePlayer.lengthSupported && root.activePlayer.length > 1) ? root.activePlayer.length : 0;
|
|
if (root.isIdle(root.activePlayer))
|
|
root._resolveActivePlayer();
|
|
}
|
|
function onTrackArtistChanged() {
|
|
if (root.isIdle(root.activePlayer))
|
|
root._resolveActivePlayer();
|
|
}
|
|
function onLengthChanged() {
|
|
if (root.activePlayer && root.activePlayer.lengthSupported && root.activePlayer.length > 1) {
|
|
root.activePlayerStableLength = root.activePlayer.length;
|
|
}
|
|
}
|
|
function onPlaybackStateChanged() {
|
|
if (root.isIdle(root.activePlayer))
|
|
root._resolveActivePlayer();
|
|
}
|
|
}
|
|
|
|
onActivePlayerChanged: {
|
|
activePlayerStableLength = (activePlayer && activePlayer.lengthSupported && activePlayer.length > 1) ? activePlayer.length : 0;
|
|
}
|
|
|
|
onAvailablePlayersChanged: _resolveActivePlayer()
|
|
Component.onCompleted: _resolveActivePlayer()
|
|
|
|
Instantiator {
|
|
model: root.availablePlayers
|
|
delegate: Connections {
|
|
required property MprisPlayer modelData
|
|
target: modelData
|
|
function onIsPlayingChanged() {
|
|
if (modelData.isPlaying)
|
|
root._resolveActivePlayer();
|
|
}
|
|
}
|
|
}
|
|
|
|
function isIdle(player: MprisPlayer): bool {
|
|
return player
|
|
&& player.playbackState === MprisPlaybackState.Stopped
|
|
&& !player.trackTitle
|
|
&& !player.trackArtist;
|
|
}
|
|
|
|
function _resolveActivePlayer(): void {
|
|
const playing = availablePlayers.find(p => p.isPlaying);
|
|
if (playing) {
|
|
activePlayer = playing;
|
|
_persistIdentity(playing.identity);
|
|
return;
|
|
}
|
|
if (activePlayer && availablePlayers.indexOf(activePlayer) >= 0 && !isIdle(activePlayer))
|
|
return;
|
|
const savedId = SessionData.lastPlayerIdentity;
|
|
if (savedId) {
|
|
const match = availablePlayers.find(p => p.identity === savedId);
|
|
if (match && !isIdle(match)) {
|
|
activePlayer = match;
|
|
return;
|
|
}
|
|
}
|
|
activePlayer = availablePlayers.find(p => p.canControl && !isIdle(p)) ?? null;
|
|
if (activePlayer)
|
|
_persistIdentity(activePlayer.identity);
|
|
}
|
|
|
|
function setActivePlayer(player: MprisPlayer): void {
|
|
activePlayer = player;
|
|
if (player)
|
|
_persistIdentity(player.identity);
|
|
}
|
|
|
|
function _persistIdentity(identity: string): void {
|
|
if (identity && SessionData.lastPlayerIdentity !== identity)
|
|
SessionData.set("lastPlayerIdentity", identity);
|
|
}
|
|
|
|
Timer {
|
|
interval: 1000
|
|
running: root.activePlayer?.playbackState === MprisPlaybackState.Playing
|
|
repeat: true
|
|
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;
|
|
if (activePlayer.position > 8 && activePlayer.canSeek)
|
|
activePlayer.position = 0.1;
|
|
else if (activePlayer.canGoPrevious)
|
|
activePlayer.previous();
|
|
}
|
|
}
|