~ubuntu-branches/ubuntu/saucy/clementine/saucy

« back to all changes in this revision

Viewing changes to spotifyblob/blob/spotifyclient.cpp

  • Committer: Package Import Robot
  • Author(s): Thomas PIERSON
  • Date: 2012-01-01 20:43:39 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20120101204339-lsb6nndwhfy05sde
Tags: 1.0.1+dfsg-1
New upstream release. (Closes: #653926, #651611, #657391)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* This file is part of Clementine.
 
2
   Copyright 2011, David Sansome <me@davidsansome.com>
 
3
 
 
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
 
7
 
 
8
       http://www.apache.org/licenses/LICENSE-2.0
 
9
 
 
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.
 
15
*/
 
16
 
 
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
 
19
// compatible.
 
20
 
 
21
 
 
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"
 
29
 
 
30
#include <QCoreApplication>
 
31
#include <QDir>
 
32
#include <QHostAddress>
 
33
#include <QTcpSocket>
 
34
#include <QTimer>
 
35
 
 
36
const int SpotifyClient::kSpotifyImageIDSize = 20;
 
37
const int SpotifyClient::kWaveHeaderSize = 44;
 
38
 
 
39
 
 
40
SpotifyClient::SpotifyClient(QObject* parent)
 
41
  : QObject(parent),
 
42
    api_key_(QByteArray::fromBase64(kSpotifyApiKey)),
 
43
    protocol_socket_(new QTcpSocket(this)),
 
44
    handler_(new SpotifyMessageHandler(protocol_socket_, this)),
 
45
    session_(NULL),
 
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_));
 
52
 
 
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;
 
65
 
 
66
 
 
67
  playlistcontainer_callbacks_.container_loaded = &PlaylistContainerLoadedCallback;
 
68
  playlistcontainer_callbacks_.playlist_added = &PlaylistAddedCallback;
 
69
  playlistcontainer_callbacks_.playlist_moved = &PlaylistMovedCallback;
 
70
  playlistcontainer_callbacks_.playlist_removed = &PlaylistRemovedCallback;
 
71
 
 
72
  get_playlists_callbacks_.playlist_state_changed = &PlaylistStateChangedForGetPlaylists;
 
73
 
 
74
  load_playlist_callbacks_.playlist_state_changed = &PlaylistStateChangedForLoadPlaylist;
 
75
 
 
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";
 
80
 
 
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";
 
89
 
 
90
  events_timer_->setSingleShot(true);
 
91
  connect(events_timer_, SIGNAL(timeout()), SLOT(ProcessEvents()));
 
92
 
 
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()));
 
97
}
 
98
 
 
99
SpotifyClient::~SpotifyClient() {
 
100
  if (session_) {
 
101
    sp_session_release(session_);
 
102
  }
 
103
 
 
104
  free(const_cast<char*>(spotify_config_.cache_location));
 
105
  free(const_cast<char*>(spotify_config_.settings_location));
 
106
}
 
107
 
 
108
void SpotifyClient::Init(quint16 port) {
 
109
  qLog(Debug) << "Connecting to port" << port;
 
110
 
 
111
  protocol_socket_->connectToHost(QHostAddress::LocalHost, port);
 
112
}
 
113
 
 
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;
 
118
 
 
119
  if (!success) {
 
120
    qLog(Warning) << "Failed to login" << sp_error_message(error);
 
121
  }
 
122
 
 
123
  switch (error) {
 
124
  case SP_ERROR_BAD_USERNAME_OR_PASSWORD:
 
125
    error_code = spotify_pb::LoginResponse_Error_BadUsernameOrPassword;
 
126
    break;
 
127
  case SP_ERROR_USER_BANNED:
 
128
    error_code = spotify_pb::LoginResponse_Error_UserBanned;
 
129
    break;
 
130
  case SP_ERROR_USER_NEEDS_PREMIUM :
 
131
    error_code = spotify_pb::LoginResponse_Error_UserNeedsPremium;
 
132
    break;
 
133
  default:
 
134
    error_code = spotify_pb::LoginResponse_Error_Other;
 
135
    break;
 
136
  }
 
