Skip to content

Commit

Permalink
Merge pull request #3913 from Holzhaus/qml-coverart
Browse files Browse the repository at this point in the history
QML: Add ImageProvider for Cover Art
  • Loading branch information
uklotzde authored Jun 5, 2021
2 parents ca3a4ee + 92af1ef commit 4f16f8e
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 1 deletion.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/skin/legacy/legacyskinparser.cpp
src/skin/legacy/pixmapsource.cpp
src/skin/legacy/legacyskin.cpp
src/skin/qml/asyncimageprovider.cpp
src/skin/qml/qmlcontrolproxy.cpp
src/skin/qml/qmlplayermanagerproxy.cpp
src/skin/qml/qmlplayerproxy.cpp
Expand Down
25 changes: 24 additions & 1 deletion res/skins/QMLDemo/DeckInfoBar.qml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "." as Skin
import Mixxx 0.1 as Mixxx
import Mixxx.Controls 0.1 as MixxxControls
import QtGraphicalEffects 1.12
import QtQuick 2.12
import "Theme"

Expand All @@ -15,14 +16,36 @@ Rectangle {
radius: 5
height: 56

Rectangle {
Image {
id: coverArt

anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 5
width: height
source: root.deckPlayer.coverArtUrl
visible: false
}

Rectangle {
id: coverArtCircle

anchors.fill: coverArt
radius: height / 2
visible: false
}

OpacityMask {
anchors.fill: coverArt
source: coverArt
maskSource: coverArtCircle
}

Rectangle {
id: spinnyCircle

anchors.fill: coverArt
radius: height / 2
border.width: 2
border.color: Theme.deckLineColor
Expand Down
79 changes: 79 additions & 0 deletions src/skin/qml/asyncimageprovider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "skin/qml/asyncimageprovider.h"

#include "library/coverartcache.h"

namespace {
const QString kCoverArtPrefix = QStringLiteral("coverart/");
}

namespace mixxx {
namespace skin {
namespace qml {

AsyncImageResponse::AsyncImageResponse(const QString& id, const QSize& requestedSize)
: m_id(id), m_requestedSize(requestedSize) {
setAutoDelete(false);
}

QQuickTextureFactory* AsyncImageResponse::textureFactory() const {
return QQuickTextureFactory::textureFactoryForImage(m_image);
}

void AsyncImageResponse::run() {
if (m_id.startsWith(kCoverArtPrefix)) {
QString trackLocation = AsyncImageProvider::coverArtUrlIdToTrackLocation(m_id);

// TODO: This code does not allow to override embedded cover art with
// a custom image, which is possible in Mixxx. We need to access the
// actual CoverInfo of the track instead of constructing a default
// instance on the fly.
//
// Unfortunately, TrackCollectionManager::getTrackByRef will not work
// when called from another thread (like this one). We need a solution
// for that.
CoverInfo coverInfo(CoverInfoRelative(), trackLocation);
coverInfo.type = CoverInfoRelative::METADATA;
CoverInfo::LoadedImage loadedImage = coverInfo.loadImage();
if (loadedImage.result != CoverInfo::LoadedImage::Result::Ok) {
coverInfo.type = CoverInfoRelative::FILE;
loadedImage = coverInfo.loadImage();
}
m_image = loadedImage.image;
} else {
qWarning() << "ImageProvider: Unknown ID " << m_id;
}

if (!m_image.isNull() && m_requestedSize.isValid()) {
m_image = m_image.scaled(m_requestedSize);
}

emit finished();
}

QQuickImageResponse* AsyncImageProvider::requestImageResponse(
const QString& id, const QSize& requestedSize) {
AsyncImageResponse* response = new AsyncImageResponse(id, requestedSize);
pool.start(response);
return response;
}

// static
const QString AsyncImageProvider::kProviderName = QStringLiteral("mixxx");

// static
QUrl AsyncImageProvider::trackLocationToCoverArtUrl(const QString& trackLocation) {
QUrl url("image://" + kProviderName + "/" + kCoverArtPrefix);
return url.resolved(
QString::fromLatin1(trackLocation.toUtf8().toBase64(
QByteArray::Base64UrlEncoding)));
}

//static
QString AsyncImageProvider::coverArtUrlIdToTrackLocation(const QString& coverArtUrlId) {
return QString::fromUtf8(QByteArray::fromBase64(
coverArtUrlId.mid(kCoverArtPrefix.size()).toLatin1(), QByteArray::Base64UrlEncoding));
}

} // namespace qml
} // namespace skin
} // namespace mixxx
40 changes: 40 additions & 0 deletions src/skin/qml/asyncimageprovider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <QImage>
#include <QQuickAsyncImageProvider>
#include <QRunnable>
#include <QSize>
#include <QString>
#include <QThreadPool>

#include "library/coverart.h"

namespace mixxx {
namespace skin {
namespace qml {

class AsyncImageResponse : public QQuickImageResponse, public QRunnable {
public:
AsyncImageResponse(const QString& id, const QSize& requestedSize);
QQuickTextureFactory* textureFactory() const override;
void run() override;

QString m_id;
QSize m_requestedSize;
QImage m_image;
};

class AsyncImageProvider : public QQuickAsyncImageProvider {
public:
QQuickImageResponse* requestImageResponse(
const QString& id, const QSize& requestedSize) override;

static const QString kProviderName;
static QUrl trackLocationToCoverArtUrl(const QString& trackLocation);
static QString coverArtUrlIdToTrackLocation(const QString& coverArtUrlId);

private:
QThreadPool pool;
};

} // namespace qml
} // namespace skin
} // namespace mixxx
12 changes: 12 additions & 0 deletions src/skin/qml/qmlplayerproxy.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "skin/qml/qmlplayerproxy.h"

#include "mixer/basetrackplayer.h"
#include "skin/qml/asyncimageprovider.h"

#define PROPERTY_IMPL(TYPE, NAME, GETTER, SETTER) \
TYPE QmlPlayerProxy::GETTER() const { \
Expand Down Expand Up @@ -125,6 +126,7 @@ void QmlPlayerProxy::slotTrackChanged() {
emit commentChanged();
emit keyTextChanged();
emit colorChanged();
emit coverArtUrlChanged();
}

PROPERTY_IMPL(QString, artist, getArtist, setArtist)
Expand Down Expand Up @@ -156,6 +158,16 @@ void QmlPlayerProxy::setColor(const QColor& value) {
}
}

QUrl QmlPlayerProxy::getCoverArtUrl() const {
const TrackPointer pTrack = m_pCurrentTrack;
if (pTrack == nullptr) {
return QUrl();
}

const CoverInfo coverInfo = pTrack->getCoverInfoWithLocation();
return AsyncImageProvider::trackLocationToCoverArtUrl(coverInfo.trackLocation);
}

} // namespace qml
} // namespace skin
} // namespace mixxx
4 changes: 4 additions & 0 deletions src/skin/qml/qmlplayerproxy.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <QColor>
#include <QObject>
#include <QString>
#include <QUrl>

