Add tidal support

This commit is contained in:
Jonas Kvinge
2018-08-09 18:10:03 +02:00
parent 26062bd07b
commit 820124f9e1
74 changed files with 5420 additions and 273 deletions

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#include "config.h"
@@ -84,6 +84,11 @@
#include "songplaylistitem.h"
#include "tagreadermessages.pb.h"
#include "internet/internetmodel.h"
#include "internet/internetplaylistitem.h"
#include "internet/internetmimedata.h"
#include "internet/internetsongmimedata.h"
using std::placeholders::_1;
using std::placeholders::_2;
using std::shared_ptr;
@@ -153,7 +158,7 @@ Playlist::~Playlist() {
}
template <typename T>
void Playlist::InsertSongItems(const SongList &songs, int pos, bool play_now, bool enqueue) {
void Playlist::InsertSongItems(const SongList &songs, int pos, bool play_now, bool enqueue, bool enqueue_next) {
PlaylistItemList items;
@@ -161,7 +166,7 @@ void Playlist::InsertSongItems(const SongList &songs, int pos, bool play_now, bo
items << PlaylistItemPtr(new T(song));
}
InsertItems(items, pos, play_now, enqueue);
InsertItems(items, pos, play_now, enqueue, enqueue_next);
}
@@ -282,16 +287,15 @@ QVariant Playlist::data(const QModelIndex &index, int role) const {
case Column_AlbumArtist: return song.playlist_albumartist();
case Column_Composer: return song.composer();
case Column_Performer: return song.performer();
case Column_Grouping: return song.grouping();
case Column_Grouping: return song.grouping();
case Column_PlayCount: return song.playcount();
case Column_SkipCount: return song.skipcount();
case Column_LastPlayed: return song.lastplayed();
case Column_Samplerate: return song.samplerate();
case Column_Bitdepth: return song.bitdepth();
case Column_Bitrate: return song.bitrate();
case Column_SamplerateBitdepth: return song.SampleRateBitDepthToText();
case Column_Bitdepth: return song.bitdepth();
case Column_Bitrate: return song.bitrate();
case Column_Filename: return song.url();
case Column_BaseFilename: return song.basefilename();
@@ -304,7 +308,7 @@ QVariant Playlist::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) return song.comment().simplified();
return song.comment();
//case Column_Source: return item->Url();
case Column_Source: return item->Url();
}
@@ -323,9 +327,7 @@ QVariant Playlist::data(const QModelIndex &index, int role) const {
if (items_[index.row()]->HasCurrentForegroundColor()) {
return QBrush(items_[index.row()]->GetCurrentForegroundColor());
}
//if (index.row() < dynamic_history_length()) {
//return QBrush(kDynamicHistoryColor);
//}
return QVariant();
case Qt::BackgroundRole:
@@ -562,7 +564,7 @@ int Playlist::previous_row(bool ignore_repeat_track) const {
void Playlist::set_current_row(int i, bool is_stopping) {
QModelIndex old_current_item_index = current_item_index_;
//ClearStreamMetadata();
ClearStreamMetadata();
current_item_index_ = QPersistentModelIndex(index(i, 0, QModelIndex()));
@@ -636,6 +638,7 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
bool play_now = false;
bool enqueue_now = false;
bool enqueue_next_now = false;
if (const MimeData *mime_data = qobject_cast<const MimeData*>(data)) {
if (mime_data->clear_first_) {
@@ -643,6 +646,7 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
}
play_now = mime_data->play_now_;
enqueue_now = mime_data->enqueue_now_;
enqueue_next_now = mime_data->enqueue_next_now_;
}
if (const SongMimeData *song_data = qobject_cast<const SongMimeData*>(data)) {
@@ -651,11 +655,13 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
if (song_data->backend && song_data->backend->songs_table() == SCollection::kSongsTable)
InsertSongItems<CollectionPlaylistItem>(song_data->songs, row, play_now, enqueue_now);
else
InsertSongItems<SongPlaylistItem>(song_data->songs, row, play_now, enqueue_now);
InsertSongItems<SongPlaylistItem>(song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
}
else if (const PlaylistItemMimeData *item_data = qobject_cast<const PlaylistItemMimeData*>(data)) {
InsertItems(item_data->items_, row, play_now, enqueue_now);
InsertItems(item_data->items_, row, play_now, enqueue_now, enqueue_next_now);
}
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 (data->hasFormat(kRowsMimetype)) {
// Dragged from the playlist
@@ -719,7 +725,7 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
}
void Playlist::InsertUrls(const QList<QUrl> &urls, int pos, bool play_now, bool enqueue) {
void Playlist::InsertUrls(const QList<QUrl> &urls, int pos, bool play_now, bool enqueue, bool enqueue_next) {
SongLoaderInserter *inserter = new SongLoaderInserter(task_manager_, collection_, backend_->app()->player());
connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
@@ -832,7 +838,7 @@ void Playlist::MoveItemsWithoutUndo(int start, const QList<int> &dest_rows) {
}
void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_now, bool enqueue) {
void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_now, bool enqueue, bool enqueue_next) {
if (itemsIn.isEmpty())
return;
@@ -932,25 +938,37 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemList &items, int pos, bo
}
void Playlist::InsertCollectionItems(const SongList &songs, int pos, bool play_now, bool enqueue) {
InsertSongItems<CollectionPlaylistItem>(songs, pos, play_now, enqueue);
void Playlist::InsertCollectionItems(const SongList &songs, int pos, bool play_now, bool enqueue, bool enqueue_next) {
InsertSongItems<CollectionPlaylistItem>(songs, pos, play_now, enqueue, enqueue_next);
}
void Playlist::InsertSongs(const SongList &songs, int pos, bool play_now, bool enqueue) {
InsertSongItems<SongPlaylistItem>(songs, pos, play_now, enqueue);
void Playlist::InsertSongs(const SongList &songs, int pos, bool play_now, bool enqueue, bool enqueue_next) {
InsertSongItems<SongPlaylistItem>(songs, pos, play_now, enqueue, enqueue_next);
}
void Playlist::InsertSongsOrCollectionItems(const SongList &songs, int pos, bool play_now, bool enqueue) {
void Playlist::InsertSongsOrCollectionItems(const SongList &songs, int pos, bool play_now, bool enqueue, bool enqueue_next) {
PlaylistItemList items;
for (const Song &song : songs) {
if (song.is_collection_song()) {
items << PlaylistItemPtr(new CollectionPlaylistItem(song));
} else {
}
else {
items << PlaylistItemPtr(new SongPlaylistItem(song));
}
}
InsertItems(items, pos, play_now, enqueue);
InsertItems(items, pos, play_now, enqueue, enqueue_next);
}
void Playlist::InsertInternetItems(InternetService *service, const SongList &songs, int pos, bool play_now, bool enqueue, bool enqueue_next) {
PlaylistItemList playlist_items;
for (const Song &song : songs) {
playlist_items << shared_ptr<PlaylistItem>(new InternetPlaylistItem(service, song));
}
InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next);
}
@@ -973,8 +991,10 @@ void Playlist::UpdateItems(const SongList &songs) {
PlaylistItemPtr &item = items_[i];
if (item->Metadata().url() == song.url() &&
(item->Metadata().filetype() == Song::Type_Unknown ||
// Stream may change and may need to be updated too
item->Metadata().filetype() == Song::Type_Stream ||
// And CD tracks as well (tags are loaded in a second step)
item->Metadata().filetype() == Song::Type_Cdda)) {
item->Metadata().filetype() == Song::Type_CDDA)) {
PlaylistItemPtr new_item;
if (song.is_collection_song()) {
new_item = PlaylistItemPtr(new CollectionPlaylistItem(song));
@@ -1069,9 +1089,7 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr<Playlist
case Column_Bitrate: cmp(bitrate);
case Column_Samplerate: cmp(samplerate);
case Column_Bitdepth: cmp(bitdepth);
case Column_SamplerateBitdepth:
return QString::localeAwareCompare(a->Metadata().SampleRateBitDepthToText().toLower(), b->Metadata().SampleRateBitDepthToText().toLower()) < 0;
case Column_Bitdepth: cmp(bitdepth);
case Column_Filename:
return (QString::localeAwareCompare(a->Url().path().toLower(), b->Url().path().toLower()) < 0);
case Column_BaseFilename: cmp(basefilename);
@@ -1081,7 +1099,7 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr<Playlist
case Column_DateCreated: cmp(ctime);
case Column_Comment: strcmp(comment);
//case Column_Source: cmp(url);
case Column_Source: cmp(url);
}
#undef cmp
@@ -1126,7 +1144,6 @@ QString Playlist::column_name(Column column) {
case Column_Samplerate: return tr("Sample rate");
case Column_Bitdepth: return tr("Bit depth");
case Column_SamplerateBitdepth: return tr("Sample rate B");
case Column_Bitrate: return tr("Bitrate");
case Column_Filename: return tr("File name");
@@ -1137,7 +1154,7 @@ QString Playlist::column_name(Column column) {
case Column_DateCreated: return tr("Date created");
case Column_Comment: return tr("Comment");
//case Column_Source: return tr("Source");
case Column_Source: return tr("Source");
default: return QString();
}
return "";
@@ -1757,6 +1774,7 @@ void Playlist::InvalidateDeletedSongs() {
PlaylistItemPtr item = items_[row];
Song song = item->Metadata();
if (!song.is_stream()) {
bool exists = QFile::exists(song.url().toLocalFile());
if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
@@ -1768,6 +1786,7 @@ void Playlist::InvalidateDeletedSongs() {
item->RemoveForegroundColor(kInvalidSongPriority);
invalidated_rows.append(row);
}
}
}
ReloadItems(invalidated_rows);

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#ifndef PLAYLIST_H
@@ -54,6 +54,8 @@ class PlaylistBackend;
class PlaylistFilter;
class Queue;
class TaskManager;
class InternetModel;
class InternetService;
namespace PlaylistUndoCommands {
class InsertItems;
@@ -110,7 +112,6 @@ class Playlist : public QAbstractListModel {
Column_Genre,
Column_Samplerate,
Column_Bitdepth,
Column_SamplerateBitdepth,
Column_Bitrate,
Column_Filename,
Column_BaseFilename,
@@ -123,6 +124,7 @@ class Playlist : public QAbstractListModel {
Column_LastPlayed,
Column_Comment,
Column_Grouping,
Column_Source,
ColumnCount
};
@@ -212,10 +214,11 @@ class Playlist : public QAbstractListModel {
QUndoStack *undo_stack() const { return undo_stack_; }
// Changing the playlist
void InsertItems (const PlaylistItemList &items, int pos = -1, bool play_now = false, bool enqueue = false);
void InsertCollectionItems (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false);
void InsertSongs (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false);
void InsertSongsOrCollectionItems (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false);
void InsertItems (const PlaylistItemList &items, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
void InsertCollectionItems (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
void InsertSongs (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
void InsertSongsOrCollectionItems (const SongList &items, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
void InsertInternetItems(InternetService* service, const SongList& songs, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
void ReshuffleIndices();
@@ -276,7 +279,7 @@ class Playlist : public QAbstractListModel {
void SetColumnAlignment(const ColumnAlignmentMap &alignment);
void InsertUrls(const QList<QUrl> &urls, int pos = -1, bool play_now = false, bool enqueue = false);
void InsertUrls(const QList<QUrl> &urls, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
// Removes items with given indices from the playlist. This operation is not undoable.
void RemoveItemsWithoutUndo(const QList<int> &indices);
@@ -302,7 +305,7 @@ private:
bool FilterContainsVirtualIndex(int i) const;
template <typename T>
void InsertSongItems(const SongList &songs, int pos, bool play_now, bool enqueue);
void InsertSongItems(const SongList &songs, int pos, bool play_now, bool enqueue, bool enqueue_next = false);
// Modify the playlist without changing the undo stack. These are used by our friends in PlaylistUndoCommands
void InsertItemsWithoutUndo(const PlaylistItemList &items, int pos, bool enqueue = false);

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#include <memory>
@@ -145,7 +145,7 @@ QSqlQuery PlaylistBackend::GetPlaylistRows(int playlist) {
" p.ROWID, " +
Song::JoinSpec("p") +
","
" p.type"
" p.type, p.internet_service"
" FROM playlist_items AS p"
" LEFT JOIN songs"
" ON p.collection_id = songs.ROWID"
@@ -279,7 +279,7 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList &items,
QSqlQuery clear(db);
clear.prepare("DELETE FROM playlist_items WHERE playlist = :playlist");
QSqlQuery insert(db);
insert.prepare("INSERT INTO playlist_items (playlist, type, collection_id, " + Song::kColumnSpec + ") VALUES (:playlist, :type, :collection_id, " + Song::kBindSpec + ")");
insert.prepare("INSERT INTO playlist_items (playlist, type, collection_id, internet_service, " + Song::kColumnSpec + ") VALUES (:playlist, :type, :collection_id, :internet_service, " + Song::kBindSpec + ")");
QSqlQuery update(db);
update.prepare("UPDATE playlists SET last_played=:last_played WHERE ROWID=:playlist");

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#include "config.h"
@@ -39,6 +39,7 @@
#include <QString>
#include <QStringBuilder>
#include <QUrl>
#include <QRegExp>
#include <QIcon>
#include <QPixmap>
#include <QPainter>
@@ -460,6 +461,12 @@ QPixmap SongSourceDelegate::LookupPixmap(const QUrl &url, const QSize &size) con
else if (url.scheme() == "cdda") {
icon = IconLoader::Load("cd");
}
else if (url.scheme() == "http" || url.scheme() == "https") {
if (url.host().contains(QRegExp(".*.tidal.com")))
icon = IconLoader::Load("tidal");
else
icon = IconLoader::Load("download");
}
else {
icon = IconLoader::Load("folder-sound");
}

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#include "config.h"
@@ -445,9 +445,6 @@ FilterTree *FilterParser::createSearchTermTreeNode(
if (columns_[col] == Playlist::Column_Length) {
search_value = parseTime(search);
}
//else if (columns_[col] == Playlist::Column_Rating) {
//search_value = static_cast<int>(search.toDouble() * 2.0 + 0.5);
//}
else {
search_value = search.toInt();
}

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#include "config.h"
@@ -37,6 +37,8 @@
#include "playlistitem.h"
#include "songplaylistitem.h"
#include "internet/internetplaylistitem.h"
PlaylistItem::~PlaylistItem() {
}
@@ -44,11 +46,13 @@ PlaylistItem* PlaylistItem::NewFromType(const QString &type) {
if (type == "Collection") return new CollectionPlaylistItem(type);
else if (type == "File") return new SongPlaylistItem(type);
else if (type == "Internet") return new InternetPlaylistItem("Internet");
else if (type == "Tidal") return new InternetPlaylistItem("Tidal");
qLog(Warning) << "Invalid PlaylistItem type:" << type;
return nullptr;
}
PlaylistItem* PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) {
@@ -65,6 +69,7 @@ void PlaylistItem::BindToQuery(QSqlQuery *query) const {
query->bindValue(":type", type());
query->bindValue(":collection_id", DatabaseValue(Column_CollectionId));
query->bindValue(":internet_service", DatabaseValue(Column_InternetService));
DatabaseSongMetadata().BindToQuery(query);
@@ -119,3 +124,4 @@ bool PlaylistItem::HasCurrentForegroundColor() const {
}
void PlaylistItem::SetShouldSkip(bool val) { should_skip_ = val; }
bool PlaylistItem::GetShouldSkip() const { return should_skip_; }

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#ifndef PLAYLISTITEM_H
@@ -104,7 +104,7 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
protected:
bool should_skip_;
enum DatabaseColumn { Column_CollectionId, Column_InternetService, };
enum DatabaseColumn { Column_CollectionId, Column_InternetService };
virtual QVariant DatabaseValue(DatabaseColumn) const {
return QVariant(QVariant::String);
@@ -126,3 +126,4 @@ Q_DECLARE_METATYPE(QList<PlaylistItemPtr>)
Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::Options)
#endif // PLAYLISTITEM_H

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#include "config.h"
@@ -228,20 +228,16 @@ void PlaylistView::SetItemDelegates(CollectionBackend *backend) {
setItemDelegateForColumn(Playlist::Column_Samplerate, new PlaylistDelegateBase(this, ("Hz")));
setItemDelegateForColumn(Playlist::Column_Bitdepth, new PlaylistDelegateBase(this, ("Bit")));
setItemDelegateForColumn(Playlist::Column_Bitrate, new PlaylistDelegateBase(this, tr("kbps")));
setItemDelegateForColumn(Playlist::Column_SamplerateBitdepth, new SamplerateBitdepthItemDelegate(this));
setItemDelegateForColumn(Playlist::Column_Filename, new NativeSeparatorsDelegate(this));
setItemDelegateForColumn(Playlist::Column_LastPlayed, new LastPlayedItemDelegate(this));
#if 0
if (app_ && app_->player()) {
setItemDelegateForColumn(Playlist::Column_Source, new SongSourceDelegate(this, app_->player()));
}
else {
header_->HideSection(Playlist::Column_Source);
}
#endif
}
@@ -946,7 +942,8 @@ void PlaylistView::ReloadSettings() {
header_->SetColumnWidth(Playlist::Column_Album, 0.10);
header_->SetColumnWidth(Playlist::Column_Length, 0.03);
header_->SetColumnWidth(Playlist::Column_Bitrate, 0.07);
header_->SetColumnWidth(Playlist::Column_SamplerateBitdepth, 0.07);
header_->SetColumnWidth(Playlist::Column_Samplerate, 0.07);
header_->SetColumnWidth(Playlist::Column_Bitdepth, 0.07);
header_->SetColumnWidth(Playlist::Column_Filetype, 0.06);
setting_initial_header_layout_ = false;
@@ -1089,7 +1086,6 @@ ColumnAlignmentMap PlaylistView::DefaultColumnAlignment() {
ret[Playlist::Column_Bitrate] =
ret[Playlist::Column_Samplerate] =
ret[Playlist::Column_Bitdepth] =
ret[Playlist::Column_SamplerateBitdepth] =
ret[Playlist::Column_Filesize] =
ret[Playlist::Column_PlayCount] =
ret[Playlist::Column_SkipCount] =
@@ -1216,8 +1212,7 @@ void PlaylistView::focusInEvent(QFocusEvent *event) {
QTreeView::focusInEvent(event);
if (event->reason() == Qt::TabFocusReason ||
event->reason() == Qt::BacktabFocusReason) {
if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason) {
// If there's a current item but no selection it probably means the list was filtered, and the selected item does not match the filter.
// If there's only 1 item in the view it is now impossible to select that item without using the mouse.
const QModelIndex &current = selectionModel()->currentIndex();

View File

@@ -15,7 +15,7 @@
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
#ifndef PLAYLISTVIEW_H
@@ -75,7 +75,7 @@ class PlaylistHeader;
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
// This proxy style uses QCommonStyle to paint the affected elements.
// This class is used by the global search view as well.
// This class is used by tidal search view as well.
class PlaylistProxyStyle : public QProxyStyle {
public:
PlaylistProxyStyle(QStyle *base);