137
 
 
138
  me->SendLoginCompleted(success, sp_error_message(error), error_code);
 
139
 
 
140
  if (success) {
 
141
    sp_playlistcontainer_add_callbacks(
 
142
          sp_session_playlistcontainer(session),
 
143
          &me->playlistcontainer_callbacks_, me);
 
144
  }
 
145
}
 
146
 
 
147
void SpotifyClient::NotifyMainThreadCallback(sp_session* session) {
 
148
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
 
149
  QMetaObject::invokeMethod(me, "ProcessEvents", Qt::QueuedConnection);
 
150
}
 
151
 
 
152
void SpotifyClient::ProcessEvents() {
 
153
  int next_timeout_ms;
 
154
  sp_session_process_events(session_, &next_timeout_ms);
 
155
  events_timer_->start(next_timeout_ms);
 
156
}
 
157
 
 
158
void SpotifyClient::LogMessageCallback(sp_session* session, const char* data) {
 
159
  qLog(Debug) << "libspotify:" << QString::fromUtf8(data).trimmed();
 
160
}
 
161
 
 
162
void SpotifyClient::Search(const spotify_pb::SearchRequest& req) {
 
163
  sp_search* search = sp_search_create(
 
164
        session_, req.query().c_str(),
 
165
        0, req.limit(),
 
166
        0, req.limit_album(),
 
167
        0, 0, // artists
 
168
        &SearchCompleteCallback, this);
 
169
 
 
170
  pending_searches_[search] = req;
 
171
}
 
172
 
 
173
void SpotifyClient::SearchCompleteCallback(sp_search* result, void* userdata) {
 
174
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
175
 
 
176
  if (!me->pending_searches_.contains(result)) {
 
177
    qLog(Warning) << "SearchComplete called with unknown search";
 
178
    return;
 
179
  }
 
180
 
 
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);
 
184
  if (count != 0) {
 
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);
 
189
 
 
190
      me->pending_search_album_browse_responses_[browse] = result;
 
191
    }
 
192
    return;
 
193
  }
 
194
 
 
195
  me->SendSearchResponse(result);
 
196
}
 
197
 
 
198
void SpotifyClient::SearchAlbumBrowseComplete(sp_albumbrowse* result, void* userdata) {
 
199
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
200
 
 
201
  if (!me->pending_search_album_browse_responses_.contains(result)) {
 
202
    qLog(Warning) << "SearchAlbumBrowseComplete called with unknown result";
 
203
    return;
 
204
  }
 
205
 
 
206
  sp_search* search = me->pending_search_album_browse_responses_.take(result);
 
207
  me->pending_search_album_browses_[search].append(result);
 
208
 
 
209
  if (me->pending_search_album_browses_[search].count() >= sp_search_num_albums(search)) {
 
210
    me->SendSearchResponse(search);
 
211
  }
 
212
}
 
213
 
 
214
void SpotifyClient::SendSearchResponse(sp_search* result) {
 
215
  // Take the request out of the queue
 
216
  spotify_pb::SearchRequest req = pending_searches_.take(result);
 
217
 
 
218
  // Prepare the response
 
219
  spotify_pb::SpotifyMessage message;
 
220
  spotify_pb::SearchResponse* response = message.mutable_search_response();
 
221
 
 
222
  *response->mutable_request() = req;
 
223
 
 
224
  // Check for errors
 
225
  sp_error error = sp_search_error(result);
 
226
  if (error != SP_ERROR_OK) {
 
227
    response->set_error(sp_error_message(error));
 
228
 
 
229
    handler_->SendMessage(message);
 
230
    sp_search_release(result);
 
231
    return;
 
232
  }
 
233
 
 
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());
 
239
  }
 
240
 
 
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();
 
246
 
 
247
    ConvertAlbum(album, msg->mutable_metadata());
 
