Commit 95ee3eae authored by Matthieu Gallien's avatar Matthieu Gallien 🎵
Browse files

improve adding tracks or albums from the proxy models (fast and not blocking)

Summary:
now adding albums from AllAlbumsProxyModel is fast

when adding tracks from the proxy models, use a working thread and
signals

this means that the main thread and the interface is no longer blocked

adds new methods to MediaPlayList to add list of albums or list of
tracks or list of artists

adding a lot of tracks or artists is still very slow and blocks the
interface

include QtConcurrentRun instead of QtConcurrent (suggested by ebn.kde.org)

enqueue a lot of tracks is now much faster

Test Plan: The tracks or the albums are still added to the playlist but much faster and without blocking the UI.

Reviewers: #elisa, astippich

Reviewed By: #elisa, astippich

Subscribers: astippich

Tags: #elisa

Maniphest Tasks: T7886

Differential Revision: https://phabricator.kde.org/D10451
parent ab81face
......@@ -274,11 +274,28 @@ void ElisaApplication::initialize()
d->mSingleArtistProxyModel->setSourceModel(d->mMusicManager->allAlbumsModel());
d->mSingleAlbumProxyModel->setSourceModel(d->mMusicManager->albumModel());
d->mAllAlbumsProxyModel->setMediaPlayList(d->mMediaPlayList.get());
d->mAllArtistsProxyModel->setMediaPlayList(d->mMediaPlayList.get());
d->mAllTracksProxyModel->setMediaPlayList(d->mMediaPlayList.get());
d->mSingleArtistProxyModel->setMediaPlayList(d->mMediaPlayList.get());
d->mSingleAlbumProxyModel->setMediaPlayList(d->mMediaPlayList.get());
QObject::connect(d->mAllAlbumsProxyModel.get(), &AllAlbumsProxyModel::albumToEnqueue,
d->mMediaPlayList.get(), static_cast<void (MediaPlayList::*)(const QList<MusicAlbum> &,
ElisaUtils::PlayListEnqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue));
QObject::connect(d->mAllArtistsProxyModel.get(), &AllArtistsProxyModel::artistToEnqueue,
d->mMediaPlayList.get(), &MediaPlayList::enqueueArtists);
QObject::connect(d->mAllTracksProxyModel.get(), &AllTracksProxyModel::trackToEnqueue,
d->mMediaPlayList.get(), static_cast<void (MediaPlayList::*)(const QList<MusicAudioTrack> &,
ElisaUtils::PlayListEnqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue));
QObject::connect(d->mSingleArtistProxyModel.get(), &SingleArtistProxyModel::albumToEnqueue,
d->mMediaPlayList.get(), static_cast<void (MediaPlayList::*)(const QList<MusicAlbum> &,
ElisaUtils::PlayListEnqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue));
QObject::connect(d->mSingleAlbumProxyModel.get(), &SingleAlbumProxyModel::trackToEnqueue,
d->mMediaPlayList.get(), static_cast<void (MediaPlayList::*)(const QList<MusicAudioTrack> &,
ElisaUtils::PlayListEnqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay)>(&MediaPlayList::enqueue));
}
QAction * ElisaApplication::action(const QString& name)
......
......@@ -168,3 +168,6 @@ MusicAudioTrack ElisaUtils::scanOneFile(const QUrl &scanFile, const QMimeDatabas
return newTrack;
}
#include "moc_elisautils.cpp"
......@@ -26,12 +26,29 @@
#include <QUrl>
#include <QMimeDatabase>
#include <QMetaObject>
namespace ElisaUtils {
Q_NAMESPACE
MusicAudioTrack scanOneFile(const QUrl &scanFile, const QMimeDatabase &mimeDatabase,
const KFileMetaData::ExtractorCollection &allExtractors);
enum PlayListEnqueueMode {
AppendPlayList,
ReplacePlayList,
};
Q_ENUM_NS(PlayListEnqueueMode)
enum PlayListEnqueueTriggerPlay {
DoNotTriggerPlay,
TriggerPlay,
};
Q_ENUM_NS(PlayListEnqueueTriggerPlay)
}
#endif // ELISAUTILS_H
......@@ -59,6 +59,7 @@
#include "notificationitem.h"
#include "topnotificationmanager.h"
#include "elisa_settings.h"
#include "elisautils.h"
#if defined Qt5DBus_FOUND && Qt5DBus_FOUND
#include "mpris2/mpris2.h"
......@@ -178,6 +179,8 @@ int main(int argc, char *argv[])
qRegisterMetaType<QAction*>();
qRegisterMetaType<NotificationItem>("NotificationItem");
qRegisterMetaType<QMap<QString,int>>("QMap<QString,int>");
qRegisterMetaType<ElisaUtils::PlayListEnqueueMode>("ElisaUtils::PlayListEnqueueMode");
qRegisterMetaType<ElisaUtils::PlayListEnqueueTriggerPlay>("ElisaUtils::PlayListEnqueueTriggerPlay");
qmlRegisterUncreatableType<ElisaApplication>("org.kde.elisa", 1, 0, "ElisaApplication", QStringLiteral("only one and done in c++"));
qRegisterMetaTypeStreamOperators<ManageMediaPlayerControl::PlayerState>("PlayListControler::PlayerState");
......
......@@ -628,7 +628,127 @@ void MediaPlayList::enqueueAndPlay(const QStringList &files)
}
}
void MediaPlayList::replaceAndPlay(qulonglong newTrackId)
void MediaPlayList::enqueue(const QList<qulonglong> &newTrackIds)
{
beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + newTrackIds.size() - 1);
for (auto newTrackId : newTrackIds) {
d->mData.push_back(MediaPlayListEntry{newTrackId});
d->mTrackData.push_back({});
Q_EMIT newTrackByIdInList(newTrackId);
}
endInsertRows();
restorePlayListPosition();
if (!d->mCurrentTrack.isValid()) {
resetCurrentTrack();
}
Q_EMIT tracksCountChanged();
Q_EMIT persistentStateChanged();
Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader});
}
void MediaPlayList::enqueue(const QList<MusicAlbum> &albums,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay)
{
auto tracksCount = 0;
for (const auto &oneAlbum : albums) {
for (auto oneTrackIndex = 0; oneTrackIndex < oneAlbum.tracksCount(); ++oneTrackIndex) {
++tracksCount;
}
}
if (enqueueMode == ElisaUtils::ReplacePlayList) {
clearPlayList();
}
beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + tracksCount - 1);
for (const auto &oneAlbum : albums) {
for (auto oneTrackIndex = 0; oneTrackIndex < oneAlbum.tracksCount(); ++oneTrackIndex) {
const auto &oneTrack = oneAlbum.trackFromIndex(oneTrackIndex);
d->mData.push_back(MediaPlayListEntry{oneTrack.databaseId()});
d->mTrackData.push_back(oneTrack);
}
}
endInsertRows();
restorePlayListPosition();
if (!d->mCurrentTrack.isValid()) {
resetCurrentTrack();
}
Q_EMIT tracksCountChanged();
Q_EMIT persistentStateChanged();
Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader});
if (triggerPlay == ElisaUtils::TriggerPlay) {
Q_EMIT ensurePlay();
}
}
void MediaPlayList::enqueue(const QList<MusicAudioTrack> &tracks,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay)
{
if (enqueueMode == ElisaUtils::ReplacePlayList) {
clearPlayList();
}
beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + tracks.size() - 1);
for (const auto &oneTrack : tracks) {
d->mData.push_back(MediaPlayListEntry{oneTrack.databaseId()});
d->mTrackData.push_back(oneTrack);
}
endInsertRows();
restorePlayListPosition();
if (!d->mCurrentTrack.isValid()) {
resetCurrentTrack();
}
Q_EMIT tracksCountChanged();
Q_EMIT persistentStateChanged();
Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0), {MediaPlayList::HasAlbumHeader});
if (triggerPlay == ElisaUtils::TriggerPlay) {
Q_EMIT ensurePlay();
}
}
void MediaPlayList::enqueueArtists(const QList<QString> &artistNames,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay)
{
if (enqueueMode == ElisaUtils::ReplacePlayList) {
clearPlayList();
}
beginInsertRows(QModelIndex(), d->mData.size(), d->mData.size() + artistNames.size() - 1);
for (const auto &artistName : artistNames) {
d->mData.push_back(MediaPlayListEntry{artistName});
d->mTrackData.push_back({});
Q_EMIT newArtistInList(artistName);
}
endInsertRows();
restorePlayListPosition();
if (!d->mCurrentTrack.isValid()) {
resetCurrentTrack();
}
Q_EMIT tracksCountChanged();
Q_EMIT persistentStateChanged();
if (triggerPlay == ElisaUtils::TriggerPlay) {
Q_EMIT ensurePlay();
}
}
void MediaPlayList::replaceAndPlay(qulonglong newTrackId)
{
clearPlayList();
enqueue(MediaPlayListEntry(newTrackId));
......
......@@ -23,6 +23,7 @@
#include "musicaudiotrack.h"
#include "musicalbum.h"
#include "musicartist.h"
#include "elisautils.h"
#include <QAbstractListModel>
#include <QVector>
......@@ -228,6 +229,20 @@ public Q_SLOTS:
void enqueue(const QStringList &files);
void enqueue(const QList<qulonglong> &newTrackId);
void enqueue(const QList<MusicAlbum> &albums,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay);
void enqueue(const QList<MusicAudioTrack> &tracks,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay);
void enqueueArtists(const QList<QString> &artistName,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay);
void enqueueAndPlay(const QStringList &files);
void replaceAndPlay(qulonglong newTrackId);
......
......@@ -20,9 +20,12 @@
#include "abstractmediaproxymodel.h"
#include <QWriteLocker>
AbstractMediaProxyModel::AbstractMediaProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
setFilterCaseSensitivity(Qt::CaseInsensitive);
mThreadPool.setMaxThreadCount(1);
}
AbstractMediaProxyModel::~AbstractMediaProxyModel()
......@@ -40,6 +43,8 @@ int AbstractMediaProxyModel::filterRating() const
void AbstractMediaProxyModel::setFilterText(const QString &filterText)
{
QWriteLocker writeLocker(&mDataLock);
if (mFilterText == filterText)
return;
......@@ -56,6 +61,8 @@ void AbstractMediaProxyModel::setFilterText(const QString &filterText)
void AbstractMediaProxyModel::setFilterRating(int filterRating)
{
QWriteLocker writeLocker(&mDataLock);
if (mFilterRating == filterRating) {
return;
}
......@@ -67,22 +74,4 @@ void AbstractMediaProxyModel::setFilterRating(int filterRating)
Q_EMIT filterRatingChanged(filterRating);
}
MediaPlayList *AbstractMediaProxyModel::mediaPlayList() const
{
return mMediaPlayList;
}
void AbstractMediaProxyModel::setMediaPlayList(MediaPlayList *playList)
{
mMediaPlayList = playList;
Q_EMIT mediaPlayListChanged(mMediaPlayList);
}
void AbstractMediaProxyModel::replaceAndPlayOfPlayList()
{
this->mediaPlayList()->clearPlayList();
this->enqueueToPlayList();
Q_EMIT this->mediaPlayList()->ensurePlay();
}
#include "moc_abstractmediaproxymodel.cpp"
......@@ -21,10 +21,12 @@
#ifndef ABSTRACTMEDIAPROXYMODEL_H
#define ABSTRACTMEDIAPROXYMODEL_H
#include "mediaplaylist.h"
#include <QSortFilterProxyModel>
#include <QRegularExpression>
#include "mediaplaylist.h"
#include <QReadWriteLock>
#include <QThreadPool>
class MediaPlayList;
......@@ -43,12 +45,6 @@ class AbstractMediaProxyModel : public QSortFilterProxyModel
WRITE setFilterRating
NOTIFY filterRatingChanged)
Q_PROPERTY(MediaPlayList* mediaPlayList
READ mediaPlayList
WRITE setMediaPlayList
NOTIFY mediaPlayListChanged)
public:
explicit AbstractMediaProxyModel(QObject *parent = nullptr);
......@@ -59,28 +55,18 @@ public:
int filterRating() const;
MediaPlayList *mediaPlayList() const;
public Q_SLOTS:
void setMediaPlayList(MediaPlayList *playList);
void setFilterText(const QString &filterText);
void setFilterRating(int filterRating);
virtual void enqueueToPlayList() = 0;
void replaceAndPlayOfPlayList();
Q_SIGNALS:
void filterTextChanged(const QString &filterText);
void filterRatingChanged(int filterRating);
void mediaPlayListChanged(MediaPlayList *mediaPlayList);
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override = 0;
......@@ -91,7 +77,9 @@ protected:
QRegularExpression mFilterExpression;
MediaPlayList* mMediaPlayList;
QReadWriteLock mDataLock;
QThreadPool mThreadPool;
};
......
......@@ -22,6 +22,17 @@
#include "allalbumsmodel.h"
#include <QReadLocker>
#include <QtConcurrentRun>
AllAlbumsProxyModel::AllAlbumsProxyModel(QObject *parent) : AbstractMediaProxyModel(parent)
{
}
AllAlbumsProxyModel::~AllAlbumsProxyModel()
{
}
bool AllAlbumsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
bool result = false;
......@@ -70,14 +81,35 @@ bool AllAlbumsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &so
void AllAlbumsProxyModel::enqueueToPlayList()
{
MediaPlayList *playList = AbstractMediaProxyModel::mediaPlayList();
for (int columnIndex = 0, columnCount = this->columnCount(); columnIndex < columnCount; ++columnIndex) {
for (int rowIndex = 0, rowCount = this->rowCount(); rowIndex < rowCount; ++rowIndex) {
auto currentIndex = this->index(rowIndex, columnIndex);
playList->enqueue(this->data(currentIndex,AllAlbumsModel::ContainerDataRole).value<MusicAlbum>());
QtConcurrent::run(&mThreadPool, [=] () {
QReadLocker locker(&mDataLock);
auto allAlbums = QList<MusicAlbum>();
allAlbums.reserve(rowCount());
for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) {
auto currentIndex = index(rowIndex, 0);
allAlbums.push_back(data(currentIndex, AllAlbumsModel::ContainerDataRole). value<MusicAlbum>());
}
}
Q_EMIT albumToEnqueue(allAlbums,
ElisaUtils::AppendPlayList,
ElisaUtils::DoNotTriggerPlay);
});
}
void AllAlbumsProxyModel::replaceAndPlayOfPlayList()
{
QtConcurrent::run(&mThreadPool, [=] () {
QReadLocker locker(&mDataLock);
auto allAlbums = QList<MusicAlbum>();
allAlbums.reserve(rowCount());
for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) {
auto currentIndex = index(rowIndex, 0);
allAlbums.push_back(data(currentIndex, AllAlbumsModel::ContainerDataRole). value<MusicAlbum>());
}
Q_EMIT albumToEnqueue(allAlbums,
ElisaUtils::ReplacePlayList,
ElisaUtils::TriggerPlay);
});
}
#include "moc_allalbumsproxymodel.cpp"
/*
* Copyright 2016-2017 Matthieu Gallien <matthieu_gallien@yahoo.fr>
* Copyright 2016-2018 Matthieu Gallien <matthieu_gallien@yahoo.fr>
* Copyright 2017 Alexander Stippich <a.stippich@gmx.net>
*
* This library is free software; you can redistribute it and/or
......@@ -22,14 +22,30 @@
#define ALLALBUMSPROXYMODEL_H
#include "abstractmediaproxymodel.h"
#include "elisautils.h"
class AllAlbumsProxyModel : public AbstractMediaProxyModel
{
Q_OBJECT
public:
AllAlbumsProxyModel(QObject *parent = nullptr);
~AllAlbumsProxyModel() override;
Q_SIGNALS:
void albumToEnqueue(QList<MusicAlbum> newAlbums,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay);
public Q_SLOTS:
void enqueueToPlayList() override;
void enqueueToPlayList();
void replaceAndPlayOfPlayList();
protected:
......
......@@ -21,6 +21,17 @@
#include "allartistsmodel.h"
#include <QReadLocker>
#include <QtConcurrentRun>
AllArtistsProxyModel::AllArtistsProxyModel(QObject *parent) : AbstractMediaProxyModel(parent)
{
}
AllArtistsProxyModel::~AllArtistsProxyModel()
{
}
bool AllArtistsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
bool result = false;
......@@ -49,13 +60,34 @@ bool AllArtistsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &s
void AllArtistsProxyModel::enqueueToPlayList()
{
MediaPlayList *playList = AbstractMediaProxyModel::mediaPlayList();
for (int columnIndex = 0, columnCount = this->columnCount(); columnIndex < columnCount; ++columnIndex) {
for (int rowIndex = 0, rowCount = this->rowCount(); rowIndex < rowCount; ++rowIndex) {
auto currentIndex = this->index(rowIndex, columnIndex);
playList->enqueue(this->data(currentIndex,AllArtistsModel::NameRole).toString());
QtConcurrent::run(&mThreadPool, [=] () {
QReadLocker locker(&mDataLock);
auto allArtists = QStringList();
allArtists.reserve(rowCount());
for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) {
auto currentIndex = index(rowIndex, 0);
allArtists.push_back(data(currentIndex, AllArtistsModel::NameRole).toString());
}
}
Q_EMIT artistToEnqueue(allArtists,
ElisaUtils::AppendPlayList,
ElisaUtils::DoNotTriggerPlay);
});
}
void AllArtistsProxyModel::replaceAndPlayOfPlayList()
{
QtConcurrent::run(&mThreadPool, [=] () {
QReadLocker locker(&mDataLock);
auto allArtists = QStringList();
allArtists.reserve(rowCount());
for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) {
auto currentIndex = index(rowIndex, 0);
allArtists.push_back(data(currentIndex, AllArtistsModel::NameRole).toString());
}
Q_EMIT artistToEnqueue(allArtists,
ElisaUtils::ReplacePlayList,
ElisaUtils::TriggerPlay);
});
}
......
/*
* Copyright 2017 Alexander Stippich <a.stippich@gmx.net>
* Copyright 2018 Matthieu Gallien <matthieu_gallien@yahoo.fr>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -21,18 +22,35 @@
#define ALLARTISTSPROXYMODEL_H
#include "abstractmediaproxymodel.h"
#include "elisautils.h"
class AllArtistsProxyModel : public AbstractMediaProxyModel
{
Q_OBJECT
public:
AllArtistsProxyModel(QObject *parent = nullptr);
~AllArtistsProxyModel() override;
Q_SIGNALS:
void artistToEnqueue(QList<QString> artistNames,
ElisaUtils::PlayListEnqueueMode enqueueMode,
ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay);
public Q_SLOTS:
void enqueueToPlayList() override;
void enqueueToPlayList();
void replaceAndPlayOfPlayList();
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
};
#endif // ALLARTISTSPROXYMODEL_H
......@@ -21,6 +21,17 @@
#include "alltracksmodel.h"
#include <QReadLocker>
#include <QtConcurrentRun>
AllTracksProxyModel::AllTracksProxyModel(QObject *parent) : AbstractMediaProxyModel(parent)
{
}
AllTracksProxyModel::~AllTracksProxyModel()
{
}
bool AllTracksProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
bool result = false;
......@@ -61,12 +72,35 @@ bool AllTracksProxyModel::filterAcceptsRow(int source_row, const QModelIndex &so
void AllTracksProxyModel::enqueueToPlayList()
{
MediaPlayList *playList = AbstractMediaProxyModel::mediaPlayList();
for (int columnIndex = 0, columnCount = this->columnCount(); columnIndex < columnCount; ++columnIndex) {
for (int rowIndex = 0, rowCount = this->rowCount(); rowIndex < rowCount; ++rowIndex) {
auto currentIndex = this->index(rowIndex, columnIndex);
playList->enqueue(this->data(currentIndex,AllTracksModel::DatabaseIdRole).toULongLong());
QtConcurrent::run(&mThreadPool, [=] () {
QReadLocker locker(&mDataLock);
auto allTracks = QList<MusicAudioTrack>();
allTracks.reserve(rowCount());
for (int rowIndex = 0, maxRowCount = rowCount(); rowIndex < maxRowCount; ++rowIndex) {
auto currentIndex = index(rowIndex, 0);
allTracks.push_back(data(currentIndex, AllTracksModel::ContainerDataRole).value<MusicAudioTrack>());
}
}
Q_EMIT trackToEnqueue(allTracks,
ElisaUtils::AppendPlayList,
ElisaUtils::DoNotTriggerPlay);
});
}