1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-12 00:32:17 -04:00

Making the new media playback OSD more beautiful (#1638)

* feat: decouple track art downloads into new TrackArtService

* feat: beautify media playback osd with track art

* fix: bug when switching from art to no art
This commit is contained in:
ArijanJ
2026-02-10 03:13:23 +01:00
committed by GitHub
parent e3bd31bb52
commit b9bcfd8d2c
3 changed files with 162 additions and 56 deletions

View File

@@ -62,8 +62,6 @@ Item {
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0) readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
property bool isSwitching: false property bool isSwitching: false
property string _lastArtUrl: ""
property string _bgArtSource: ""
// Derived "no players" state: always correct, no timers. // Derived "no players" state: always correct, no timers.
readonly property int _playerCount: allPlayers ? allPlayers.length : 0 readonly property int _playerCount: allPlayers ? allPlayers.length : 0
@@ -88,28 +86,7 @@ Item {
isSwitching = true; isSwitching = true;
_switchHold = true; _switchHold = true;
_switchHoldTimer.restart(); _switchHoldTimer.restart();
if (activePlayer.trackArtUrl) TrackArtService.loadArtwork(activePlayer.trackArtUrl);
loadArtwork(activePlayer.trackArtUrl);
}
property string activeTrackArtFile: ""
function loadArtwork(url) {
if (!url)
return;
if (url.startsWith("http://") || url.startsWith("https://")) {
const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg";
activeTrackArtFile = filename;
cleanupProcess.command = ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"];
cleanupProcess.running = true;
imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url];
imageDownloader.targetFile = filename;
imageDownloader.running = true;
return;
}
_bgArtSource = url;
} }
function maybeFinishSwitch() { function maybeFinishSwitch() {
@@ -138,10 +115,7 @@ Item {
maybeFinishSwitch(); maybeFinishSwitch();
} }
function onTrackArtUrlChanged() { function onTrackArtUrlChanged() {
if (activePlayer?.trackArtUrl) { TrackArtService.loadArtwork(activePlayer.trackArtUrl);
_lastArtUrl = activePlayer.trackArtUrl;
loadArtwork(activePlayer.trackArtUrl);
}
} }
} }
@@ -213,22 +187,6 @@ Item {
} }
} }
Process {
id: imageDownloader
running: false
property string targetFile: ""
onExited: exitCode => {
if (exitCode === 0 && targetFile)
_bgArtSource = "file://" + targetFile;
}
}
Process {
id: cleanupProcess
running: false
}
property bool isSeeking: false property bool isSeeking: false
Timer { Timer {
@@ -241,14 +199,14 @@ Item {
Item { Item {
id: bgContainer id: bgContainer
anchors.fill: parent anchors.fill: parent
visible: _bgArtSource !== "" visible: TrackArtService._bgArtSource !== ""
Image { Image {
id: bgImage id: bgImage
anchors.centerIn: parent anchors.centerIn: parent
width: Math.max(parent.width, parent.height) * 1.1 width: Math.max(parent.width, parent.height) * 1.1
height: width height: width
source: _bgArtSource source: TrackArtService._bgArtSource
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
asynchronous: true asynchronous: true
cache: true cache: true

View File

@@ -1,4 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -10,7 +12,7 @@ DankOSD {
readonly property bool useVertical: isVerticalLayout readonly property bool useVertical: isVerticalLayout
readonly property var player: MprisController.activePlayer readonly property var player: MprisController.activePlayer
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2) osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(280, Screen.width - Theme.spacingM * 2)
osdHeight: useVertical ? (Theme.iconSize * 2) : (40 + Theme.spacingS * 2) osdHeight: useVertical ? (Theme.iconSize * 2) : (40 + Theme.spacingS * 2)
autoHideInterval: 3000 autoHideInterval: 3000
enableMouseInteraction: true enableMouseInteraction: true
@@ -44,12 +46,17 @@ DankOSD {
target: player target: player
function handleUpdate() { function handleUpdate() {
if (!root.player?.trackTitle) return; if (!root.player?.trackTitle)
return;
if (SettingsData.osdMediaPlaybackEnabled) { if (SettingsData.osdMediaPlaybackEnabled) {
TrackArtService.loadArtwork(player.trackArtUrl);
root.show(); root.show();
} }
} }
function onTrackArtUrlChanged() {
TrackArtService.loadArtwork(player.trackArtUrl);
}
function onIsPlayingChanged() { function onIsPlayingChanged() {
handleUpdate(); handleUpdate();
} }
@@ -79,6 +86,67 @@ DankOSD {
onClicked: root.hide() onClicked: root.hide()
} }
Item {
id: bgContainer
anchors.fill: parent
visible: TrackArtService._bgArtSource !== ""
Image {
id: bgImage
anchors.centerIn: parent
width: Math.max(parent.width, parent.height)
height: width
source: TrackArtService._bgArtSource
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: false
}
Item {
id: blurredBg
anchors.fill: parent
visible: false
MultiEffect {
anchors.centerIn: parent
width: bgImage.width
height: bgImage.height
source: bgImage
blurEnabled: true
blurMax: 64
blur: 0.3
saturation: -0.2
brightness: -0.25
}
}
Rectangle {
id: bgMask
anchors.fill: parent
radius: Theme.cornerRadius
visible: false
layer.enabled: true
}
MultiEffect {
anchors.fill: parent
source: blurredBg
maskEnabled: true
maskSource: bgMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
opacity: 0.7
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.surface
opacity: 0.3
}
}
Rectangle { Rectangle {
width: Theme.iconSize width: Theme.iconSize
height: Theme.iconSize height: Theme.iconSize
@@ -107,17 +175,33 @@ DankOSD {
} }
} }
StyledText { Column {
id: textItem
x: parent.gap * 2 + Theme.iconSize x: parent.gap * 2 + Theme.iconSize
width: parent.width - Theme.iconSize - parent.gap * 3 width: parent.width - Theme.iconSize - parent.gap * 3
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: player ? `${player.trackTitle || I18n.tr("Unknown Title")} ${player.trackArtist || I18n.tr("Unknown Artist")}` : "" spacing: 3
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium StyledText {
color: Theme.surfaceText id: topText
wrapMode: Text.Wrap width: parent.width
maximumLineCount: 3 text: player ? `${player.trackTitle || I18n.tr("Unknown Title")}` : ""
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
StyledText {
id: bottomText
width: parent.width
text: player ? ((player.trackArtist || I18n.tr("Unknown Artist")) + (player.trackAlbum ? ` ${player.trackAlbum}` : "")) : ""
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Light
color: Theme.surfaceText
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
} }
} }
} }

View File

@@ -0,0 +1,64 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import Quickshell.Io
import Quickshell.Services.Mpris
Singleton {
id: root
property string _lastArtUrl: ""
property string _bgArtSource: ""
property string activeTrackArtFile: ""
function loadArtwork(url) {
if (!url || url == "") {
_bgArtSource = "";
_lastArtUrl = "";
return;
}
if (url == _lastArtUrl)
return;
_lastArtUrl = url;
if (url.startsWith("http://") || url.startsWith("https://")) {
const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg";
activeTrackArtFile = filename;
cleanupProcess.command = ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"];
cleanupProcess.running = true;
imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url];
imageDownloader.targetFile = filename;
imageDownloader.running = true;
return;
}
// otherwise
_bgArtSource = url;
}
property MprisPlayer activePlayer: MprisController.activePlayer
onActivePlayerChanged: {
loadArtwork(activePlayer.trackArtUrl);
}
Process {
id: imageDownloader
running: false
property string targetFile: ""
onExited: exitCode => {
if (exitCode === 0 && targetFile)
_bgArtSource = "file://" + targetFile;
}
}
Process {
id: cleanupProcess
running: false
}
}