mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-29 10:39:47 +01:00
Get playlist contents from Spotify and display them in the UI
This commit is contained in:
parent
ef24073b5c
commit
3a34d416f9
@ -38,16 +38,20 @@ SpotifyClient::SpotifyClient(QObject* parent)
|
||||
memset(&spotify_callbacks_, 0, sizeof(spotify_callbacks_));
|
||||
memset(&spotify_config_, 0, sizeof(spotify_config_));
|
||||
memset(&playlistcontainer_callbacks_, 0, sizeof(playlistcontainer_callbacks_));
|
||||
memset(&load_playlist_callbacks_, 0, sizeof(load_playlist_callbacks_));
|
||||
|
||||
spotify_callbacks_.logged_in = &LoggedInCallback;
|
||||
spotify_callbacks_.notify_main_thread = &NotifyMainThreadCallback;
|
||||
spotify_callbacks_.log_message = &LogMessageCallback;
|
||||
spotify_callbacks_.metadata_updated = &MetadataUpdatedCallback;
|
||||
|
||||
playlistcontainer_callbacks_.container_loaded = &PlaylistContainerLoadedCallback;
|
||||
playlistcontainer_callbacks_.playlist_added = &PlaylistAddedCallback;
|
||||
playlistcontainer_callbacks_.playlist_moved = &PlaylistMovedCallback;
|
||||
playlistcontainer_callbacks_.playlist_removed = &PlaylistRemovedCallback;
|
||||
|
||||
load_playlist_callbacks_.playlist_state_changed = &PlaylistStateChanged;
|
||||
|
||||
spotify_config_.api_version = SPOTIFY_API_VERSION; // From libspotify/api.h
|
||||
spotify_config_.cache_location = strdup(QDir::tempPath().toLocal8Bit().constData());
|
||||
spotify_config_.settings_location = strdup(QDir::tempPath().toLocal8Bit().constData());
|
||||
@ -74,7 +78,7 @@ SpotifyClient::~SpotifyClient() {
|
||||
}
|
||||
|
||||
void SpotifyClient::Init(quint16 port) {
|
||||
qLog(Debug) << "connecting to port" << port;
|
||||
qLog(Debug) << "Connecting to port" << port;
|
||||
|
||||
socket_->connectToHost(QHostAddress::LocalHost, port);
|
||||
}
|
||||
@ -108,7 +112,7 @@ void SpotifyClient::ProcessEvents() {
|
||||
}
|
||||
|
||||
void SpotifyClient::LogMessageCallback(sp_session* session, const char* data) {
|
||||
qLog(Debug) << "libspotify:" << data;
|
||||
qLog(Debug) << "libspotify:" << QString::fromUtf8(data).trimmed();
|
||||
}
|
||||
|
||||
void SpotifyClient::Search(const QString& query) {
|
||||
@ -153,6 +157,8 @@ void SpotifyClient::SocketReadyRead() {
|
||||
if (message.has_login_request()) {
|
||||
const protobuf::LoginRequest& r = message.login_request();
|
||||
Login(QStringFromStdString(r.username()), QStringFromStdString(r.password()));
|
||||
} else if (message.has_load_playlist_request()) {
|
||||
LoadPlaylist(message.load_playlist_request());
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,3 +234,145 @@ void SpotifyClient::GetPlaylists() {
|
||||
|
||||
SendMessage(socket_, message);
|
||||
}
|
||||
|
||||
void SpotifyClient::LoadPlaylist(const protobuf::LoadPlaylistRequest& req) {
|
||||
PendingLoadPlaylist pending_load;
|
||||
pending_load.request_ = req;
|
||||
pending_load.playlist_ = NULL;
|
||||
|
||||
switch (req.type()) {
|
||||
case protobuf::LoadPlaylistRequest_Type_Inbox:
|
||||
pending_load.playlist_ = sp_session_inbox_create(session_);
|
||||
break;
|
||||
|
||||
case protobuf::LoadPlaylistRequest_Type_Starred:
|
||||
pending_load.playlist_ = sp_session_starred_create(session_);
|
||||
break;
|
||||
|
||||
case protobuf::LoadPlaylistRequest_Type_UserPlaylist: {
|
||||
const int index = req.user_playlist_index();
|
||||
sp_playlistcontainer* pc = sp_session_playlistcontainer(session_);
|
||||
|
||||
if (pc && index <= sp_playlistcontainer_num_playlists(pc)) {
|
||||
if (sp_playlistcontainer_playlist_type(pc, index) == SP_PLAYLIST_TYPE_PLAYLIST) {
|
||||
pending_load.playlist_ = sp_playlistcontainer_playlist(pc, index);
|
||||
sp_playlist_add_ref(pending_load.playlist_);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// A null playlist might mean the user wasn't logged in, or an invalid
|
||||
// playlist index was requested, so we'd better return an error straight away.
|
||||
if (!pending_load.playlist_) {
|
||||
qLog(Warning) << "Invalid playlist requested or not logged in";
|
||||
|
||||
protobuf::SpotifyMessage message;
|
||||
protobuf::LoadPlaylistResponse* response = message.mutable_load_playlist_response();
|
||||
*response->mutable_request() = req;
|
||||
SendMessage(socket_, message);
|
||||
return;
|
||||
}
|
||||
|
||||
sp_playlist_add_callbacks(pending_load.playlist_, &load_playlist_callbacks_, this);
|
||||
pending_load_playlists_ << pending_load;
|
||||
|
||||
PlaylistStateChanged(pending_load.playlist_, this);
|
||||
}
|
||||
|
||||
void SpotifyClient::PlaylistStateChanged(sp_playlist* pl, void* userdata) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
|
||||
|
||||
// If the playlist isn't loaded yet we have to wait
|
||||
if (!sp_playlist_is_loaded(pl)) {
|
||||
qLog(Debug) << "Playlist isn't loaded yet, waiting";
|
||||
return;
|
||||
}
|
||||
|
||||
// Find this playlist's pending load object
|
||||
int pending_load_index = -1;
|
||||
PendingLoadPlaylist* pending_load = NULL;
|
||||
for (int i=0 ; i<me->pending_load_playlists_.count() ; ++i) {
|
||||
if (me->pending_load_playlists_[i].playlist_ == pl) {
|
||||
pending_load_index = i;
|
||||
pending_load = &me->pending_load_playlists_[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pending_load) {
|
||||
qLog(Warning) << "Playlist not found in pending load list";
|
||||
return;
|
||||
}
|
||||
|
||||
// If the playlist was just loaded then get all its tracks and ref them
|
||||
if (pending_load->tracks_.isEmpty()) {
|
||||
const int count = sp_playlist_num_tracks(pl);
|
||||
for (int i=0 ; i<count ; ++i) {
|
||||
sp_track* track = sp_playlist_track(pl, i);
|
||||
sp_track_add_ref(track);
|
||||
pending_load->tracks_ << track;
|
||||
}
|
||||
}
|
||||
|
||||
// If any of the tracks aren't loaded yet we have to wait
|
||||
foreach (sp_track* track, pending_load->tracks_) {
|
||||
if (!sp_track_is_loaded(track)) {
|
||||
qLog(Debug) << "One or more tracks aren't loaded yet, waiting";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Everything is loaded so send the response protobuf and unref everything.
|
||||
protobuf::SpotifyMessage message;
|
||||
protobuf::LoadPlaylistResponse* response = message.mutable_load_playlist_response();
|
||||
|
||||
*response->mutable_request() = pending_load->request_;
|
||||
foreach (sp_track* track, pending_load->tracks_) {
|
||||
me->ConvertTrack(track, response->add_track());
|
||||
sp_track_release(track);
|
||||
}
|
||||
me->SendMessage(me->socket_, message);
|
||||
|
||||
// Unref the playlist and remove our callbacks
|
||||
sp_playlist_remove_callbacks(pl, &me->load_playlist_callbacks_, me);
|
||||
sp_playlist_release(pl);
|
||||
|
||||
// Remove the pending load object
|
||||
me->pending_load_playlists_.removeAt(pending_load_index);
|
||||
}
|
||||
|
||||
void SpotifyClient::ConvertTrack(sp_track* track, protobuf::Track* pb) {
|
||||
sp_album* album = sp_track_album(track);
|
||||
|
||||
pb->set_starred(sp_track_is_starred(session_, track));
|
||||
pb->set_title(sp_track_name(track));
|
||||
pb->set_album(sp_album_name(album));
|
||||
pb->set_year(sp_album_year(album));
|
||||
pb->set_duration_msec(sp_track_duration(track));
|
||||
pb->set_popularity(sp_track_popularity(track));
|
||||
pb->set_disc(sp_track_disc(track));
|
||||
pb->set_track(sp_track_index(track));
|
||||
|
||||
for (int i=0 ; i<sp_track_num_artists(track) ; ++i) {
|
||||
pb->add_artist(sp_artist_name(sp_track_artist(track, i)));
|
||||
}
|
||||
|
||||
// Blugh
|
||||
char uri[256];
|
||||
sp_link* link = sp_link_create_from_track(track, 0);
|
||||
sp_link_as_string(link, uri, sizeof(uri));
|
||||
sp_link_release(link);
|
||||
|
||||
pb->set_uri(uri);
|
||||
}
|
||||
|
||||
void SpotifyClient::MetadataUpdatedCallback(sp_session* session) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
|
||||
|
||||
foreach (const PendingLoadPlaylist& load, me->pending_load_playlists_) {
|
||||
PlaylistStateChanged(load.playlist_, me);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
#ifndef SPOTIFYCLIENT_H
|
||||
#define SPOTIFYCLIENT_H
|
||||
|
||||
#include "spotifymessages.pb.h"
|
||||
#include "spotifymessageutils.h"
|
||||
|
||||
#include <QObject>
|
||||
@ -54,6 +55,7 @@ private:
|
||||
static void NotifyMainThreadCallback(sp_session* session);
|
||||
static void LogMessageCallback(sp_session* session, const char* data);
|
||||
static void SearchCompleteCallback(sp_search* result, void* userdata);
|
||||
static void MetadataUpdatedCallback(sp_session* session);
|
||||
|
||||
// Spotify playlist container callbacks.
|
||||
static void PlaylistAddedCallback(
|
||||
@ -68,10 +70,25 @@ private:
|
||||
static void PlaylistContainerLoadedCallback(
|
||||
sp_playlistcontainer* pc, void* userdata);
|
||||
|
||||
// Spotify playlist callbacks - when loading a playlist
|
||||
static void PlaylistStateChanged(sp_playlist* pl, void* userdata);
|
||||
|
||||
// Request handlers.
|
||||
void Login(const QString& username, const QString& password);
|
||||
void GetPlaylists();
|
||||
void Search(const QString& query);
|
||||
void LoadPlaylist(const protobuf::LoadPlaylistRequest& req);
|
||||
|
||||
void ConvertTrack(sp_track* track, protobuf::Track* pb);
|
||||
|
||||
private:
|
||||
struct PendingLoadPlaylist {
|
||||
protobuf::LoadPlaylistRequest request_;
|
||||
|
||||
sp_playlist* playlist_;
|
||||
|
||||
QList<sp_track*> tracks_;
|
||||
};
|
||||
|
||||
QByteArray api_key_;
|
||||
|
||||
@ -80,9 +97,12 @@ private:
|
||||
sp_session_config spotify_config_;
|
||||
sp_session_callbacks spotify_callbacks_;
|
||||
sp_playlistcontainer_callbacks playlistcontainer_callbacks_;
|
||||
sp_playlist_callbacks load_playlist_callbacks_;
|
||||
sp_session* session_;
|
||||
|
||||
QTimer* events_timer_;
|
||||
|
||||
QList<PendingLoadPlaylist> pending_load_playlists_;
|
||||
};
|
||||
|
||||
#endif // SPOTIFYCLIENT_H
|
||||
|
@ -40,8 +40,39 @@ message Playlists {
|
||||
repeated Playlist playlist = 1;
|
||||
}
|
||||
|
||||
message Track {
|
||||
required bool starred = 1;
|
||||
required string title = 2;
|
||||
repeated string artist = 3;
|
||||
required string album = 4;
|
||||
required int32 duration_msec = 5;
|
||||
required int32 popularity = 6;
|
||||
required int32 disc = 7;
|
||||
required int32 track = 8;
|
||||
required int32 year = 9;
|
||||
required string uri = 10;
|
||||
}
|
||||
|
||||
message LoadPlaylistRequest {
|
||||
enum Type {
|
||||
Starred = 1;
|
||||
Inbox = 2;
|
||||
UserPlaylist = 3;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
optional int32 user_playlist_index = 2;
|
||||
}
|
||||
|
||||
message LoadPlaylistResponse {
|
||||
required LoadPlaylistRequest request = 1;
|
||||
repeated Track track = 2;
|
||||
}
|
||||
|
||||
message SpotifyMessage {
|
||||
optional LoginRequest login_request = 1;
|
||||
optional LoginResponse login_response = 2;
|
||||
optional Playlists playlists_updated = 3;
|
||||
optional LoadPlaylistRequest load_playlist_request = 4;
|
||||
optional LoadPlaylistResponse load_playlist_response = 5;
|
||||
}
|
||||
|
@ -103,5 +103,45 @@ void SpotifyServer::ProtocolSocketReadyRead() {
|
||||
emit LoginCompleted(response.success());
|
||||
} else if (message.has_playlists_updated()) {
|
||||
emit PlaylistsUpdated(message.playlists_updated());
|
||||
} else if (message.has_load_playlist_response()) {
|
||||
const protobuf::LoadPlaylistResponse& response = message.load_playlist_response();
|
||||
|
||||
switch (response.request().type()) {
|
||||
case protobuf::LoadPlaylistRequest_Type_Inbox:
|
||||
emit InboxLoaded(response);
|
||||
break;
|
||||
|
||||
case protobuf::LoadPlaylistRequest_Type_Starred:
|
||||
emit StarredLoaded(response);
|
||||
break;
|
||||
|
||||
case protobuf::LoadPlaylistRequest_Type_UserPlaylist:
|
||||
emit UserPlaylistLoaded(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyServer::LoadPlaylist(protobuf::LoadPlaylistRequest_Type type, int index) {
|
||||
protobuf::SpotifyMessage message;
|
||||
protobuf::LoadPlaylistRequest* req = message.mutable_load_playlist_request();
|
||||
|
||||
req->set_type(type);
|
||||
if (index != -1) {
|
||||
req->set_user_playlist_index(index);
|
||||
}
|
||||
|
||||
SendMessage(message);
|
||||
}
|
||||
|
||||
void SpotifyServer::LoadInbox() {
|
||||
LoadPlaylist(protobuf::LoadPlaylistRequest_Type_Inbox);
|
||||
}
|
||||
|
||||
void SpotifyServer::LoadStarred() {
|
||||
LoadPlaylist(protobuf::LoadPlaylistRequest_Type_Starred);
|
||||
}
|
||||
|
||||
void SpotifyServer::LoadUserPlaylist(int index) {
|
||||
LoadPlaylist(protobuf::LoadPlaylistRequest_Type_UserPlaylist, index);
|
||||
}
|
||||
|
@ -35,17 +35,26 @@ public:
|
||||
void Init();
|
||||
void Login(const QString& username, const QString& password);
|
||||
|
||||
void LoadStarred();
|
||||
void LoadInbox();
|
||||
void LoadUserPlaylist(int index);
|
||||
|
||||
int server_port() const;
|
||||
|
||||
signals:
|
||||
void LoginCompleted(bool success);
|
||||
void PlaylistsUpdated(const protobuf::Playlists& playlists);
|
||||
|
||||
void StarredLoaded(const protobuf::LoadPlaylistResponse& response);
|
||||
void InboxLoaded(const protobuf::LoadPlaylistResponse& response);
|
||||
void UserPlaylistLoaded(const protobuf::LoadPlaylistResponse& response);
|
||||
|
||||
private slots:
|
||||
void NewConnection();
|
||||
void ProtocolSocketReadyRead();
|
||||
|
||||
private:
|
||||
void LoadPlaylist(protobuf::LoadPlaylistRequest_Type type, int index = -1);
|
||||
void SendMessage(const protobuf::SpotifyMessage& message);
|
||||
|
||||
QTcpServer* server_;
|
||||
|
@ -38,6 +38,21 @@ void SpotifyService::LazyPopulate(QStandardItem* item) {
|
||||
EnsureServerCreated();
|
||||
break;
|
||||
|
||||
case Type_InboxPlaylist:
|
||||
EnsureServerCreated();
|
||||
server_->LoadInbox();
|
||||
break;
|
||||
|
||||
case Type_StarredPlaylist:
|
||||
EnsureServerCreated();
|
||||
server_->LoadStarred();
|
||||
break;
|
||||
|
||||
case Type_UserPlaylist:
|
||||
EnsureServerCreated();
|
||||
server_->LoadUserPlaylist(item->data(Role_UserPlaylistIndex).toInt());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -86,6 +101,12 @@ void SpotifyService::EnsureServerCreated(const QString& username,
|
||||
connect(server_, SIGNAL(LoginCompleted(bool)), SLOT(LoginCompleted(bool)));
|
||||
connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)),
|
||||
SLOT(PlaylistsUpdated(protobuf::Playlists)));
|
||||
connect(server_, SIGNAL(InboxLoaded(protobuf::LoadPlaylistResponse)),
|
||||
SLOT(InboxLoaded(protobuf::LoadPlaylistResponse)));
|
||||
connect(server_, SIGNAL(StarredLoaded(protobuf::LoadPlaylistResponse)),
|
||||
SLOT(StarredLoaded(protobuf::LoadPlaylistResponse)));
|
||||
connect(server_, SIGNAL(UserPlaylistLoaded(protobuf::LoadPlaylistResponse)),
|
||||
SLOT(UserPlaylistLoaded(protobuf::LoadPlaylistResponse)));
|
||||
|
||||
connect(blob_process_,
|
||||
SIGNAL(error(QProcess::ProcessError)),
|
||||
@ -135,10 +156,67 @@ void SpotifyService::PlaylistsUpdated(const protobuf::Playlists& response) {
|
||||
const protobuf::Playlists::Playlist& msg = response.playlist(i);
|
||||
|
||||
QStandardItem* item = new QStandardItem(QStringFromStdString(msg.name()));
|
||||
item->setData(Type_Playlist, RadioModel::Role_Type);
|
||||
item->setData(Type_UserPlaylist, RadioModel::Role_Type);
|
||||
item->setData(true, RadioModel::Role_CanLazyLoad);
|
||||
item->setData(msg.index(), Role_PlaylistIndex);
|
||||
item->setData(msg.index(), Role_UserPlaylistIndex);
|
||||
|
||||
root_->appendRow(item);
|
||||
playlists_ << item;
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyService::InboxLoaded(const protobuf::LoadPlaylistResponse& response) {
|
||||
FillPlaylist(inbox_, response);
|
||||
}
|
||||
|
||||
void SpotifyService::StarredLoaded(const protobuf::LoadPlaylistResponse& response) {
|
||||
FillPlaylist(starred_, response);
|
||||
}
|
||||
|
||||
void SpotifyService::UserPlaylistLoaded(const protobuf::LoadPlaylistResponse& response) {
|
||||
// Find a playlist with this index
|
||||
foreach (QStandardItem* item, playlists_) {
|
||||
if (item->data(Role_UserPlaylistIndex).toInt() == response.request().user_playlist_index()) {
|
||||
FillPlaylist(item, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyService::FillPlaylist(QStandardItem* item, const protobuf::LoadPlaylistResponse& response) {
|
||||
if (item->hasChildren())
|
||||
item->removeRows(0, item->rowCount());
|
||||
|
||||
for (int i=0 ; i<response.track_size() ; ++i) {
|
||||
Song song;
|
||||
SongFromProtobuf(response.track(i), &song);
|
||||
|
||||
QStandardItem* child = new QStandardItem(song.PrettyTitleWithArtist());
|
||||
child->setData(Type_Track, RadioModel::Role_Type);
|
||||
child->setData(QVariant::fromValue(song), Role_Metadata);
|
||||
|
||||
item->appendRow(child);
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyService::SongFromProtobuf(const protobuf::Track& track, Song* song) const {
|
||||
song->set_rating(track.starred() ? 1.0 : 0.0);
|
||||
song->set_title(QStringFromStdString(track.title()));
|
||||
song->set_album(QStringFromStdString(track.album()));
|
||||
song->set_length_nanosec(track.duration_msec() * kNsecPerMsec);
|
||||
song->set_score(track.popularity());
|
||||
song->set_disc(track.disc());
|
||||
song->set_track(track.track());
|
||||
song->set_year(track.year());
|
||||
song->set_filename(QStringFromStdString(track.uri()));
|
||||
|
||||
QStringList artists;
|
||||
for (int i=0 ; i<track.artist_size() ; ++i) {
|
||||
artists << QStringFromStdString(track.artist(i));
|
||||
}
|
||||
|
||||
song->set_artist(artists.join(", "));
|
||||
|
||||
song->set_filetype(Song::Type_Stream);
|
||||
song->set_valid(true);
|
||||
}
|
||||
|
@ -20,11 +20,13 @@ public:
|
||||
enum Type {
|
||||
Type_StarredPlaylist = RadioModel::TypeCount,
|
||||
Type_InboxPlaylist,
|
||||
Type_Playlist,
|
||||
Type_UserPlaylist,
|
||||
Type_Track,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
Role_PlaylistIndex = RadioModel::RoleCount,
|
||||
Role_UserPlaylistIndex = RadioModel::RoleCount,
|
||||
Role_Metadata,
|
||||
};
|
||||
|
||||
virtual QStandardItem* CreateRootItem();
|
||||
@ -44,11 +46,16 @@ protected:
|
||||
private:
|
||||
void EnsureServerCreated(const QString& username = QString(),
|
||||
const QString& password = QString());
|
||||
void FillPlaylist(QStandardItem* item, const protobuf::LoadPlaylistResponse& response);
|
||||
void SongFromProtobuf(const protobuf::Track& track, Song* song) const;
|
||||
|
||||
private slots:
|
||||
void BlobProcessError(QProcess::ProcessError error);
|
||||
void LoginCompleted(bool success);
|
||||
void PlaylistsUpdated(const protobuf::Playlists& response);
|
||||
void InboxLoaded(const protobuf::LoadPlaylistResponse& response);
|
||||
void StarredLoaded(const protobuf::LoadPlaylistResponse& response);
|
||||
void UserPlaylistLoaded(const protobuf::LoadPlaylistResponse& response);
|
||||
|
||||
private:
|
||||
SpotifyServer* server_;
|
||||
|
Loading…
x
Reference in New Issue
Block a user