Files
dotfiles/quickshell/Services/MusicManager.qml
2025-10-11 16:28:11 +02:00

165 lines
4.6 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Services.Mpris
import qs.Modules.Misc
pragma Singleton
Singleton {
id: manager
// Properties
property var currentPlayer: null
property real currentPosition: 0
property bool isPlaying: currentPlayer ? currentPlayer.isPlaying : false
property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "Unknown Track") : ""
property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "Unknown Artist") : ""
property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "Unknown Album") : ""
property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : ""
property real trackLength: currentPlayer ? currentPlayer.length : 0
property bool canPlay: currentPlayer ? currentPlayer.canPlay : false
property bool canPause: currentPlayer ? currentPlayer.canPause : false
property bool canGoNext: currentPlayer ? currentPlayer.canGoNext : false
property bool canGoPrevious: currentPlayer ? currentPlayer.canGoPrevious : false
property bool canSeek: currentPlayer ? currentPlayer.canSeek : false
property bool hasPlayer: getAvailablePlayers().length > 0
// Expose cava values
property alias cavaValues: cava.values
// Returns available MPRIS players
function getAvailablePlayers() {
if (!Mpris.players || !Mpris.players.values)
return [];
let allPlayers = Mpris.players.values;
let controllablePlayers = [];
for (let i = 0; i < allPlayers.length; i++) {
let player = allPlayers[i];
if (player && player.canControl)
controllablePlayers.push(player);
}
return controllablePlayers;
}
// Returns active player or first available
function findActivePlayer() {
let availablePlayers = getAvailablePlayers();
if (availablePlayers.length === 0)
return null;
// Get the first playing player
for (let i = availablePlayers.length - 1; i >= 0; i--) {
if (availablePlayers[i].isPlaying)
return availablePlayers[i];
}
// Fallback to last player
return availablePlayers[availablePlayers.length - 1];
}
// Updates currentPlayer and currentPosition
function updateCurrentPlayer() {
let newPlayer = findActivePlayer();
if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer;
currentPosition = currentPlayer ? currentPlayer.position : 0;
}
}
// Player control functions
function playPause() {
if (currentPlayer) {
if (currentPlayer.isPlaying)
currentPlayer.pause();
else
currentPlayer.play();
}
}
function isAllPaused() {
let availablePlayers = getAvailablePlayers();
for (let i = 0; i < availablePlayers.length; i++) {
if (availablePlayers[i].isPlaying)
return false;
}
return true;
}
function play() {
if (currentPlayer && currentPlayer.canPlay)
currentPlayer.play();
}
function pause() {
if (currentPlayer && currentPlayer.canPause)
currentPlayer.pause();
}
function next() {
if (currentPlayer && currentPlayer.canGoNext)
currentPlayer.next();
}
function previous() {
if (currentPlayer && currentPlayer.canGoPrevious)
currentPlayer.previous();
}
function seek(position) {
if (currentPlayer && currentPlayer.canSeek) {
currentPlayer.position = position;
currentPosition = position;
}
}
function seekByRatio(ratio) {
if (currentPlayer && currentPlayer.canSeek && currentPlayer.length > 0) {
let seekPosition = ratio * currentPlayer.length;
currentPlayer.position = seekPosition;
currentPosition = seekPosition;
}
}
// Initialize
Item {
Component.onCompleted: {
updateCurrentPlayer();
}
}
// Updates progress bar every second
Timer {
id: positionTimer
interval: 1000
running: currentPlayer && currentPlayer.isPlaying && currentPlayer.length > 0
repeat: true
onTriggered: {
if (currentPlayer && currentPlayer.isPlaying)
currentPosition = currentPlayer.position;
}
}
// Reacts to player list changes
Connections {
function onValuesChanged() {
updateCurrentPlayer();
}
target: Mpris.players
}
Cava {
id: cava
count: 44
}
}