Add smart playlists, ratings and Qobuz

Fixes #259
Fixes #264
This commit is contained in:
Jonas Kvinge
2020-09-17 17:50:17 +02:00
parent fdf96e8342
commit 89d6b7cec0
102 changed files with 10949 additions and 525 deletions

View File

@@ -84,6 +84,10 @@
#include "songplaylistitem.h"
#include "tagreadermessages.pb.h"
#include "smartplaylists/playlistgenerator.h"
#include "smartplaylists/playlistgeneratorinserter.h"
#include "smartplaylists/playlistgeneratormimedata.h"
#include "internet/internetplaylistitem.h"
#include "internet/internetsongmimedata.h"
@@ -271,6 +275,9 @@ QVariant Playlist::data(const QModelIndex &idx, int role) const {
case Role_QueuePosition:
return queue_->PositionOf(idx);
case Role_CanSetRating:
return idx.column() == Column_Rating && items_[idx.row()]->IsLocalCollectionItem() && items_[idx.row()]->Metadata().id() != -1;
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::DisplayRole: {
@@ -314,6 +321,8 @@ QVariant Playlist::data(const QModelIndex &idx, int role) const {
case Column_Source: return song.source();
case Column_Rating: return song.rating();
}
return QVariant();
@@ -331,6 +340,9 @@ QVariant Playlist::data(const QModelIndex &idx, int role) const {
if (items_[idx.row()]->HasCurrentForegroundColor()) {
return QBrush(items_[idx.row()]->GetCurrentForegroundColor());
}
if (idx.row() < dynamic_history_length()) {
return QBrush(kDynamicHistoryColor);
}
return QVariant();
@@ -634,6 +646,35 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b
InformOfCurrentSongChange(autoscroll);
}
// The structure of a dynamic playlist is as follows:
// history - active song - future
// We have to ensure that this invariant is maintained.
if (dynamic_playlist_ && current_item_index_.isValid()) {
// When advancing to the next track
if (i > old_current_item_index.row()) {
// Move the new item one position ahead of the last item in the history.
MoveItemWithoutUndo(current_item_index_.row(), dynamic_history_length());
// Compute the number of new items that have to be inserted. This is not
// necessarily 1 because the user might have added or removed items
// manually. Note that the future excludes the current item.
const int count = dynamic_history_length() + 1 + dynamic_playlist_->GetDynamicFuture() - items_.count();
if (count > 0) {
InsertDynamicItems(count);
}
// Shrink the history, again this is not necessarily by 1, because the
// user might have moved items by hand.
const int remove_count = dynamic_history_length() - dynamic_playlist_->GetDynamicHistory();
if (0 < remove_count) RemoveItemsWithoutUndo(0, remove_count);
}
// the above actions make all commands on the undo stack invalid, so we
// better clear it.
undo_stack_->clear();
}
if (current_item_index_.isValid()) {
last_played_item_index_ = current_item_index_;
Save();
@@ -643,6 +684,16 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b
}
void Playlist::InsertDynamicItems(const int count) {
PlaylistGeneratorInserter* inserter = new PlaylistGeneratorInserter(task_manager_, collection_, this);
connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(inserter, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
inserter->Load(this, -1, false, false, false, dynamic_playlist_, count);
}
Qt::ItemFlags Playlist::flags(const QModelIndex &idx) const {
if (idx.isValid()) {
@@ -697,6 +748,9 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
else if (const InternetSongMimeData* internet_song_data = qobject_cast<const InternetSongMimeData*>(data)) {
InsertInternetItems(internet_song_data->service, internet_song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
}
else if (const PlaylistGeneratorMimeData *generator_data = qobject_cast<const PlaylistGeneratorMimeData*>(data)) {
InsertSmartPlaylist(generator_data->generator_, row, play_now, enqueue_now, enqueue_next_now);
}
else if (data->hasFormat(kRowsMimetype)) {
// Dragged from the playlist
// Rearranging it is tricky...
@@ -768,6 +822,34 @@ void Playlist::InsertUrls(const QList<QUrl> &urls, const int pos, const bool pla
}
void Playlist::InsertSmartPlaylist(PlaylistGeneratorPtr generator, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) {
// Hack: If the generator hasn't got a collection set then use the main one
if (!generator->collection()) {
generator->set_collection(collection_);
}
PlaylistGeneratorInserter *inserter = new PlaylistGeneratorInserter(task_manager_, collection_, this);
connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
inserter->Load(this, pos, play_now, enqueue, enqueue_next, generator);
if (generator->is_dynamic()) {
TurnOnDynamicPlaylist(generator);
}
}
void Playlist::TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen) {
dynamic_playlist_ = gen;
playlist_sequence_->SetUsingDynamicPlaylist(true);
ShuffleModeChanged(PlaylistSequence::Shuffle_Off);
emit DynamicModeChanged(true);
Save();
}
void Playlist::MoveItemWithoutUndo(const int source, const int dest) {
MoveItemsWithoutUndo(QList<int>() << source, dest);
}
@@ -1139,6 +1221,9 @@ bool Playlist::CompareItems(const int column, const Qt::SortOrder order, std::sh
case Column_Comment: strcmp(comment);
case Column_Source: cmp(source);
case Column_Rating: cmp(rating);
default: qLog(Error) << "No such column" << column;
}
@@ -1196,6 +1281,7 @@ QString Playlist::column_name(Column column) {
case Column_Comment: return tr("Comment");
case Column_Source: return tr("Source");
case Column_Mood: return tr("Mood");
case Column_Rating: return tr("Rating");
default: qLog(Error) << "No such column" << column;;
}
return "";
@@ -1226,6 +1312,9 @@ void Playlist::sort(int column, Qt::SortOrder order) {
PlaylistItemList new_items(items_);
PlaylistItemList::iterator begin = new_items.begin();
if (dynamic_playlist_ && current_item_index_.isValid())
begin += current_item_index_.row() + 1;
if (column == Column_Album) {
// When sorting by album, also take into account discs and tracks.
std::stable_sort(begin, new_items.end(), std::bind(&Playlist::CompareItems, Column_Track, order, _1, _2));
@@ -1291,7 +1380,7 @@ void Playlist::Save() const {
if (!backend_ || is_loading_) return;
backend_->SavePlaylistAsync(id_, items_, last_played_row());
backend_->SavePlaylistAsync(id_, items_, last_played_row(), dynamic_playlist_);
}
@@ -1334,6 +1423,22 @@ void Playlist::ItemsLoaded(QFuture<PlaylistItemList> future) {
// The newly loaded list of items might be shorter than it was before so look out for a bad last_played index
last_played_item_index_ = p.last_played == -1 || p.last_played >= rowCount() ? QModelIndex() : index(p.last_played);
if (p.dynamic_type == PlaylistGenerator::Type_Query) {
PlaylistGeneratorPtr gen = PlaylistGenerator::Create(p.dynamic_type);
if (gen) {
CollectionBackend *backend = nullptr;
if (p.dynamic_backend == collection_->songs_table()) backend = collection_;
if (backend) {
gen->set_collection(backend);
gen->Load(p.dynamic_data);
TurnOnDynamicPlaylist(gen);
}
}
}
emit RestoreFinished();
QSettings s;
@@ -1572,10 +1677,29 @@ void Playlist::Clear() {
undo_stack_->push(new PlaylistUndoCommands::RemoveItems(this, 0, count));
}
TurnOffDynamicPlaylist();
Save();
}
void Playlist::RepopulateDynamicPlaylist() {
if (!dynamic_playlist_) return;
RemoveItemsNotInQueue();
InsertSmartPlaylist(dynamic_playlist_);
}
void Playlist::ExpandDynamicPlaylist() {
if (!dynamic_playlist_) return;
InsertDynamicItems(5);
}
void Playlist::RemoveItemsNotInQueue() {
if (queue_->is_empty() && !current_item_index_.isValid()) {
@@ -1650,6 +1774,9 @@ void Playlist::Shuffle() {
begin = 1;
}
if (dynamic_playlist_ && current_item_index_.isValid())
begin += current_item_index_.row() + 1;
const int count = items_.count();
for (int i = begin; i < count; ++i) {
int new_pos = i + (rand() % (count - i));
@@ -2036,3 +2163,48 @@ void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &
}
}
int Playlist::dynamic_history_length() const {
return dynamic_playlist_ && last_played_item_index_.isValid() ? last_played_item_index_.row() + 1 : 0;
}
void Playlist::TurnOffDynamicPlaylist() {
dynamic_playlist_.reset();
if (playlist_sequence_) {
playlist_sequence_->SetUsingDynamicPlaylist(false);
ShuffleModeChanged(playlist_sequence_->shuffle_mode());
}
emit DynamicModeChanged(false);
Save();
}
void Playlist::RateSong(const QModelIndex &idx, const double rating) {
if (has_item_at(idx.row())) {
PlaylistItemPtr item = item_at(idx.row());
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
collection_->UpdateSongRatingAsync(item->Metadata().id(), rating);
}
}
}
void Playlist::RateSongs(const QModelIndexList &index_list, const double rating) {
QList<int> id_list;
for (const QModelIndex &idx : index_list) {
const int row = idx.row();
if (has_item_at(row)) {
PlaylistItemPtr item = item_at(row);
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
id_list << item->Metadata().id();
}
}
}
collection_->UpdateSongsRatingAsync(id_list, rating);
}