138 lines
4.4 KiB
QML
138 lines
4.4 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import QtQml.Models
|
|
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: ({})
|
|
|
|
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 {
|
|
_switchToBest();
|
|
}
|
|
}
|
|
|
|
// Watch all raw players for state changes via Instantiator -
|
|
// delegates persist across state changes since the model only
|
|
// changes on player add/remove, not property changes
|
|
property Instantiator _stateWatchers: Instantiator {
|
|
model: Mpris.players
|
|
delegate: QtObject {
|
|
required property MprisPlayer modelData
|
|
property Connections _conn: Connections {
|
|
target: modelData
|
|
function onPlaybackStateChanged() {
|
|
root._onPlaybackStateChanged(modelData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
_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 ?? "";
|
|
}
|
|
|
|
// Album art caching - persists across panel/dock collapse
|
|
property string cachedArt: ""
|
|
property string _artTrack: ""
|
|
readonly property string _artUrl: player?.trackArtUrl ?? ""
|
|
readonly property string _currentTrack: player?.trackTitle ?? ""
|
|
on_ArtUrlChanged: if (_artUrl)
|
|
cachedArt = _artUrl
|
|
on_CurrentTrackChanged: if (_currentTrack !== _artTrack) {
|
|
_artTrack = _currentTrack;
|
|
cachedArt = _artUrl || "";
|
|
}
|
|
|
|
function switchPlayer(idx) {
|
|
playerIdx = idx;
|
|
if (players[idx])
|
|
_selectedIdentity = players[idx].identity ?? "";
|
|
}
|
|
}
|