Added Search functionality, and fixed navigation
[spotify:spotify.git] / libqspotify / src / qspotify.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QMLify research project.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qspotify.h"
43 #include "qspotifyaudiothread.h"
44
45 #include <libspotify/api.h>
46
47 #include <QString>
48 #include <QTime>
49
50 #include <QCoreApplication>
51 #include <QThread>
52 #include <QMutex>
53 #include <QLinkedList>
54
55 #include <QDebug>
56
57 #include "appkey.h"
58
59 class QSpotifyPrivate
60 {
61 public:
62     QSpotifyPrivate(QSpotify *spotify)
63         : session(0)
64         , processEventInterval(0)
65         , audioState(QSpotify::Stop)
66         , signInState(QSpotify::NotSignedIn)
67         , storredPlaylist(0)
68         , spotifyAudioThread(new QSpotifyAudioThread())
69         , finishedSearches(new QSpotifySearchResultModel())
70         , q_ptr(spotify)
71     {
72     }
73
74     sp_session *session;
75     QTimer processEventInterval;
76     QSpotify::AudioState audioState;
77     QSpotify::SignInState signInState;
78     QSpotifyPlaylistContainer *storredPlaylist;
79     QSpotifyAudioThread *spotifyAudioThread;
80     QMutex storredPlaylistMutex;
81     QList<sp_search *> searchingList;
82     QSpotifySearchResultModel *finishedSearches;
83
84     Q_DECLARE_PUBLIC(QSpotify);
85 private:
86     Q_DISABLE_COPY(QSpotifyPrivate);
87     QSpotify *q_ptr;
88
89 };
90
91 QSpotifyPrivate * libQSpotifyGetSpotifyPrivate(QSpotify *spotify)
92 {
93     return spotify->d_func();
94 }
95
96 static void logged_in(sp_session *session, sp_error error)
97 {
98     if (error == SP_ERROR_OK) {
99         sp_user *user = sp_session_user(session);
100         QString userString = QString::fromLatin1(sp_user_display_name(user));
101         QSpotify *spotify = static_cast<QSpotify *>(sp_session_userdata(session));
102         spotify->setSignInState(QSpotify::SignedIn);
103         QMetaObject::invokeMethod(spotify,"loggedIn",Q_ARG(QString,userString));
104     }
105 }
106
107 static void logged_out(sp_session *session)
108 {
109     QSpotify *spotify = static_cast<QSpotify *>(sp_session_userdata(session));
110     spotify->setSignInState(QSpotify::NotSignedIn);
111 }
112
113 static void metadata_updated(sp_session *)
114 {
115 }
116
117 static void connection_error(sp_session *, sp_error )
118 {
119     qDebug() << "connection_error callback not implemented";
120 }
121
122 static void message_to_user(sp_session *, const char *message)
123 {
124     qDebug() << "message to user: " << message;
125 }
126
127 static void notify_main_thread(sp_session *session)
128 {
129     QSpotify *spotify = static_cast<QSpotify *>(sp_session_userdata(session));
130     QMetaObject::invokeMethod(spotify,"processSpotifyEvents",Qt::QueuedConnection);
131 }
132
133 int music_delivery(sp_session *session, const sp_audioformat *format, const void *data, int size)
134 {
135     QSpotify *spotify = static_cast<QSpotify *>(sp_session_userdata(session));
136
137     static const sp_audioformat *spotifyAudioFormat= 0;
138     if (!spotifyAudioFormat) {
139         qDebug() << "Setting new audioformat";
140         spotifyAudioFormat = format;
141
142         QAudioFormat audioFormat;
143         audioFormat.setCodec("audio/pcm");
144         audioFormat.setChannels(2);
145         audioFormat.setFrequency(format->sample_rate);
146         switch (format->sample_type) {
147             case SP_SAMPLETYPE_INT16_NATIVE_ENDIAN:
148             default:
149                 {
150                     audioFormat.setSampleSize(16);
151                     audioFormat.setByteOrder(QAudioFormat::Endian(QSysInfo::ByteOrder));
152                     audioFormat.setSampleType(QAudioFormat::SignedInt);
153                 }
154         }
155         spotify->setNewAudioFormat(audioFormat);
156         QThread::currentThread();
157     }
158
159     return spotify->playCallback(data,qint64(size));
160 }
161
162 static void play_token_lost(sp_session *)
163 {
164     qDebug() << "play_token callback not implemented";
165 }
166
167 static void log_message(sp_session *, const char *data)
168 {
169     qDebug() << "LOG: " << data;
170 }
171
172 static void end_of_track(sp_session *)
173 {
174     qDebug() << "end_of_track callback not implemented";
175 }
176
177 QSpotify::QSpotify()
178     : QObject()
179     , d_ptr(new QSpotifyPrivate(this))
180 {
181 }
182
183 QSpotify::~QSpotify()
184 {
185 }
186
187 void QSpotify::login(const QString &login, const QString &pass)
188 {
189     if (login.isEmpty()|| login.isNull() || pass.isEmpty() || pass.isNull())
190         return;
191     Q_D(QSpotify);
192
193     setSignInState(QSpotify::TryingToSignIn);
194
195     sp_session_callbacks *callbacks = new sp_session_callbacks;
196     callbacks->logged_in = &logged_in;
197     callbacks->logged_out = &logged_out;
198     callbacks->metadata_updated = &metadata_updated;
199     callbacks->connection_error = &connection_error;
200     callbacks->message_to_user = &message_to_user;
201     callbacks->notify_main_thread = &notify_main_thread;
202     callbacks->music_delivery = &music_delivery;
203     callbacks->play_token_lost = &play_token_lost;
204     callbacks->log_message = &log_message;
205     callbacks->end_of_track = &end_of_track;
206     callbacks->streaming_error = 0;
207     callbacks->userinfo_updated = 0;
208     callbacks->start_playback = 0;
209     callbacks->stop_playback = 0;
210     callbacks->get_audio_buffer_stats = 0;
211
212     sp_session_config *config = new sp_session_config;
213     config->api_version = SPOTIFY_API_VERSION;
214     config->cache_location = "temp";
215     config->settings_location = "temp";
216     config->application_key = g_appkey;
217     config->application_key_size = g_appkey_size;
218     config->user_agent = "qspot";
219     config->callbacks = callbacks;
220     config->userdata = this;
221
222     sp_session_create(config,&d->session);
223
224     sp_session_login(d->session,login.toLocal8Bit().data(),pass.toLocal8Bit().data());
225     d->processEventInterval.setSingleShot(true);
226     connect(&d->processEventInterval,SIGNAL(timeout()),SLOT(processSpotifyEvents()));
227     d->processEventInterval.start(20);
228
229 }
230
231 QSpotifyPlaylistContainer *QSpotify::storredPlaylists()
232 {
233     Q_D(QSpotify);
234     if (!d->storredPlaylist) {
235         d->storredPlaylistMutex.lock();
236         if (!d->storredPlaylist) {
237             d->storredPlaylist = new QSpotifyPlaylistContainer(sp_session_playlistcontainer(d->session));
238             connect(d->storredPlaylist,SIGNAL(containerFullyLoaded()),SIGNAL(storredPlaylistsFullyLoaded()));
239         }
240         d->storredPlaylistMutex.unlock();
241     }
242
243     return d->storredPlaylist;
244 }
245
246 bool QSpotify::isLoggedIn()
247 {
248     Q_D(QSpotify);
249     if (d->session) {
250         if (sp_session_user(d->session))
251             return true;
252     }
253     return false;
254 }
255
256 QString QSpotify::userName()
257 {
258     Q_D(QSpotify);
259     if (d->session) {
260         if (sp_user *user = sp_session_user(d->session))
261         {
262             return QString(sp_user_display_name(user));
263         }
264     }
265     return QString();
266 }
267
268 void search_complete (sp_search *result, void *userdata)
269 {
270     Q_UNUSED(result);
271     Q_UNUSED(userdata);
272     QSpotifySearchResult *searchResult = new QSpotifySearchResult(result);
273     QSpotifyPrivate *spotifyPrivate = static_cast<QSpotifyPrivate *>(userdata);
274     if (spotifyPrivate) {
275         int indexOfSearch = spotifyPrivate->searchingList.indexOf(result);
276         qDebug() << result;
277         if (indexOfSearch == -1) {
278             qWarning("Some search was finished, but QSpotify doesn't know about it");
279             return;
280         }
281         spotifyPrivate->searchingList.takeAt(indexOfSearch);
282         spotifyPrivate->finishedSearches->addResult(searchResult);
283     }
284 }
285
286 void QSpotify::search(const QString &query, int trackOffset,int trackCount,
287                                        int albumOffset, int albumCount,
288                                        int artistOffset, int artistCount)
289 {
290     Q_D(QSpotify);
291     sp_search *search = sp_search_create(d->session,query.toLatin1(),trackOffset,trackCount,
292                      albumOffset,albumCount,artistOffset,artistCount,&search_complete,d);
293     d->searchingList.append(search);
294
295 }
296
297 void QSpotify::play(QSpotifyTrack *track)
298 {
299     qDebug() << "Playing track" << track;
300     if (!track)
301         return;
302
303     if (!track->isLoaded())
304     {
305         qDebug() << "not loaded";
306         return;
307     }
308
309     Q_D(QSpotify);
310
311     if (audioState() == Play) {
312         sp_error error = sp_session_player_play(d->session,false);
313         if (error != SP_ERROR_OK) {
314             qDebug() << "error3" << error;
315             return;
316         }
317         sp_session_player_unload(d->session);
318     }
319
320     sp_error error = sp_session_player_load(d->session,track->spTrack());
321     if (error != SP_ERROR_OK) {
322         qDebug() << "error1" << error;
323         return; //should emit some error signal.
324     }
325
326     error = sp_session_player_play(d->session,true);
327     if (error != SP_ERROR_OK) {
328         qDebug() << "error2 " << error;
329         return; //should emit some error signal.
330     }
331
332     setAudioState(Play);
333 }
334
335 int QSpotify::playCallback(const void *data, int frames)
336 {
337     Q_D(QSpotify);
338     return d->spotifyAudioThread->writeFrames(data,frames);
339 }
340
341 QSpotify::AudioState QSpotify::audioState() const
342 {
343     Q_D(const QSpotify);
344     return d->audioState;
345 }
346
347 void QSpotify::setNewAudioFormat(const QAudioFormat &format)
348 {
349     Q_D(QSpotify);
350     //QSpotifyAudioThread::setNewAudioFormat is not thread safe
351     //Execute synchroniously, since we are going to stream audio to the thread
352     //straight after this call.
353     qRegisterMetaType<QAudioFormat>("QAudioFormat");
354     QMetaObject::invokeMethod(d->spotifyAudioThread,"setNewAudioFormat",Qt::BlockingQueuedConnection,Q_ARG(const QAudioFormat,format));
355 }
356
357 void QSpotify::togglePauseContinue()
358 {
359     Q_D(QSpotify);
360     if (d->audioState == Pause) {
361         setAudioState(Play);
362     } else if ( d->audioState == Play) {
363         setAudioState(Pause);
364     }
365 }
366
367 void QSpotify::stop()
368 {
369     setAudioState(Stop);
370 }
371
372 void QSpotify::processSpotifyEvents()
373 {
374     Q_D(QSpotify);
375     int timeout = -1;
376     sp_session_process_events(d->session,&timeout);
377     if (timeout < 0)
378         timeout = 20;
379     d->processEventInterval.start(timeout);
380 }
381
382
383 void QSpotify::setAudioState(AudioState state)
384 {
385     Q_D(QSpotify);
386     if (d->audioState == state)
387         return;
388     d->audioState = state;
389     switch (d->audioState) {
390     case Play:
391         d->spotifyAudioThread->startAudio();
392         break;
393     case Pause:
394         d->spotifyAudioThread->pauseAudio();
395         break;
396     case Stop:
397         d->spotifyAudioThread->flushAndReset();
398         break;
399     }
400
401     emit audioStateChanged(d->audioState);
402 }
403
404 QSpotify::SignInState QSpotify::signInState() const
405 {
406     Q_D(const QSpotify);
407     return d->signInState;
408 }
409
410 void QSpotify::setSignInState(QSpotify::SignInState state)
411 {
412     Q_D(QSpotify);
413     if (d->signInState == state)
414         return;
415     d->signInState = state;
416     if (d->signInState == QSpotify::SignedIn)
417         storredPlaylists();
418     emit signInStateChanged(d->signInState);
419 }
420
421 QSpotifySearchResultModel *QSpotify::searchModel() const
422 {
423     Q_D(const QSpotify);
424     return d->finishedSearches;
425 }