248
    ConvertAlbumBrowse(browse, msg->mutable_metadata());
 
249
 
 
250
    // Add all tracks
 
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());
 
254
    }
 
255
 
 
256
    sp_albumbrowse_release(browse);
 
257
  }
 
258
 
 
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));
 
262
 
 
263
  handler_->SendMessage(message);
 
264
  sp_search_release(result);
 
265
}
 
266
 
 
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());
 
286
  }
 
287
}
 
288
 
 
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;
 
295
  }
 
296
 
 
297
  qLog(Debug) << "Setting playback settings: bitrate"
 
298
              << bitrate << "normalisation" << req.volume_normalisation();
 
299
 
 
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());
 
303
}
 
304
 
 
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);
 
310
    return;
 
311
  }
 
312
 
 
313
  SetPlaybackSettings(req.playback_settings());
 
314
 
 
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);
 
321
    }
 
322
  } else {
 
323
    sp_session_login(session_,
 
324
                     req.username().c_str(),
 
325
                     req.password().c_str(),
 
326
                     true);  // Remember the password.
 
327
  }
 
328
}
 
329
 
 
330
void SpotifyClient::SendLoginCompleted(bool success, const QString& error,
 
331
                                       spotify_pb::LoginResponse_Error error_code) {
 
332
  spotify_pb::SpotifyMessage message;
 
333
 
 
334
  spotify_pb::LoginResponse* response = message.mutable_login_response();
 
335
  response->set_success(success);
 
336
  response->set_error(DataCommaSizeFromQString(error));
 
337
 
 
338
  if (!success) {
 
339
    response->set_error_code(error_code);
 
340
  }
 
341
 
 
342
  handler_->SendMessage(message);
 
343
}
 
344
 
 
345
void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, void* userdata) {
 
346
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
347
 
 
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);
 
353
  }
 
354
 
 
355
  me->SendPlaylistList();
 
356
}
 
357
 
 
358
void SpotifyClient::PlaylistAddedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) {
 
359
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
360
 
 
361
  // Install callbacks on this playlist
 
362
  sp_playlist_add_callbacks(playlist, &me->get_playlists_callbacks_, me);
 
363
 
 
364
  me->SendPlaylistList();
 
365
}
 
366
 
 
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();
 
370
}
 
371
 
 
372
void SpotifyClient::PlaylistRemovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) {
 
373
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
374
 
 
375
  // Remove callbacks from this playlist
 
376
  sp_playlist_remove_callbacks(playlist, &me->get_playlists_callbacks_, me);
 
377
 
 
378
  me->SendPlaylistList();
 
379
}
 
380
 
 
381
void SpotifyClient::SendPlaylistList() {
 
382
  spotify_pb::SpotifyMessage message;
 
383
  spotify_pb::Playlists* response = message.mutable_playlists_updated();
 
384
 
 
385
  sp_playlistcontainer* container = sp_session_playlistcontainer(session_);
 
386
  if (!container) {
 
387
    qLog(Warning) << "sp_session_playlistcontainer returned NULL";
 
388
    return;
 
389
  }
 
390
 
 
391
  const int count = sp_playlistcontainer_num_playlists(container);
 
392
 
 
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);
 
397
 
 
398
    qLog(Debug) << "Got playlist" << i << is_loaded << type << sp_playlist_name(playlist);
 
399
 
 
400
    if (!is_loaded) {
 
401
      qLog(Info) << "Playlist is not loaded yet, waiting...";
 
402
      return;
 
403
    }
 
404
 
 
405
    if (type != SP_PLAYLIST_TYPE_PLAYLIST) {
 
406
      // Just ignore folders for now
 
407
      continue;
 
408
    }
 
409
 
 
410
    spotify_pb::Playlists::Playlist* msg = response->add_playlist();
 
411
    msg->set_index(i);
 
412
    msg->set_name(sp_playlist_name(playlist));
 
413
 
 
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);
 
423
    }
 
424
  }
 
