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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
quickshell/Services/TrackArtService.qml
Normal file
64
quickshell/Services/TrackArtService.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user