pragma Singleton import QtQuick import Quickshell.Services.Mpris QtObject { id: root readonly property var players: (Mpris.players.values ?? []).filter(p => p.trackTitle || p.playbackState === MprisPlaybackState.Playing || p.playbackState === MprisPlaybackState.Paused) property int playerIdx: 0 readonly property MprisPlayer player: players[playerIdx] ?? players[0] ?? null readonly property bool playing: player?.playbackState === MprisPlaybackState.Playing property string _selectedIdentity: "" property var _lastPlayedTime: ({}) property var _watchers: [] onPlayerChanged: { if (player) _selectedIdentity = player.identity ?? ""; } onPlayersChanged: { if (_selectedIdentity) { const idx = players.findIndex(p => (p.identity ?? "") === _selectedIdentity); if (idx >= 0) { playerIdx = idx; } else { _switchToBest(); } } else if (playerIdx >= players.length) { playerIdx = 0; } } // Watch raw Mpris.players for add/remove - this does NOT fire on state changes property Connections _mprisConn: Connections { target: Mpris function onPlayersChanged() { root._reconnectWatchers(); } } function _reconnectWatchers() { for (const w of _watchers) w.destroy(); const raw = Mpris.players.values ?? []; const newWatchers = []; for (const p of raw) { const conn = _watcherComp.createObject(root, { target: p }); newWatchers.push(conn); } _watchers = newWatchers; } property Component _watcherComp: Component { Connections { function onPlaybackStateChanged() { root._onPlaybackStateChanged(target); } } } function _onPlaybackStateChanged(p) { if (!p) return; const identity = p.identity ?? ""; if (p.playbackState === MprisPlaybackState.Playing) { const times = Object.assign({}, _lastPlayedTime); times[identity] = Date.now(); _lastPlayedTime = times; // Always switch to the newly playing player if (player !== p) { const idx = players.indexOf(p); if (idx >= 0) { playerIdx = idx; _selectedIdentity = identity; } } } else if (p.playbackState === MprisPlaybackState.Paused && p === player) { // Current player paused - switch to a playing one if available _switchToPlaying(); } } function _switchToPlaying() { let best = -1; let bestTime = 0; for (let i = 0; i < players.length; i++) { if (players[i].playbackState === MprisPlaybackState.Playing) { const t = _lastPlayedTime[players[i].identity ?? ""] || 0; if (best < 0 || t > bestTime) { best = i; bestTime = t; } } } if (best >= 0) { playerIdx = best; _selectedIdentity = players[best].identity ?? ""; } } function _switchToBest() { let best = -1; let bestTime = 0; let bestPlaying = -1; let bestPlayingTime = 0; for (let i = 0; i < players.length; i++) { const identity = players[i].identity ?? ""; const t = _lastPlayedTime[identity] || 0; if (players[i].playbackState === MprisPlaybackState.Playing) { if (bestPlaying < 0 || t > bestPlayingTime) { bestPlaying = i; bestPlayingTime = t; } } if (best < 0 || t > bestTime) { best = i; bestTime = t; } } const chosen = bestPlaying >= 0 ? bestPlaying : (best >= 0 ? best : 0); playerIdx = chosen; if (players[chosen]) _selectedIdentity = players[chosen].identity ?? ""; } function switchPlayer(idx) { playerIdx = idx; if (players[idx]) _selectedIdentity = players[idx].identity ?? ""; } Component.onCompleted: _reconnectWatchers() }