noctalia-shell/Services/MediaService.qml
LemmyCook 7d2eaa46e6 qmlfmt: increase line-length to 360 to avoid hard-wrap.
+ cleaned up power menu/panel
2025-09-12 21:07:11 -04:00

200 lines
6.2 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
import qs.Commons
import qs.Services
Singleton {
id: root
property var currentPlayer: null
property real currentPosition: 0
property bool isSeeking: false
property int selectedPlayerIndex: 0
property bool isPlaying: currentPlayer ? (currentPlayer.playbackState === MprisPlaybackState.Playing || currentPlayer.isPlaying) : false
property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "") : ""
property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "") : ""
property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "") : ""
property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : ""
property real trackLength: currentPlayer ? ((currentPlayer.length < infiniteTrackLength) ? currentPlayer.length : 0) : 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 real infiniteTrackLength: 922337203685
Component.onCompleted: {
updateCurrentPlayer()
}
function getAvailablePlayers() {
if (!Mpris.players || !Mpris.players.values) {
return []
}
let allPlayers = Mpris.players.values
let controllablePlayers = []
// Apply blacklist and controllable filter
const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : []
for (var i = 0; i < allPlayers.length; i++) {
let player = allPlayers[i]
if (!player)
continue
const identity = String(player.identity || "")
const busName = String(player.busName || "")
const desktop = String(player.desktopEntry || "")
const idKey = identity.toLowerCase()
const match = blacklist.find(b => {
const s = String(b || "").toLowerCase()
return s && (idKey.includes(s) || busName.toLowerCase().includes(s) || desktop.toLowerCase().includes(s))
})
if (match)
continue
if (player.canControl)
controllablePlayers.push(player)
}
return controllablePlayers
}
function findActivePlayer() {
let availablePlayers = getAvailablePlayers()
if (availablePlayers.length === 0) {
Logger.log("Media", "No active player found")
return null
}
// Preferred player logic (preferred > fallback)
const preferred = (Settings.data.audio.preferredPlayer || "")
if (preferred !== "") {
for (var i = 0; i < availablePlayers.length; i++) {
const p = availablePlayers[i]
const identity = String(p.identity || "").toLowerCase()
const busName = String(p.busName || "").toLowerCase()
const desktop = String(p.desktopEntry || "").toLowerCase()
const pref = preferred.toLowerCase()
if (identity.includes(pref) || busName.includes(pref) || desktop.includes(pref)) {
selectedPlayerIndex = i
return p
}
}
}
if (selectedPlayerIndex < availablePlayers.length) {
return availablePlayers[selectedPlayerIndex]
} else {
selectedPlayerIndex = 0
return availablePlayers[0]
}
}
// Switch to the most recently active player
function updateCurrentPlayer() {
let newPlayer = findActivePlayer()
if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer
currentPosition = currentPlayer ? currentPlayer.position : 0
Logger.log("Media", "Switching player")
}
}
function playPause() {
if (currentPlayer) {
if (currentPlayer.isPlaying) {
currentPlayer.pause()
} else {
currentPlayer.play()
}
}
}
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
}
}
// Seek to position based on ratio (0.0 to 1.0)
function seekByRatio(ratio) {
if (currentPlayer && currentPlayer.canSeek && currentPlayer.length > 0) {
let seekPosition = ratio * currentPlayer.length
currentPlayer.position = seekPosition
currentPosition = seekPosition
}
}
// Update progress bar every second while playing
Timer {
id: positionTimer
interval: 1000
running: currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.length > 0 && currentPlayer.playbackState === MprisPlaybackState.Playing
repeat: true
onTriggered: {
if (currentPlayer && !root.isSeeking && currentPlayer.isPlaying && currentPlayer.playbackState === MprisPlaybackState.Playing) {
currentPosition = currentPlayer.position
} else {
running = false
}
}
}
// Avoid overwriting currentPosition while seeking due to backend position changes
Connections {
target: currentPlayer
function onPositionChanged() {
if (!root.isSeeking && currentPlayer) {
currentPosition = currentPlayer.position
}
}
function onPlaybackStateChanged() {
if (!root.isSeeking && currentPlayer) {
currentPosition = currentPlayer.position
}
}
}
// Reset position when switching to inactive player
onCurrentPlayerChanged: {
if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) {
currentPosition = 0
}
}
// Update current player when available players change
Connections {
target: Mpris.players
function onValuesChanged() {
Logger.log("Media", "Players changed")
updateCurrentPlayer()
}
}
}