1
0
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:
David Sansome 2011-04-26 17:06:36 +00:00
parent ef24073b5c
commit 3a34d416f9
7 changed files with 339 additions and 6 deletions

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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_;

View File

@ -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);
}

View File

@ -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_;