425
 
 
426
  handler_->SendMessage(message);
 
427
}
 
428
 
 
429
sp_playlist* SpotifyClient::GetPlaylist(spotify_pb::PlaylistType type, int user_index) {
 
430
  sp_playlist* playlist = NULL;
 
431
  switch (type) {
 
432
    case spotify_pb::Inbox:
 
433
      playlist = sp_session_inbox_create(session_);
 
434
      break;
 
435
 
 
436
    case spotify_pb::Starred:
 
437
      playlist = sp_session_starred_create(session_);
 
438
      break;
 
439
 
 
440
    case spotify_pb::UserPlaylist: {
 
441
      sp_playlistcontainer* pc = sp_session_playlistcontainer(session_);
 
442
 
 
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);
 
447
        }
 
448
      }
 
449
 
 
450
      break;
 
451
    }
 
452
  }
 
453
  return playlist;
 
454
}
 
455
 
 
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());
 
460
 
 
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";
 
465
 
 
466
    spotify_pb::SpotifyMessage message;
 
467
    spotify_pb::LoadPlaylistResponse* response = message.mutable_load_playlist_response();
 
468
    *response->mutable_request() = req;
 
469
    handler_->SendMessage(message);
 
470
    return;
 
471
  }
 
472
 
 
473
  sp_playlist_add_callbacks(pending_load.playlist_, &load_playlist_callbacks_, this);
 
474
  pending_load_playlists_ << pending_load;
 
475
 
 
476
  PlaylistStateChangedForLoadPlaylist(pending_load.playlist_, this);
 
477
}
 
478
 
 
479
void SpotifyClient::SyncPlaylist(const spotify_pb::SyncPlaylistRequest& req) {
 
480
  sp_playlist* playlist = GetPlaylist(req.request().type(), req.request().user_playlist_index());
 
481
 
 
482
  // The playlist should already be loaded.
 
483
  sp_playlist_set_offline_mode(session_, playlist, req.offline_sync());
 
484
}
 
485
 
 
486
void SpotifyClient::PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* userdata) {
 
487
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
488
 
 
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";
 
492
    return;
 
493
  }
 
494
 
 
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];
 
502
      break;
 
503
    }
 
504
  }
 
505
 
 
506
  if (!pending_load) {
 
507
    qLog(Warning) << "Playlist not found in pending load list";
 
508
    return;
 
509
  }
 
510
 
 
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;
 
518
    }
 
519
  }
 
520
 
 
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";
 
525
      return;
 
526
    }
 
527
  }
 
528
 
 
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();
 
532
 
 
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);
 
537
  }
 
538
  me->handler_->SendMessage(message);
 
539
 
 
540
  // Unref the playlist and remove our callbacks
 
541
  sp_playlist_remove_callbacks(pl, &me->load_playlist_callbacks_, me);
 
542
  sp_playlist_release(pl);
 
543
 
 
544
  // Remove the pending load object
 
545
  me->pending_load_playlists_.removeAt(pending_load_index);
 
546
}
 
547
 
 
548
void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void* userdata) {
 
549
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
550
 
 
551
  me->SendPlaylistList();
 
552
}
 
553
 
 
554
void SpotifyClient::ConvertTrack(sp_track* track, spotify_pb::Track* pb) {
 
555
  sp_album* album = sp_track_album(track);
 
556
 
 
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));
 
565
 
 
566
  // Album art
 
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));
 
572
 
 
573
  // Artists
 
574
  for (int i=0 ; i<sp_track_num_artists(track) ; ++i) {
 
575
    pb->add_artist(sp_artist_name(sp_track_artist(track, i)));
 
576
  }
 
577
 
 
578
  // URI - Blugh
 
579
  char uri[256];
 
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);
 
583
 
 
584
  pb->set_uri(uri);
 
585
}
 
586
 
 
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)));
 
591
 
 
592
  // These fields were required in a previous version so need to set them again
 
593
  // now.
 
594
  pb->mutable_title();
 