#include "track/track.h"

Expand All @@ -27,6 +28,7 @@ class QmlPlayerProxy : public QObject {
Q_PROPERTY(QString comment READ getComment WRITE setComment NOTIFY commentChanged)
Q_PROPERTY(QString keyText READ getKeyText WRITE setKeyText NOTIFY keyTextChanged)
Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QUrl coverArtUrl READ getCoverArtUrl NOTIFY coverArtUrlChanged)

public:
explicit QmlPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent);
Expand All @@ -45,6 +47,7 @@ class QmlPlayerProxy : public QObject {
QString getComment() const;
QString getKeyText() const;
QColor getColor() const;
QUrl getCoverArtUrl() const;

public slots:
void slotTrackLoaded(TrackPointer pTrack);
Expand Down Expand Up @@ -84,6 +87,7 @@ class QmlPlayerProxy : public QObject {
void commentChanged();
void keyTextChanged();
void colorChanged();
void coverArtUrlChanged();

private:
BaseTrackPlayer* m_pTrackPlayer;
Expand Down
6 changes: 6 additions & 0 deletions src/skin/qml/qmlskin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <functional>

#include "coreservices.h"
#include "skin/qml/asyncimageprovider.h"
#include "skin/qml/qmlcontrolproxy.h"
#include "skin/qml/qmlplayermanagerproxy.h"
#include "skin/qml/qmlplayerproxy.h"
Expand Down Expand Up @@ -155,6 +156,11 @@ QWidget* QmlSkin::loadSkin(QWidget* pParent,

pWidget->engine()->setBaseUrl(QUrl::fromLocalFile(m_path.absoluteFilePath()));
pWidget->engine()->addImportPath(m_path.absoluteFilePath());

// No memory leak here, the QQmlENgine takes ownership of the provider
QQuickAsyncImageProvider* pImageProvider = new AsyncImageProvider();
pWidget->engine()->addImageProvider(AsyncImageProvider::kProviderName, pImageProvider);

pWidget->setSource(QUrl::fromLocalFile(dir().absoluteFilePath(kMainQmlFileName)));
pWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
if (pWidget->status() != QQuickWidget::Ready) {
Expand Down

0 comments on commit 4f16f8e

Please sign in to comment.