1
/* This file is part of Clementine.
2
Copyright 2011, David Sansome <me@davidsansome.com>
4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
8
http://www.apache.org/licenses/LICENSE-2.0
10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
17
// Note: this file is licensed under the Apache License instead of GPL because
18
// it is used by the Spotify blob which links against libspotify and is not GPL
22
#include "mediapipeline.h"
23
#include "spotifyclient.h"
24
#include "spotifykey.h"
25
#include "spotifymessagehandler.h"
26
#include "spotifymessages.pb.h"
27
#include "spotify_utilities.h"
28
#include "core/logging.h"
30
#include <QCoreApplication>
32
#include <QHostAddress>
36
const int SpotifyClient::kSpotifyImageIDSize = 20;
37
const int SpotifyClient::kWaveHeaderSize = 44;
40
SpotifyClient::SpotifyClient(QObject* parent)
42
api_key_(QByteArray::fromBase64(kSpotifyApiKey)),
43
protocol_socket_(new QTcpSocket(this)),
44
handler_(new SpotifyMessageHandler(protocol_socket_, this)),
46
events_timer_(new QTimer(this)) {
47
memset(&spotify_callbacks_, 0, sizeof(spotify_callbacks_));
48
memset(&spotify_config_, 0, sizeof(spotify_config_));
49
memset(&playlistcontainer_callbacks_, 0, sizeof(playlistcontainer_callbacks_));
50
memset(&get_playlists_callbacks_, 0, sizeof(get_playlists_callbacks_));
51
memset(&load_playlist_callbacks_, 0, sizeof(load_playlist_callbacks_));
53
spotify_callbacks_.logged_in = &LoggedInCallback;
54
spotify_callbacks_.notify_main_thread = &NotifyMainThreadCallback;
55
spotify_callbacks_.log_message = &LogMessageCallback;
56
spotify_callbacks_.metadata_updated = &MetadataUpdatedCallback;
57
spotify_callbacks_.music_delivery = &MusicDeliveryCallback;
58
spotify_callbacks_.end_of_track = &EndOfTrackCallback;
59
spotify_callbacks_.streaming_error = &StreamingErrorCallback;
60
spotify_callbacks_.offline_status_updated = &OfflineStatusUpdatedCallback;
61
spotify_callbacks_.connection_error = &ConnectionErrorCallback;
62
spotify_callbacks_.message_to_user = &UserMessageCallback;
63
spotify_callbacks_.start_playback = &StartPlaybackCallback;
64
spotify_callbacks_.stop_playback = &StopPlaybackCallback;
67
playlistcontainer_callbacks_.container_loaded = &PlaylistContainerLoadedCallback;
68
playlistcontainer_callbacks_.playlist_added = &PlaylistAddedCallback;
69
playlistcontainer_callbacks_.playlist_moved = &PlaylistMovedCallback;
70
playlistcontainer_callbacks_.playlist_removed = &PlaylistRemovedCallback;
72
get_playlists_callbacks_.playlist_state_changed = &PlaylistStateChangedForGetPlaylists;
74
load_playlist_callbacks_.playlist_state_changed = &PlaylistStateChangedForLoadPlaylist;
76
QString cache = utilities::GetCacheDirectory();
77
qLog(Debug) << "Using:" << cache << "for Spotify cache";
78
QString settings_dir = utilities::GetSettingsDirectory();
79
qLog(Debug) << "Using:" << settings_dir << "for Spotify settings";
81
spotify_config_.api_version = SPOTIFY_API_VERSION; // From libspotify/api.h
82
spotify_config_.cache_location = strdup(cache.toUtf8().constData());
83
spotify_config_.settings_location = strdup(settings_dir.toUtf8().constData());
84
spotify_config_.application_key = api_key_.constData();
85
spotify_config_.application_key_size = api_key_.size();
86
spotify_config_.callbacks = &spotify_callbacks_;
87
spotify_config_.userdata = this;
88
spotify_config_.user_agent = "Clementine Player";
90
events_timer_->setSingleShot(true);
91
connect(events_timer_, SIGNAL(timeout()), SLOT(ProcessEvents()));
93
connect(handler_, SIGNAL(MessageArrived(spotify_pb::SpotifyMessage)),
94
SLOT(HandleMessage(spotify_pb::SpotifyMessage)));
95
connect(protocol_socket_, SIGNAL(disconnected()),
96
QCoreApplication::instance(), SLOT(quit()));
99
SpotifyClient::~SpotifyClient() {
101
sp_session_release(session_);
104
free(const_cast<char*>(spotify_config_.cache_location));
105
free(const_cast<char*>(spotify_config_.settings_location));
108
void SpotifyClient::Init(quint16 port) {
109
qLog(Debug) << "Connecting to port" << port;
111
protocol_socket_->connectToHost(QHostAddress::LocalHost, port);
114
void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) {
115
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
116
const bool success = error == SP_ERROR_OK;
117
spotify_pb::LoginResponse_Error error_code = spotify_pb::LoginResponse_Error_Other;
120
qLog(Warning) << "Failed to login" << sp_error_message(error);
124
case SP_ERROR_BAD_USERNAME_OR_PASSWORD:
125
error_code = spotify_pb::LoginResponse_Error_BadUsernameOrPassword;
127
case SP_ERROR_USER_BANNED:
128
error_code = spotify_pb::LoginResponse_Error_UserBanned;
130
case SP_ERROR_USER_NEEDS_PREMIUM :
131
error_code = spotify_pb::LoginResponse_Error_UserNeedsPremium;
134
error_code = spotify_pb::LoginResponse_Error_Other;
138
me->SendLoginCompleted(success, sp_error_message(error), error_code);
141
sp_playlistcontainer_add_callbacks(
142
sp_session_playlistcontainer(session),
143
&me->playlistcontainer_callbacks_, me);
147
void SpotifyClient::NotifyMainThreadCallback(sp_session* session) {
148
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
149
QMetaObject::invokeMethod(me, "ProcessEvents", Qt::QueuedConnection);
152
void SpotifyClient::ProcessEvents() {
154
sp_session_process_events(session_, &next_timeout_ms);
155
events_timer_->start(next_timeout_ms);
158
void SpotifyClient::LogMessageCallback(sp_session* session, const char* data) {
159
qLog(Debug) << "libspotify:" << QString::fromUtf8(data).trimmed();
162
void SpotifyClient::Search(const spotify_pb::SearchRequest& req) {
163
sp_search* search = sp_search_create(
164
session_, req.query().c_str(),
166
0, req.limit_album(),
168
&SearchCompleteCallback, this);
170
pending_searches_[search] = req;
173
void SpotifyClient::SearchCompleteCallback(sp_search* result, void* userdata) {
174
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
176
if (!me->pending_searches_.contains(result)) {
177
qLog(Warning) << "SearchComplete called with unknown search";
181
// If there were any album results then we need to resolve those before
182
// we can send our response.
183
const int count = sp_search_num_albums(result);
185
for (int i=0 ; i<count ; ++i) {
186
sp_album* album = sp_search_album(result, i);
187
sp_albumbrowse* browse =
188
sp_albumbrowse_create(me->session_, album, &SearchAlbumBrowseComplete, me);
190
me->pending_search_album_browse_responses_[browse] = result;
195
me->SendSearchResponse(result);
198
void SpotifyClient::SearchAlbumBrowseComplete(sp_albumbrowse* result, void* userdata) {
199
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
201
if (!me->pending_search_album_browse_responses_.contains(result)) {
202
qLog(Warning) << "SearchAlbumBrowseComplete called with unknown result";
206
sp_search* search = me->pending_search_album_browse_responses_.take(result);
207
me->pending_search_album_browses_[search].append(result);
209
if (me->pending_search_album_browses_[search].count() >= sp_search_num_albums(search)) {
210
me->SendSearchResponse(search);
214
void SpotifyClient::SendSearchResponse(sp_search* result) {
215
// Take the request out of the queue
216
spotify_pb::SearchRequest req = pending_searches_.take(result);
218
// Prepare the response
219
spotify_pb::SpotifyMessage message;
220
spotify_pb::SearchResponse* response = message.mutable_search_response();
222
*response->mutable_request() = req;
225
sp_error error = sp_search_error(result);
226
if (error != SP_ERROR_OK) {
227
response->set_error(sp_error_message(error));
229
handler_->SendMessage(message);
230
sp_search_release(result);
234
// Get the list of tracks from the search
235
int count = sp_search_num_tracks(result);
236
for (int i=0 ; i<count ; ++i) {
237
sp_track* track = sp_search_track(result, i);
238
ConvertTrack(track, response->add_result());
241
// Get the albums from the search. All these should be resolved by now.
242
QList<sp_albumbrowse*> browses = pending_search_album_browses_.take(result);
243
foreach (sp_albumbrowse* browse, browses) {
244
sp_album* album = sp_albumbrowse_album(browse);
245
spotify_pb::Album* msg = response->add_album();
247
ConvertAlbum(album, msg->mutable_metadata());
248
ConvertAlbumBrowse(browse, msg->mutable_metadata());
251
const int tracks = sp_albumbrowse_num_tracks(browse);
252
for (int i=0 ; i<tracks ; ++i) {
253
ConvertTrack(sp_albumbrowse_track(browse, i), msg->add_track());
256
sp_albumbrowse_release(browse);
259
// Add other data to the response
260
response->set_total_tracks(sp_search_total_tracks(result));
261
response->set_did_you_mean(sp_search_did_you_mean(result));
263
handler_->SendMessage(message);
264
sp_search_release(result);
267
void SpotifyClient::HandleMessage(const spotify_pb::SpotifyMessage& message) {
268
if (message.has_login_request()) {
269
Login(message.login_request());
270
} else if (message.has_load_playlist_request()) {
271
LoadPlaylist(message.load_playlist_request());
272
} else if (message.has_playback_request()) {
273
StartPlayback(message.playback_request());
274
} else if (message.has_seek_request()) {
275
Seek(message.seek_request().offset_bytes());
276
} else if (message.has_search_request()) {
277
Search(message.search_request());
278
} else if (message.has_image_request()) {
279
LoadImage(QStringFromStdString(message.image_request().id()));
280
} else if (message.has_sync_playlist_request()) {
281
SyncPlaylist(message.sync_playlist_request());
282
} else if (message.has_browse_album_request()) {
283
BrowseAlbum(QStringFromStdString(message.browse_album_request().uri()));
284
} else if (message.has_set_playback_settings_request()) {
285
SetPlaybackSettings(message.set_playback_settings_request());
289
void SpotifyClient::SetPlaybackSettings(const spotify_pb::PlaybackSettings& req) {
290
sp_bitrate bitrate = SP_BITRATE_320k;
291
switch (req.bitrate()) {
292
case spotify_pb::Bitrate96k: bitrate = SP_BITRATE_96k; break;
293
case spotify_pb::Bitrate160k: bitrate = SP_BITRATE_160k; break;
294
case spotify_pb::Bitrate320k: bitrate = SP_BITRATE_320k; break;
297
qLog(Debug) << "Setting playback settings: bitrate"
298
<< bitrate << "normalisation" << req.volume_normalisation();
300
sp_session_preferred_bitrate(session_, bitrate);
301
sp_session_preferred_offline_bitrate(session_, bitrate, false);
302
sp_session_set_volume_normalization(session_, req.volume_normalisation());
305
void SpotifyClient::Login(const spotify_pb::LoginRequest& req) {
306
sp_error error = sp_session_create(&spotify_config_, &session_);
307
if (error != SP_ERROR_OK) {
308
qLog(Warning) << "Failed to create session" << sp_error_message(error);
309
SendLoginCompleted(false, sp_error_message(error), spotify_pb::LoginResponse_Error_Other);
313
SetPlaybackSettings(req.playback_settings());
315
if (req.password().empty()) {
316
sp_error error = sp_session_relogin(session_);
317
if (error != SP_ERROR_OK) {
318
qLog(Warning) << "Tried to relogin but no stored credentials";
319
SendLoginCompleted(false, sp_error_message(error),
320
spotify_pb::LoginResponse_Error_ReloginFailed);
323
sp_session_login(session_,
324
req.username().c_str(),
325
req.password().c_str(),
326
true); // Remember the password.
330
void SpotifyClient::SendLoginCompleted(bool success, const QString& error,
331
spotify_pb::LoginResponse_Error error_code) {
332
spotify_pb::SpotifyMessage message;
334
spotify_pb::LoginResponse* response = message.mutable_login_response();
335
response->set_success(success);
336
response->set_error(DataCommaSizeFromQString(error));
339
response->set_error_code(error_code);
342
handler_->SendMessage(message);
345
void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, void* userdata) {
346
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
348
// Install callbacks on all the playlists
349
const int count = sp_playlistcontainer_num_playlists(pc);
350
for (int i=0 ; i<count ; ++i) {
351
sp_playlist* playlist = sp_playlistcontainer_playlist(pc, i);
352
sp_playlist_add_callbacks(playlist, &me->get_playlists_callbacks_, me);
355
me->SendPlaylistList();
358
void SpotifyClient::PlaylistAddedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) {
359
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
361
// Install callbacks on this playlist
362
sp_playlist_add_callbacks(playlist, &me->get_playlists_callbacks_, me);
364
me->SendPlaylistList();
367
void SpotifyClient::PlaylistMovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, int new_position, void* userdata) {
368
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
369
me->SendPlaylistList();
372
void SpotifyClient::PlaylistRemovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) {
373
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
375
// Remove callbacks from this playlist
376
sp_playlist_remove_callbacks(playlist, &me->get_playlists_callbacks_, me);
378
me->SendPlaylistList();
381
void SpotifyClient::SendPlaylistList() {
382
spotify_pb::SpotifyMessage message;
383
spotify_pb::Playlists* response = message.mutable_playlists_updated();
385
sp_playlistcontainer* container = sp_session_playlistcontainer(session_);
387
qLog(Warning) << "sp_session_playlistcontainer returned NULL";
391
const int count = sp_playlistcontainer_num_playlists(container);
393
for (int i=0 ; i<count ; ++i) {
394
const int type = sp_playlistcontainer_playlist_type(container, i);
395
sp_playlist* playlist = sp_playlistcontainer_playlist(container, i);
396
const bool is_loaded = sp_playlist_is_loaded(playlist);
398
qLog(Debug) << "Got playlist" << i << is_loaded << type << sp_playlist_name(playlist);
401
qLog(Info) << "Playlist is not loaded yet, waiting...";
405
if (type != SP_PLAYLIST_TYPE_PLAYLIST) {
406
// Just ignore folders for now
410
spotify_pb::Playlists::Playlist* msg = response->add_playlist();
412
msg->set_name(sp_playlist_name(playlist));
414
sp_playlist_offline_status offline_status =
415
sp_playlist_get_offline_status(session_, playlist);
416
const bool is_offline = offline_status == SP_PLAYLIST_OFFLINE_STATUS_YES;
417
msg->set_is_offline(is_offline);
418
if (offline_status == SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING) {
419
msg->set_download_progress(
420
sp_playlist_get_offline_download_completed(session_, playlist));
421
} else if (offline_status == SP_PLAYLIST_OFFLINE_STATUS_WAITING) {
422
msg->set_download_progress(0);
426
handler_->SendMessage(message);
429
sp_playlist* SpotifyClient::GetPlaylist(spotify_pb::PlaylistType type, int user_index) {
430
sp_playlist* playlist = NULL;
432
case spotify_pb::Inbox:
433
playlist = sp_session_inbox_create(session_);
436
case spotify_pb::Starred:
437
playlist = sp_session_starred_create(session_);
440
case spotify_pb::UserPlaylist: {
441
sp_playlistcontainer* pc = sp_session_playlistcontainer(session_);
443
if (pc && user_index <= sp_playlistcontainer_num_playlists(pc)) {
444
if (sp_playlistcontainer_playlist_type(pc, user_index) == SP_PLAYLIST_TYPE_PLAYLIST) {
445
playlist = sp_playlistcontainer_playlist(pc, user_index);
446
sp_playlist_add_ref(playlist);
456
void SpotifyClient::LoadPlaylist(const spotify_pb::LoadPlaylistRequest& req) {
457
PendingLoadPlaylist pending_load;
458
pending_load.request_ = req;
459
pending_load.playlist_ = GetPlaylist(req.type(), req.user_playlist_index());
461
// A null playlist might mean the user wasn't logged in, or an invalid
462
// playlist index was requested, so we'd better return an error straight away.
463
if (!pending_load.playlist_) {
464
qLog(Warning) << "Invalid playlist requested or not logged in";
466
spotify_pb::SpotifyMessage message;
467
spotify_pb::LoadPlaylistResponse* response = message.mutable_load_playlist_response();
468
*response->mutable_request() = req;
469
handler_->SendMessage(message);
473
sp_playlist_add_callbacks(pending_load.playlist_, &load_playlist_callbacks_, this);
474
pending_load_playlists_ << pending_load;
476
PlaylistStateChangedForLoadPlaylist(pending_load.playlist_, this);
479
void SpotifyClient::SyncPlaylist(const spotify_pb::SyncPlaylistRequest& req) {
480
sp_playlist* playlist = GetPlaylist(req.request().type(), req.request().user_playlist_index());
482
// The playlist should already be loaded.
483
sp_playlist_set_offline_mode(session_, playlist, req.offline_sync());
486
void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* userdata) {
487
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
489
// If the playlist isn't loaded yet we have to wait
490
if (!sp_playlist_is_loaded(pl)) {
491
qLog(Debug) << "Playlist isn't loaded yet, waiting";
495
// Find this playlist's pending load object
496
int pending_load_index = -1;
497
PendingLoadPlaylist* pending_load = NULL;
498
for (int i=0 ; i<me->pending_load_playlists_.count() ; ++i) {
499
if (me->pending_load_playlists_[i].playlist_ == pl) {
500
pending_load_index = i;
501
pending_load = &me->pending_load_playlists_[i];
507
qLog(Warning) << "Playlist not found in pending load list";
511
// If the playlist was just loaded then get all its tracks and ref them
512
if (pending_load->tracks_.isEmpty()) {
513
const int count = sp_playlist_num_tracks(pl);
514
for (int i=0 ; i<count ; ++i) {
515
sp_track* track = sp_playlist_track(pl, i);
516
sp_track_add_ref(track);
517
pending_load->tracks_ << track;
521
// If any of the tracks aren't loaded yet we have to wait
522
foreach (sp_track* track, pending_load->tracks_) {
523
if (!sp_track_is_loaded(track)) {
524
qLog(Debug) << "One or more tracks aren't loaded yet, waiting";
529
// Everything is loaded so send the response protobuf and unref everything.
530
spotify_pb::SpotifyMessage message;
531
spotify_pb::LoadPlaylistResponse* response = message.mutable_load_playlist_response();
533
*response->mutable_request() = pending_load->request_;
534
foreach (sp_track* track, pending_load->tracks_) {
535
me->ConvertTrack(track, response->add_track());
536
sp_track_release(track);
538
me->handler_->SendMessage(message);
540
// Unref the playlist and remove our callbacks
541
sp_playlist_remove_callbacks(pl, &me->load_playlist_callbacks_, me);
542
sp_playlist_release(pl);
544
// Remove the pending load object
545
me->pending_load_playlists_.removeAt(pending_load_index);
548
void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void* userdata) {
549
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
551
me->SendPlaylistList();
554
void SpotifyClient::ConvertTrack(sp_track* track, spotify_pb::Track* pb) {
555
sp_album* album = sp_track_album(track);
557
pb->set_starred(sp_track_is_starred(session_, track));
558
pb->set_title(sp_track_name(track));
559
pb->set_album(sp_album_name(album));
560
pb->set_year(sp_album_year(album));
561
pb->set_duration_msec(sp_track_duration(track));
562
pb->set_popularity(sp_track_popularity(track));
563
pb->set_disc(sp_track_disc(track));
564
pb->set_track(sp_track_index(track));
567
const QByteArray art_id(
568
reinterpret_cast<const char*>(sp_album_cover(sp_track_album(track))),
569
kSpotifyImageIDSize);
570
const QString art_id_b64 = QString::fromAscii(art_id.toBase64());
571
pb->set_album_art_id(DataCommaSizeFromQString(art_id_b64));
574
for (int i=0 ; i<sp_track_num_artists(track) ; ++i) {
575
pb->add_artist(sp_artist_name(sp_track_artist(track, i)));
580
sp_link* link = sp_link_create_from_track(track, 0);
581
sp_link_as_string(link, uri, sizeof(uri));
582
sp_link_release(link);
587
void SpotifyClient::ConvertAlbum(sp_album* album, spotify_pb::Track* pb) {
588
pb->set_album(sp_album_name(album));
589
pb->set_year(sp_album_year(album));
590
pb->add_artist(sp_artist_name(sp_album_artist(album)));
592
// These fields were required in a previous version so need to set them again
595
pb->set_duration_msec(-1);
596
pb->set_popularity(-1);
599
pb->set_starred(false);
602
const QByteArray art_id(
603
reinterpret_cast<const char*>(sp_album_cover(album)),
604
kSpotifyImageIDSize);
605
const QString art_id_b64 = QString::fromAscii(art_id.toBase64());
606
pb->set_album_art_id(DataCommaSizeFromQString(art_id_b64));
610
sp_link* link = sp_link_create_from_album(album);
611
sp_link_as_string(link, uri, sizeof(uri));
612
sp_link_release(link);
617
void SpotifyClient::ConvertAlbumBrowse(sp_albumbrowse* browse, spotify_pb::Track* pb) {
618
pb->set_track(sp_albumbrowse_num_tracks(browse));
621
void SpotifyClient::MetadataUpdatedCallback(sp_session* session) {
622
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
624
foreach (const PendingLoadPlaylist& load, me->pending_load_playlists_) {
625
PlaylistStateChangedForLoadPlaylist(load.playlist_, me);
627
foreach (const PendingPlaybackRequest& playback, me->pending_playback_requests_) {
628
me->TryPlaybackAgain(playback);
632
int SpotifyClient::MusicDeliveryCallback(
633
sp_session* session, const sp_audioformat* format,
634
const void* frames, int num_frames) {
635
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
637
if (!me->media_pipeline_) {
641
if (num_frames == 0) {
645
if (!me->media_pipeline_->is_initialised()) {
646
if (!me->media_pipeline_->Init(format->sample_rate, format->channels)) {
647
qLog(Warning) << "Failed to intitialise media pipeline";
648
sp_session_player_unload(me->session_);
649
me->media_pipeline_.reset();
654
if (!me->media_pipeline_->is_accepting_data()) {
658
me->media_pipeline_->WriteData(
659
reinterpret_cast<const char*>(frames),
660
num_frames * format->channels * 2);
665
void SpotifyClient::EndOfTrackCallback(sp_session* session) {
666
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
668
me->media_pipeline_.reset();
671
void SpotifyClient::StreamingErrorCallback(sp_session* session, sp_error error) {
672
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
674
me->media_pipeline_.reset();
677
me->SendPlaybackError(QString::fromUtf8(sp_error_message(error)));
680
void SpotifyClient::ConnectionErrorCallback(sp_session* session, sp_error error) {
681
qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
684
void SpotifyClient::UserMessageCallback(sp_session* session, const char* message) {
685
qLog(Debug) << Q_FUNC_INFO << message;
688
void SpotifyClient::StartPlaybackCallback(sp_session* session) {
689
qLog(Debug) << Q_FUNC_INFO;
692
void SpotifyClient::StopPlaybackCallback(sp_session* session) {
693
qLog(Debug) << Q_FUNC_INFO;
696
void SpotifyClient::OfflineStatusUpdatedCallback(sp_session* session) {
697
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
698
sp_playlistcontainer* container = sp_session_playlistcontainer(session);
700
qLog(Warning) << "sp_session_playlistcontainer returned NULL";
704
const int count = sp_playlistcontainer_num_playlists(container);
706
for (int i=0 ; i<count ; ++i) {
707
const sp_playlist_type type = sp_playlistcontainer_playlist_type(container, i);
708
sp_playlist* playlist = sp_playlistcontainer_playlist(container, i);
710
if (type != SP_PLAYLIST_TYPE_PLAYLIST) {
711
// Just ignore folders for now
715
int download_progress = me->GetDownloadProgress(playlist);
716
if (download_progress != -1) {
717
me->SendDownloadProgress(spotify_pb::UserPlaylist, i, download_progress);
721
sp_playlist* inbox = sp_session_inbox_create(session);
722
int download_progress = me->GetDownloadProgress(inbox);
723
sp_playlist_release(inbox);
725
if (download_progress != -1) {
726
me->SendDownloadProgress(spotify_pb::Inbox, -1, download_progress);
729
sp_playlist* starred = sp_session_starred_create(session);
730
download_progress = me->GetDownloadProgress(starred);
731
sp_playlist_release(starred);
733
if (download_progress != -1) {
734
me->SendDownloadProgress(spotify_pb::Starred, -1, download_progress);
738
void SpotifyClient::SendDownloadProgress(
739
spotify_pb::PlaylistType type, int index, int download_progress) {
740
spotify_pb::SpotifyMessage message;
741
spotify_pb::SyncPlaylistProgress* progress = message.mutable_sync_playlist_progress();
742
progress->mutable_request()->set_type(type);
744
progress->mutable_request()->set_user_playlist_index(index);
746
progress->set_sync_progress(download_progress);
747
handler_->SendMessage(message);
750
int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) {
751
sp_playlist_offline_status status =
752
sp_playlist_get_offline_status(session_, playlist);
754
case SP_PLAYLIST_OFFLINE_STATUS_NO:
756
case SP_PLAYLIST_OFFLINE_STATUS_YES:
758
case SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING:
759
return sp_playlist_get_offline_download_completed(session_, playlist);
760
case SP_PLAYLIST_OFFLINE_STATUS_WAITING:
766
void SpotifyClient::StartPlayback(const spotify_pb::PlaybackRequest& req) {
767
// Get a link object from the URI
768
sp_link* link = sp_link_create_from_string(req.track_uri().c_str());
770
SendPlaybackError("Invalid Spotify URI");
774
// Get the track from the link
775
sp_track* track = sp_link_as_track(link);
777
SendPlaybackError("Spotify URI was not a track");
778
sp_link_release(link);
782
PendingPlaybackRequest pending_playback;
783
pending_playback.request_ = req;
784
pending_playback.link_ = link;
785
pending_playback.track_ = track;
787
pending_playback_requests_ << pending_playback;
789
TryPlaybackAgain(pending_playback);
792
void SpotifyClient::Seek(qint64 offset_bytes) {
794
qLog(Error) << "TODO seeking";
797
void SpotifyClient::TryPlaybackAgain(const PendingPlaybackRequest& req) {
798
// If the track was not loaded then we have to come back later
799
if (!sp_track_is_loaded(req.track_)) {
800
qLog(Debug) << "Playback track not loaded yet, will try again later";
804
// Remove this from the pending list now
805
pending_playback_requests_.removeAll(req);
808
sp_error error = sp_session_player_load(session_, req.track_);
809
if (error != SP_ERROR_OK) {
810
SendPlaybackError("Spotify playback error: " +
811
QString::fromUtf8(sp_error_message(error)));
812
sp_link_release(req.link_);
816
// Create the media socket
817
media_pipeline_.reset(new MediaPipeline(req.request_.media_port(),
818
sp_track_duration(req.track_)));
820
qLog(Info) << "Starting playback of uri" << req.request_.track_uri().c_str()
821
<< "to port" << req.request_.media_port();
824
sp_session_player_play(session_, true);
826
sp_link_release(req.link_);
829
void SpotifyClient::SendPlaybackError(const QString& error) {
830
spotify_pb::SpotifyMessage message;
831
spotify_pb::PlaybackError* msg = message.mutable_playback_error();
833
msg->set_error(DataCommaSizeFromQString(error));
834
handler_->SendMessage(message);
837
void SpotifyClient::LoadImage(const QString& id_b64) {
838
QByteArray id = QByteArray::fromBase64(id_b64.toAscii());
839
if (id.length() != kSpotifyImageIDSize) {
840
qLog(Warning) << "Invalid image ID (did not decode to"
841
<< kSpotifyImageIDSize << "bytes):" << id_b64;
843
// Send an error response straight away
844
spotify_pb::SpotifyMessage message;
845
spotify_pb::ImageResponse* msg = message.mutable_image_response();
846
msg->set_id(DataCommaSizeFromQString(id_b64));
847
handler_->SendMessage(message);
851
PendingImageRequest pending_load;
852
pending_load.id_ = id;
853
pending_load.id_b64_ = id_b64;
854
pending_load.image_ = sp_image_create(session_,
855
reinterpret_cast<const byte*>(id.constData()));
856
pending_image_requests_ << pending_load;
858
if (!image_callbacks_registered_[pending_load.image_]) {
859
sp_image_add_load_callback(pending_load.image_, &ImageLoaded, this);
861
image_callbacks_registered_[pending_load.image_] ++;
863
TryImageAgain(pending_load.image_);
866
void SpotifyClient::TryImageAgain(sp_image* image) {
867
if (!sp_image_is_loaded(image)) {
868
qLog(Debug) << "Image not loaded, will try again later";
872
// Find the pending request for this image
874
PendingImageRequest* req = NULL;
875
for (int i=0 ; i<pending_image_requests_.count() ; ++i) {
876
if (pending_image_requests_[i].image_ == image) {
878
req = &pending_image_requests_[i];
884
qLog(Warning) << "Image not found in pending load list";
888
// Get the image data
890
const void* data = sp_image_data(image, &size);
893
spotify_pb::SpotifyMessage message;
894
spotify_pb::ImageResponse* msg = message.mutable_image_response();
895
msg->set_id(DataCommaSizeFromQString(req->id_b64_));
897
msg->set_data(data, size);
899
handler_->SendMessage(message);
902
image_callbacks_registered_[image] --;
904
// TODO: memory leak?
905
// sp_image_remove_load_callback(image, &ImageLoaded, this);
906
image_callbacks_registered_.remove(image);
908
sp_image_release(image);
909
pending_image_requests_.removeAt(index);
912
void SpotifyClient::ImageLoaded(sp_image* image, void* userdata) {
913
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
914
me->TryImageAgain(image);
917
void SpotifyClient::BrowseAlbum(const QString& uri) {
918
// Get a link object from the URI
919
sp_link* link = sp_link_create_from_string(uri.toStdString().c_str());
921
SendPlaybackError("Invalid Album URI");
925
// Get the album from the link
926
sp_album* album = sp_link_as_album(link);
928
SendPlaybackError("Spotify URI was not an album");
929
sp_link_release(link);
933
sp_albumbrowse* browse =
934
sp_albumbrowse_create(session_, album, &AlbumBrowseComplete, this);
935
pending_album_browses_[browse] = uri;
938
void SpotifyClient::AlbumBrowseComplete(sp_albumbrowse* result, void* userdata) {
939
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
941
if (!me->pending_album_browses_.contains(result))
944
QString uri = me->pending_album_browses_.take(result);
946
spotify_pb::SpotifyMessage message;
947
spotify_pb::BrowseAlbumResponse* msg = message.mutable_browse_album_response();
949
msg->set_uri(DataCommaSizeFromQString(uri));
951
const int count = sp_albumbrowse_num_tracks(result);
952
for (int i=0 ; i<count ; ++i) {
953
me->ConvertTrack(sp_albumbrowse_track(result, i), msg->add_track());
956
me->handler_->SendMessage(message);
957
sp_albumbrowse_release(result);