595
  pb->set_duration_msec(-1);
 
596
  pb->set_popularity(-1);
 
597
  pb->set_disc(-1);
 
598
  pb->set_track(-1);
 
599
  pb->set_starred(false);
 
600
 
 
601
  // Album art
 
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));
 
607
 
 
608
  // URI - Blugh
 
609
  char uri[256];
 
610
  sp_link* link = sp_link_create_from_album(album);
 
611
  sp_link_as_string(link, uri, sizeof(uri));
 
612
  sp_link_release(link);
 
613
 
 
614
  pb->set_uri(uri);
 
615
}
 
616
 
 
617
void SpotifyClient::ConvertAlbumBrowse(sp_albumbrowse* browse, spotify_pb::Track* pb) {
 
618
  pb->set_track(sp_albumbrowse_num_tracks(browse));
 
619
}
 
620
 
 
621
void SpotifyClient::MetadataUpdatedCallback(sp_session* session) {
 
622
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
 
623
 
 
624
  foreach (const PendingLoadPlaylist& load, me->pending_load_playlists_) {
 
625
    PlaylistStateChangedForLoadPlaylist(load.playlist_, me);
 
626
  }
 
627
  foreach (const PendingPlaybackRequest& playback, me->pending_playback_requests_) {
 
628
    me->TryPlaybackAgain(playback);
 
629
  }
 
630
}
 
631
 
 
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));
 
636
 
 
637
  if (!me->media_pipeline_) {
 
638
    return 0;
 
639
  }
 
640
 
 
641
  if (num_frames == 0) {
 
642
    return 0;
 
643
  }
 
644
 
 
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();
 
650
      return 0;
 
651
    }
 
652
  }
 
653
 
 
654
  if (!me->media_pipeline_->is_accepting_data()) {
 
655
    return 0;
 
656
  }
 
657
 
 
658
  me->media_pipeline_->WriteData(
 
659
        reinterpret_cast<const char*>(frames),
 
660
        num_frames * format->channels * 2);
 
661
 
 
662
  return num_frames;
 
663
}
 
664
 
 
665
void SpotifyClient::EndOfTrackCallback(sp_session* session) {
 
666
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
 
667
 
 
668
  me->media_pipeline_.reset();
 
669
}
 
670
 
 
671
void SpotifyClient::StreamingErrorCallback(sp_session* session, sp_error error) {
 
672
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
 
673
 
 
674
  me->media_pipeline_.reset();
 
675
 
 
676
  // Send the error
 
677
  me->SendPlaybackError(QString::fromUtf8(sp_error_message(error)));
 
678
}
 
679
 
 
680
void SpotifyClient::ConnectionErrorCallback(sp_session* session, sp_error error) {
 
681
  qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
 
682
}
 
683
 
 
684
void SpotifyClient::UserMessageCallback(sp_session* session, const char* message) {
 
685
  qLog(Debug) << Q_FUNC_INFO << message;
 
686
}
 
687
 
 
688
void SpotifyClient::StartPlaybackCallback(sp_session* session) {
 
689
  qLog(Debug) << Q_FUNC_INFO;
 
690
}
 
691
 
 
692
void SpotifyClient::StopPlaybackCallback(sp_session* session) {
 
693
  qLog(Debug) << Q_FUNC_INFO;
 
694
}
 
695
 
 
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);
 
699
  if (!container) {
 
700
    qLog(Warning) << "sp_session_playlistcontainer returned NULL";
 
701
    return;
 
702
  }
 
703
 
 
704
  const int count = sp_playlistcontainer_num_playlists(container);
 
705
 
 
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);
 
709
 
 
710
    if (type != SP_PLAYLIST_TYPE_PLAYLIST) {
 
711
      // Just ignore folders for now
 
712
      continue;
 
713
    }
 
714
 
 
715
    int download_progress = me->GetDownloadProgress(playlist);
 
716
    if (download_progress != -1) {
 
717
      me->SendDownloadProgress(spotify_pb::UserPlaylist, i, download_progress);
 
718
    }
 
