Replace the following file:
/usr/share/plasma/plasmoids/org.kde.plasma.mediacontroller/contents/ui/AlbumArtStackView.qml
/* | |
SPDX-FileCopyrightText: 2013 Marco Martin <[email protected]> | |
SPDX-FileCopyrightText: 2014 Sebastian Kügler <[email protected]> | |
SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <[email protected]> | |
SPDX-License-Identifier: GPL-2.0-or-later | |
*/ | |
import QtQuick 2.15 | |
import QtQuick.Controls 2.15 | |
import org.kde.plasma.plasmoid 2.0 | |
import org.kde.plasma.core 2.0 as PlasmaCore | |
import org.kde.plasma.extras 2.0 as PlasmaExtras | |
Item { | |
id: container | |
/** | |
* Whether the album art image is available or in loading status | |
*/ | |
readonly property bool hasImage: (root.albumArt && albumArt.empty) | |
|| (albumArt.currentItem instanceof Image && (albumArt.currentItem.status === Image.Ready || albumArt.currentItem.status === Image.Loading)) | |
readonly property bool animating: exitTransition.running || popExitTransition.running | |
/** | |
* Whether the component is used in the compact representation | |
*/ | |
property bool inCompactRepresentation: false | |
/** | |
* Provides source item for \ShaderEffectSource | |
*/ | |
readonly property alias albumArt: albumArt | |
// HACK: fix media thumbnail not loading | |
function albumArtFixes(url) { | |
// expand this array for additional fixes | |
const replacing = [ | |
{ match: "https://open.spotify.com", replace: "https://open.spotify.com", to: "https://i.scdn.co" }, | |
{ match: "https://i.ytimg.com", replace: /\?.*/, to: "" }, | |
] | |
for (const { match, replace, to } of replacing) { | |
if (url.startsWith(match)) { | |
url = url.replace(replace, to) | |
} | |
} | |
return url | |
} | |
function loadAlbumArt() { | |
if (!root.albumArt) { | |
albumArt.clear(StackView.PopTransition); | |
return; | |
} | |
const oldImageRatio = albumArt.currentItem instanceof Image ? albumArt.currentItem.sourceSize.width / albumArt.currentItem.sourceSize.height : 1; | |
const pendingImage = albumArtComponent.createObject(albumArt, { | |
// apply patches to the source | |
"source": albumArtFixes(root.albumArt), | |
"opacity": 0, | |
}); | |
function replaceWhenLoaded() { | |
if (pendingImage.status === Image.Loading) { | |
return; | |
} | |
if (pendingImage.status === Image.Ready) { | |
const newImageRatio = pendingImage.sourceSize.width / pendingImage.sourceSize.height; | |
exitTransitionOpacityAnimator.duration = oldImageRatio === newImageRatio ? 0 : PlasmaCore.Units.longDuration; | |
} else { | |
// Load placeholder icon, but keep the invalid image to avoid flashing application icons | |
exitTransitionOpacityAnimator.duration = 0; | |
} | |
albumArt.replace(pendingImage, {}, StackView.ReplaceTransition); | |
pendingImage.statusChanged.disconnect(replaceWhenLoaded); | |
} | |
pendingImage.statusChanged.connect(replaceWhenLoaded); | |
replaceWhenLoaded(); | |
} | |
StackView { | |
id: albumArt | |
anchors.fill: parent | |
readonly property string icon: (mpris2Source.currentData && mpris2Source.currentData["Desktop Icon Name"]) || "media-album-cover" | |
replaceEnter: Transition { | |
OpacityAnimator { | |
from: 0 | |
to: 1 | |
duration: PlasmaCore.Units.longDuration | |
} | |
} | |
replaceExit: Transition { | |
id: exitTransition | |
SequentialAnimation { | |
PauseAnimation { | |
duration: PlasmaCore.Units.longDuration | |
} | |
/** | |
* If the new ratio and the old ratio are different, | |
* perform a fade-out animation for the old image | |
* to prevent it from suddenly disappearing. | |
*/ | |
OpacityAnimator { | |
id: exitTransitionOpacityAnimator | |
from: 1 | |
to: 0 | |
duration: 0 | |
} | |
} | |
} | |
popExit: Transition { | |
id: popExitTransition | |
OpacityAnimator { | |
from: 1 | |
to: 0 | |
duration: PlasmaCore.Units.longDuration | |
} | |
} | |
Component { | |
id: albumArtComponent | |
Image { // Album Art | |
horizontalAlignment: Image.AlignRight | |
verticalAlignment: Image.AlignVCenter | |
fillMode: Image.PreserveAspectFit | |
asynchronous: true | |
cache: false | |
StackView.onRemoved: { | |
source = ""; // HACK: Reduce memory usage | |
destroy(); | |
} | |
} | |
} | |
Component { | |
id: fallbackIconItem | |
PlasmaCore.IconItem { // Fallback | |
id: fallbackIcon | |
anchors.margins: PlasmaCore.Units.largeSpacing * 2 | |
opacity: 0 | |
source: albumArt.icon | |
NumberAnimation { | |
duration: PlasmaCore.Units.longDuration | |
easing.type: Easing.OutCubic | |
property: "opacity" | |
running: true | |
target: fallbackIcon | |
to: 1 | |
} | |
} | |
} | |
// "No media playing" placeholder message | |
Component { | |
id: placeholderMessage | |
// Put PlaceholderMessage in Item so PlaceholderMessage will not fill its parent. | |
Item { | |
property alias source: message.iconName | |
PlasmaExtras.PlaceholderMessage { | |
id: message | |
anchors.centerIn: parent | |
width: parent.width // For text wrap | |
iconName: albumArt.icon | |
text: (root.isPlaying || root.state === "paused") ? i18n("No title") : i18n("No media playing") | |
} | |
} | |
} | |
} | |
Loader { | |
anchors.fill: parent | |
visible: active | |
active: (inCompactRepresentation || Plasmoid.expanded) && !container.hasImage | |
asynchronous: true | |
sourceComponent: root.track ? fallbackIconItem : placeholderMessage | |
} | |
} |