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 // Track the identity of the selected player so we can find it after list changes property string _selectedIdentity: "" // Track which player played most recently (by identity) property var _lastPlayedTime: ({}) // Track previous playing state per player to detect transitions property var _wasPlaying: ({}) onPlayerChanged: { if (player) _selectedIdentity = player.identity ?? ""; } onPlayersChanged: { // Try to find the previously selected player by identity if (_selectedIdentity) { const idx = players.findIndex(p => (p.identity ?? "") === _selectedIdentity); if (idx >= 0) { playerIdx = idx; } else { // Selected player disappeared - switch to best alternative _switchToBest(); } } else if (playerIdx >= players.length) { playerIdx = 0; } } // Poll playback states to detect transitions - Connections on dynamic // targets get destroyed during list re-evaluation, losing the signal property Timer _statePoll: Timer { interval: 250 running: root.players.length > 0 repeat: true onTriggered: root._checkStates() } function _checkStates() { const prevWas = _wasPlaying; const nextWas = {}; for (let i = 0; i < players.length; i++) { const p = players[i]; const identity = p.identity ?? ""; const isPlaying = p.playbackState === MprisPlaybackState.Playing; nextWas[identity] = isPlaying; if (isPlaying && !prevWas[identity]) { // This player just started playing const times = Object.assign({}, _lastPlayedTime); times[identity] = Date.now(); _lastPlayedTime = times; // Always switch to the newly playing player if (player !== p) { playerIdx = i; _selectedIdentity = identity; } } } // Check if current player just paused const curIdentity = player?.identity ?? ""; if (prevWas[curIdentity] && !nextWas[curIdentity]) { _switchToPlaying(); } _wasPlaying = nextWas; } 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 ?? ""; } }