719
  }
 
720
 
 
721
  sp_playlist* inbox = sp_session_inbox_create(session);
 
722
  int download_progress = me->GetDownloadProgress(inbox);
 
723
  sp_playlist_release(inbox);
 
724
 
 
725
  if (download_progress != -1) {
 
726
    me->SendDownloadProgress(spotify_pb::Inbox, -1, download_progress);
 
727
  }
 
728
 
 
729
  sp_playlist* starred = sp_session_starred_create(session);
 
730
  download_progress = me->GetDownloadProgress(starred);
 
731
  sp_playlist_release(starred);
 
732
 
 
733
  if (download_progress != -1) {
 
734
    me->SendDownloadProgress(spotify_pb::Starred, -1, download_progress);
 
735
  }
 
736
}
 
737
 
 
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);
 
743
  if (index != -1) {
 
744
    progress->mutable_request()->set_user_playlist_index(index);
 
745
  }
 
746
  progress->set_sync_progress(download_progress);
 
747
  handler_->SendMessage(message);
 
748
}
 
749
 
 
750
int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) {
 
751
  sp_playlist_offline_status status =
 
752
    sp_playlist_get_offline_status(session_, playlist);
 
753
  switch (status) {
 
754
    case SP_PLAYLIST_OFFLINE_STATUS_NO:
 
755
      return -1;
 
756
    case SP_PLAYLIST_OFFLINE_STATUS_YES:
 
757
      return 100;
 
758
    case SP_PLAYLIST_OFFLINE_STATUS_DOWNLOADING:
 
759
      return sp_playlist_get_offline_download_completed(session_, playlist);
 
760
    case SP_PLAYLIST_OFFLINE_STATUS_WAITING:
 
761
      return 0;
 
762
  }
 
763
  return -1;
 
764
}
 
765
 
 
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());
 
769
  if (!link) {
 
770
    SendPlaybackError("Invalid Spotify URI");
 
771
    return;
 
772
  }
 
773
 
 
774
  // Get the track from the link
 
775
  sp_track* track = sp_link_as_track(link);
 
776
  if (!track) {
 
777
    SendPlaybackError("Spotify URI was not a track");
 
778
    sp_link_release(link);
 
779
    return;
 
780
  }
 
781
 
 
782
  PendingPlaybackRequest pending_playback;
 
783
  pending_playback.request_ = req;
 
784
  pending_playback.link_ = link;
 
785
  pending_playback.track_ = track;
 
786
 
 
787
  pending_playback_requests_ << pending_playback;
 
788
 
 
789
  TryPlaybackAgain(pending_playback);
 
790
}
 
791
 
 
792
void SpotifyClient::Seek(qint64 offset_bytes) {
 
793
  // TODO
 
794
  qLog(Error) << "TODO seeking";
 
795
}
 
796
 
 
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";
 
801
    return;
 
802
  }
 
803
 
 
804
  // Remove this from the pending list now
 
805
  pending_playback_requests_.removeAll(req);
 
806
 
 
807
  // Load the track
 
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_);
 
813
    return;
 
814
  }
 
815
 
 
816
  // Create the media socket
 
817
  media_pipeline_.reset(new MediaPipeline(req.request_.media_port(),
 
818
                                          sp_track_duration(req.track_)));
 
819
 
 
820
  qLog(Info) << "Starting playback of uri" << req.request_.track_uri().c_str()
 
821
             << "to port" << req.request_.media_port();
 
822
 
 
823
  // Start playback
 
824
  sp_session_player_play(session_, true);
 
825
 
 
826
  sp_link_release(req.link_);
 
827
}
 
828
 
 
829
void SpotifyClient::SendPlaybackError(const QString& error) {
 
830
  spotify_pb::SpotifyMessage message;
 
831
  spotify_pb::PlaybackError* msg = message.mutable_playback_error();
 
832
 
 
833
  msg->set_error(DataCommaSizeFromQString(error));
 
834
  handler_->SendMessage(message);
 
835
}
 
836
 
 
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;
 
842
 
 
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);
 
848
    return;
 
849
  }
 
850
 
 
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;
 
857
 
 
858
  if (!image_callbacks_registered_[pending_load.image_]) {
 
859
    sp_image_add_load_callback(pending_load.image_, &ImageLoaded, this);
 
860
  }
 
861
  image_callbacks_registered_[pending_load.image_] ++;
 
862
 
 
863
  TryImageAgain(pending_load.image_);
 
864
}
 
865
 
 
866
void SpotifyClient::TryImageAgain(sp_image* image) {
 
867
  if (!sp_image_is_loaded(image)) {
 
868
    qLog(Debug) << "Image not loaded, will try again later";
 
869
    return;
 
870
  }
 
871
 
 
872
  // Find the pending request for this image
 
873
  int index = -1;
 
874
  PendingImageRequest* req = NULL;
 
875
  for (int i=0 ; i<pending_image_requests_.count() ; ++i) {
 
876
    if (pending_image_requests_[i].image_ == image) {
 
877
      index = i;
 
878
      req = &pending_image_requests_[i];
 
879
      break;
 
880
    }
 
881
  }
 
882
 
 
883
  if (index == -1) {
 
884
    qLog(Warning) << "Image not found in pending load list";
 
885
    return;
 
886
  }
 
887
 
 
888
  // Get the image data
 
889
  size_t size = 0;
 
890
  const void* data = sp_image_data(image, &size);
 
891
 
 
892
  // Send the response
 
893
  spotify_pb::SpotifyMessage message;
 
894
  spotify_pb::ImageResponse* msg = message.mutable_image_response();
 
895
  msg->set_id(DataCommaSizeFromQString(req->id_b64_));
 
896
  if (data && size) {
 
897
    msg->set_data(data, size);
 
898
  }
 
899
  handler_->SendMessage(message);
 
900
 
 
901
  // Free stuff
 
902
  image_callbacks_registered_[image] --;
 
903
 
 
904
  // TODO: memory leak?
 
905
  // sp_image_remove_load_callback(image, &ImageLoaded, this);
 
906
  image_callbacks_registered_.remove(image);
 
907
 
 
908
  sp_image_release(image);
 
909
  pending_image_requests_.removeAt(index);
 
910
}
 
911
 
 
912
void SpotifyClient::ImageLoaded(sp_image* image, void* userdata) {
 
913
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
914
  me->TryImageAgain(image);
 
915
}
 
916
 
 
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());
 
920
  if (!link) {
 
921
    SendPlaybackError("Invalid Album URI");
 
922
    return;
 
923
  }
 
924
 
 
925
  // Get the album from the link
 
926
  sp_album* album = sp_link_as_album(link);
 
927
  if (!album) {
 
928
    SendPlaybackError("Spotify URI was not an album");
 
929
    sp_link_release(link);
 
930
    return;
 
931
  }
 
932
 
 
933
  sp_albumbrowse* browse =
 
934
      sp_albumbrowse_create(session_, album, &AlbumBrowseComplete, this);
 
935
  pending_album_browses_[browse] = uri;
 
936
}
 
937
 
 
938
void SpotifyClient::AlbumBrowseComplete(sp_albumbrowse* result, void* userdata) {
 
939
  SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
 
940
 
 
941
  if (!me->pending_album_browses_.contains(result))
 
942
    return;
 
943
 
 
944
  QString uri = me->pending_album_browses_.take(result);
 
945
 
 
946
  spotify_pb::SpotifyMessage message;
 
947
  spotify_pb::BrowseAlbumResponse* msg = message.mutable_browse_album_response();
 
948
 
 
949
  msg->set_uri(DataCommaSizeFromQString(uri));
 
950
 
 
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());
 
954
  }
 
955
 
 
956
  me->handler_->SendMessage(message);
 
957
  sp_albumbrowse_release(result);
 
958
}