1 /*  This file is part of the KDE project
2     Copyright (C) 2005-2007 Matthias Kretz <kretz@kde.org>
3     Copyright (C) 2011 Trever Fischer <tdfischer@kde.org>
4 
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Lesser General Public
7     License as published by the Free Software Foundation; either
8     version 2.1 of the License, or (at your option) version 3, or any
9     later version accepted by the membership of KDE e.V. (or its
10     successor approved by the membership of KDE e.V.), Nokia Corporation
11     (or its successors, if any) and the KDE Free Qt Foundation, which shall
12     act as a proxy defined in Section 6 of version 3 of the license.
13 
14     This library is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     Lesser General Public License for more details.
18 
19     You should have received a copy of the GNU Lesser General Public
20     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
21 
22 */
23 #include "mediaobject.h"
24 #include "mediaobject_p.h"
25 
26 #include "factory_p.h"
27 #include "mediaobjectinterface.h"
28 #include "audiooutput.h"
29 #include "phonondefs_p.h"
30 #include "abstractmediastream.h"
31 #include "abstractmediastream_p.h"
32 #include "frontendinterface_p.h"
33 
34 #include <QtCore/QStringBuilder>
35 #include <QtCore/QStringList>
36 #include <QtCore/QDateTime>
37 #include <QtCore/QTimer>
38 #include <QtCore/QUrl>
39 
40 #include "phononnamespace_p.h"
41 #include "platform_p.h"
42 #include "statesvalidator_p.h"
43 
44 #define PHONON_CLASSNAME MediaObject
45 #define PHONON_INTERFACENAME MediaObjectInterface
46 
47 namespace Phonon
48 {
49 PHONON_OBJECT_IMPL
50 
~MediaObject()51 MediaObject::~MediaObject()
52 {
53     P_D(MediaObject);
54     if (d->m_backendObject) {
55         switch (state()) {
56         case PlayingState:
57         case BufferingState:
58         case PausedState:
59             stop();
60             break;
61         case ErrorState:
62         case StoppedState:
63         case LoadingState:
64             break;
65         }
66     }
67 }
68 
state() const69 Phonon::State MediaObject::state() const
70 {
71     P_D(const MediaObject);
72 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
73     if (d->errorOverride) {
74         return d->state;
75     }
76     if (d->ignoreLoadingToBufferingStateChange) {
77         return BufferingState;
78     }
79     if (d->ignoreErrorToLoadingStateChange) {
80         return LoadingState;
81     }
82 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM
83     if (!d->m_backendObject) {
84         return d->state;
85     }
86     return INTERFACE_CALL(state());
87 }
88 
PHONON_INTERFACE_SETTER(setTickInterval,tickInterval,qint32)89 PHONON_INTERFACE_SETTER(setTickInterval, tickInterval, qint32)
90 PHONON_INTERFACE_GETTER(qint32, tickInterval, d->tickInterval)
91 PHONON_INTERFACE_GETTER(bool, hasVideo, false)
92 PHONON_INTERFACE_GETTER(bool, isSeekable, false)
93 PHONON_INTERFACE_GETTER(qint64, currentTime, d->currentTime)
94 
95 static inline bool isPlayable(const MediaSource::Type t)
96 {
97     return t != MediaSource::Invalid && t != MediaSource::Empty;
98 }
99 
play()100 void MediaObject::play()
101 {
102     P_D(MediaObject);
103     if (d->backendObject() && isPlayable(d->mediaSource.type())) {
104         INTERFACE_CALL(play());
105     }
106 }
107 
pause()108 void MediaObject::pause()
109 {
110     P_D(MediaObject);
111     if (d->backendObject() && isPlayable(d->mediaSource.type())) {
112         INTERFACE_CALL(pause());
113     }
114 }
115 
stop()116 void MediaObject::stop()
117 {
118     P_D(MediaObject);
119     if (d->backendObject() && isPlayable(d->mediaSource.type())) {
120         INTERFACE_CALL(stop());
121     }
122 }
123 
seek(qint64 time)124 void MediaObject::seek(qint64 time)
125 {
126     P_D(MediaObject);
127     if (d->backendObject() && isPlayable(d->mediaSource.type())) {
128         INTERFACE_CALL(seek(time));
129     }
130 }
131 
errorString() const132 QString MediaObject::errorString() const
133 {
134     if (state() == Phonon::ErrorState) {
135         P_D(const MediaObject);
136 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
137         if (d->errorOverride) {
138             return d->errorString;
139         }
140 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM
141         return INTERFACE_CALL(errorString());
142     }
143     return QString();
144 }
145 
errorType() const146 ErrorType MediaObject::errorType() const
147 {
148     if (state() == Phonon::ErrorState) {
149         P_D(const MediaObject);
150 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
151         if (d->errorOverride) {
152             return d->errorType;
153         }
154 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM
155         return INTERFACE_CALL(errorType());
156     }
157     return Phonon::NoError;
158 }
159 
metaData(Phonon::MetaData f) const160 QStringList MediaObject::metaData(Phonon::MetaData f) const
161 {
162     switch (f) {
163     case ArtistMetaData:
164         return metaData(QLatin1String("ARTIST"));
165     case AlbumMetaData:
166         return metaData(QLatin1String("ALBUM"));
167     case TitleMetaData:
168         return metaData(QLatin1String("TITLE"));
169     case DateMetaData:
170         return metaData(QLatin1String("DATE"));
171     case GenreMetaData:
172         return metaData(QLatin1String("GENRE"));
173     case TracknumberMetaData:
174         return metaData(QLatin1String("TRACKNUMBER"));
175     case DescriptionMetaData:
176         return metaData(QLatin1String("DESCRIPTION"));
177     case MusicBrainzDiscIdMetaData:
178         return metaData(QLatin1String("MUSICBRAINZ_DISCID"));
179     }
180     return QStringList();
181 }
182 
metaData(const QString & key) const183 QStringList MediaObject::metaData(const QString &key) const
184 {
185     P_D(const MediaObject);
186     return d->metaData.values(key);
187 }
188 
metaData() const189 QMultiMap<QString, QString> MediaObject::metaData() const
190 {
191     P_D(const MediaObject);
192     return d->metaData;
193 }
194 
195 PHONON_INTERFACE_GETTER(qint32, prefinishMark, d->prefinishMark)
PHONON_INTERFACE_SETTER(setPrefinishMark,prefinishMark,qint32)196 PHONON_INTERFACE_SETTER(setPrefinishMark, prefinishMark, qint32)
197 
198 PHONON_INTERFACE_GETTER(qint32, transitionTime, d->transitionTime)
199 PHONON_INTERFACE_SETTER(setTransitionTime, transitionTime, qint32)
200 
201 qint64 MediaObject::totalTime() const
202 {
203     P_D(const MediaObject);
204     if (!d->m_backendObject) {
205         return -1;
206     }
207     return INTERFACE_CALL(totalTime());
208 }
209 
remainingTime() const210 qint64 MediaObject::remainingTime() const
211 {
212     P_D(const MediaObject);
213     if (!d->m_backendObject) {
214         return -1;
215     }
216     qint64 ret = INTERFACE_CALL(remainingTime());
217     if (ret < 0) {
218         return -1;
219     }
220     return ret;
221 }
222 
currentSource() const223 MediaSource MediaObject::currentSource() const
224 {
225     P_D(const MediaObject);
226     return d->mediaSource;
227 }
228 
setCurrentSource(const MediaSource & newSource)229 void MediaObject::setCurrentSource(const MediaSource &newSource)
230 {
231     P_D(MediaObject);
232     if (!k_ptr->backendObject()) {
233         d->mediaSource = newSource;
234         return;
235     }
236 
237     pDebug() << Q_FUNC_INFO << newSource.type() << newSource.url() << newSource.deviceName();
238 
239     stop(); // first call stop as that often is the expected state
240             // for setting a new URL
241 
242     d->mediaSource = newSource;
243 
244 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
245     d->abstractStream = 0; // abstractStream auto-deletes
246     if (d->mediaSource.type() == MediaSource::Stream) {
247         Q_ASSERT(d->mediaSource.stream());
248         d->mediaSource.stream()->d_func()->setMediaObjectPrivate(d);
249     }
250 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM
251 
252     d->playingQueuedSource = false;
253 
254     INTERFACE_CALL(setSource(d->mediaSource));
255 }
256 
clear()257 void MediaObject::clear()
258 {
259     P_D(MediaObject);
260     d->sourceQueue.clear();
261     setCurrentSource(MediaSource());
262 }
263 
queue() const264 QList<MediaSource> MediaObject::queue() const
265 {
266     P_D(const MediaObject);
267     return d->sourceQueue;
268 }
269 
setQueue(const QList<MediaSource> & sources)270 void MediaObject::setQueue(const QList<MediaSource> &sources)
271 {
272     P_D(MediaObject);
273     d->sourceQueue.clear();
274     enqueue(sources);
275 }
276 
setQueue(const QList<QUrl> & urls)277 void MediaObject::setQueue(const QList<QUrl> &urls)
278 {
279     P_D(MediaObject);
280     d->sourceQueue.clear();
281     enqueue(urls);
282 }
283 
enqueue(const MediaSource & source)284 void MediaObject::enqueue(const MediaSource &source)
285 {
286     P_D(MediaObject);
287     if (!isPlayable(d->mediaSource.type())) {
288         // the current source is nothing valid so this source needs to become the current one
289         setCurrentSource(source);
290     } else {
291         d->sourceQueue << source;
292     }
293 }
294 
enqueue(const QList<MediaSource> & sources)295 void MediaObject::enqueue(const QList<MediaSource> &sources)
296 {
297     for (int i = 0; i < sources.count(); ++i) {
298         enqueue(sources.at(i));
299     }
300 }
301 
enqueue(const QList<QUrl> & urls)302 void MediaObject::enqueue(const QList<QUrl> &urls)
303 {
304     for (int i = 0; i < urls.count(); ++i) {
305         enqueue(urls.at(i));
306     }
307 }
308 
clearQueue()309 void MediaObject::clearQueue()
310 {
311     P_D(MediaObject);
312     d->sourceQueue.clear();
313 }
314 
aboutToDeleteBackendObject()315 bool MediaObjectPrivate::aboutToDeleteBackendObject()
316 {
317     //pDebug() << Q_FUNC_INFO;
318     prefinishMark = pINTERFACE_CALL(prefinishMark());
319     transitionTime = pINTERFACE_CALL(transitionTime());
320     //pDebug() << Q_FUNC_INFO;
321     if (m_backendObject) {
322         state = pINTERFACE_CALL(state());
323         currentTime = pINTERFACE_CALL(currentTime());
324         tickInterval = pINTERFACE_CALL(tickInterval());
325     }
326     return true;
327 }
328 
329 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
streamError(Phonon::ErrorType type,const QString & text)330 void MediaObjectPrivate::streamError(Phonon::ErrorType type, const QString &text)
331 {
332     P_Q(MediaObject);
333     State lastState = q->state();
334     errorOverride = true;
335     errorType = type;
336     errorString = text;
337     state = ErrorState;
338     QMetaObject::invokeMethod(q, "stateChanged", Qt::QueuedConnection, Q_ARG(Phonon::State, Phonon::ErrorState), Q_ARG(Phonon::State, lastState));
339     //emit q->stateChanged(ErrorState, lastState);
340 }
341 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM
342 
343 // TODO: this needs serious cleanup...
_k_stateChanged(Phonon::State newstate,Phonon::State oldstate)344 void MediaObjectPrivate::_k_stateChanged(Phonon::State newstate, Phonon::State oldstate)
345 {
346     P_Q(MediaObject);
347 
348     // AbstractMediaStream fallback stuff --------------------------------------
349     if (errorOverride) {
350         errorOverride = false;
351         if (newstate == ErrorState) {
352             return;
353         }
354         oldstate = ErrorState;
355     }
356 
357     if (mediaSource.type() != MediaSource::Url) {
358         // special handling only necessary for URLs because of the fallback
359         emit q->stateChanged(newstate, oldstate);
360         return;
361     }
362 
363     // backend MediaObject reached ErrorState, try a KioMediaSource
364     if (newstate == Phonon::ErrorState && !abstractStream) {
365         abstractStream = Platform::createMediaStream(mediaSource.url(), q);
366         if (!abstractStream) {
367             pDebug() << "backend MediaObject reached ErrorState, no KIO fallback available";
368             emit q->stateChanged(newstate, oldstate);
369             return;
370         }
371         pDebug() << "backend MediaObject reached ErrorState, trying Platform::createMediaStream now";
372         ignoreLoadingToBufferingStateChange = false;
373         ignoreErrorToLoadingStateChange = false;
374         switch (oldstate) {
375         case Phonon::BufferingState:
376             // play() has already been called, we need to make sure it is called
377             // on the backend with the KioMediaStream MediaSource now, too
378             ignoreLoadingToBufferingStateChange = true;
379             break;
380         case Phonon::LoadingState:
381             ignoreErrorToLoadingStateChange = true;
382             // no extras
383             break;
384         default:
385             pError() << "backend MediaObject reached ErrorState after " << oldstate
386                 << ". It seems a KioMediaStream will not help here, trying anyway.";
387             emit q->stateChanged(Phonon::LoadingState, oldstate);
388             break;
389         }
390         abstractStream->d_func()->setMediaObjectPrivate(this);
391         MediaSource mediaSource(abstractStream);
392         mediaSource.setAutoDelete(true);
393         pINTERFACE_CALL(setSource(mediaSource));
394         if (oldstate == Phonon::BufferingState) {
395             q->play();
396         }
397         return;
398     } else if (ignoreLoadingToBufferingStateChange &&
399             abstractStream &&
400             oldstate == Phonon::LoadingState) {
401         if (newstate != Phonon::BufferingState) {
402             emit q->stateChanged(newstate, Phonon::BufferingState);
403         }
404         return;
405     } else if (ignoreErrorToLoadingStateChange && abstractStream && oldstate == ErrorState) {
406         if (newstate != LoadingState) {
407             emit q->stateChanged(newstate, Phonon::LoadingState);
408         }
409         return;
410     }
411 
412     emit q->stateChanged(newstate, oldstate);
413 }
414 
_k_aboutToFinish()415 void MediaObjectPrivate::_k_aboutToFinish()
416 {
417     P_Q(MediaObject);
418     pDebug() << Q_FUNC_INFO;
419 
420 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
421     abstractStream = 0; // abstractStream auto-deletes
422 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM
423 
424     if (sourceQueue.isEmpty()) {
425         emit q->aboutToFinish();
426         if (sourceQueue.isEmpty()) {
427             return;
428         }
429     }
430 
431     mediaSource = sourceQueue.head();
432     playingQueuedSource = true;
433     pINTERFACE_CALL(setNextSource(mediaSource));
434 
435     if (validator)
436         validator->sourceQueued();
437 }
438 
_k_currentSourceChanged(const MediaSource & source)439 void MediaObjectPrivate::_k_currentSourceChanged(const MediaSource &source)
440 {
441     P_Q(MediaObject);
442     pDebug() << Q_FUNC_INFO;
443 
444     if (!sourceQueue.isEmpty() && sourceQueue.head() == source)
445         sourceQueue.dequeue();
446 
447     emit q->currentSourceChanged(source);
448 }
449 
setupBackendObject()450 void MediaObjectPrivate::setupBackendObject()
451 {
452     P_Q(MediaObject);
453     Q_ASSERT(m_backendObject);
454 
455     // Queue *everything* there is. That way the backend always is in a defined state.
456     // If the signals were not queued, and the backend emitted something mid-execution
457     // of whatever it is doing, an API consumer works with an undefined state.
458     // This causes major headaches. If we must enforce implicit execution stop via
459     // signals, they ought to be done in private slots.
460 
461     qRegisterMetaType<MediaSource>("MediaSource");
462     qRegisterMetaType<QMultiMap<QString, QString> >("QMultiMap<QString, QString>");
463 
464     if (validateStates)
465         validator = new StatesValidator(q); // Parented, and non-invasive to MO.
466 
467 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
468     QObject::connect(m_backendObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)),
469                      q, SLOT(_k_stateChanged(Phonon::State,Phonon::State)), Qt::QueuedConnection);
470 #else
471     QObject::connect(m_backendObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)),
472                      q, SIGNAL(stateChanged(Phonon::State,Phonon::State)), Qt::QueuedConnection);
473 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM
474 #ifndef QT_NO_PHONON_VIDEO
475     QObject::connect(m_backendObject, SIGNAL(hasVideoChanged(bool)),
476                      q, SIGNAL(hasVideoChanged(bool)), Qt::QueuedConnection);
477 #endif //QT_NO_PHONON_VIDEO
478 
479     QObject::connect(m_backendObject, SIGNAL(tick(qint64)),
480                      q, SIGNAL(tick(qint64)), Qt::QueuedConnection);
481     QObject::connect(m_backendObject, SIGNAL(seekableChanged(bool)),
482                      q, SIGNAL(seekableChanged(bool)), Qt::QueuedConnection);
483     QObject::connect(m_backendObject, SIGNAL(bufferStatus(int)),
484                      q, SIGNAL(bufferStatus(int)), Qt::QueuedConnection);
485     QObject::connect(m_backendObject, SIGNAL(finished()),
486                      q, SIGNAL(finished()), Qt::QueuedConnection);
487     QObject::connect(m_backendObject, SIGNAL(aboutToFinish()),
488                      q, SLOT(_k_aboutToFinish()), Qt::QueuedConnection);
489     QObject::connect(m_backendObject, SIGNAL(prefinishMarkReached(qint32)),
490                      q, SIGNAL(prefinishMarkReached(qint32)), Qt::QueuedConnection);
491     QObject::connect(m_backendObject, SIGNAL(totalTimeChanged(qint64)),
492                      q, SIGNAL(totalTimeChanged(qint64)), Qt::QueuedConnection);
493     QObject::connect(m_backendObject, SIGNAL(metaDataChanged(QMultiMap<QString,QString>)),
494                      q, SLOT(_k_metaDataChanged(QMultiMap<QString,QString>)), Qt::QueuedConnection);
495     QObject::connect(m_backendObject, SIGNAL(currentSourceChanged(MediaSource)),
496                      q, SLOT(_k_currentSourceChanged(MediaSource)), Qt::QueuedConnection);
497 
498     // set up attributes
499     pINTERFACE_CALL(setTickInterval(tickInterval));
500     pINTERFACE_CALL(setPrefinishMark(prefinishMark));
501     pINTERFACE_CALL(setTransitionTime(transitionTime));
502 
503     switch(state)
504     {
505     case LoadingState:
506     case StoppedState:
507     case ErrorState:
508         break;
509     case PlayingState:
510     case BufferingState:
511         QTimer::singleShot(0, q, SLOT(_k_resumePlay()));
512         break;
513     case PausedState:
514         QTimer::singleShot(0, q, SLOT(_k_resumePause()));
515         break;
516     }
517     const State backendState = pINTERFACE_CALL(state());
518     if (state != backendState && state != ErrorState) {
519         // careful: if state is ErrorState we might be switching from a
520         // MediaObject to a ByteStream for KIO fallback. In that case the state
521         // change to ErrorState was already suppressed.
522         pDebug() << "emitting a state change because the backend object has been replaced";
523         emit q->stateChanged(backendState, state);
524         state = backendState;
525     }
526 
527 #ifndef QT_NO_PHONON_MEDIACONTROLLER
528     for (int i = 0 ; i < interfaceList.count(); ++i) {
529         interfaceList.at(i)->_backendObjectChanged();
530     }
531 #endif //QT_NO_PHONON_MEDIACONTROLLER
532 
533     // set up attributes
534     if (isPlayable(mediaSource.type())) {
535 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
536         if (mediaSource.type() == MediaSource::Stream) {
537             Q_ASSERT(mediaSource.stream());
538             mediaSource.stream()->d_func()->setMediaObjectPrivate(this);
539         }
540 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM
541         pINTERFACE_CALL(setSource(mediaSource));
542     }
543 }
544 
_k_resumePlay()545 void MediaObjectPrivate::_k_resumePlay()
546 {
547     qobject_cast<MediaObjectInterface *>(m_backendObject)->play();
548     if (currentTime > 0) {
549         qobject_cast<MediaObjectInterface *>(m_backendObject)->seek(currentTime);
550     }
551 }
552 
_k_resumePause()553 void MediaObjectPrivate::_k_resumePause()
554 {
555     pINTERFACE_CALL(pause());
556     if (currentTime > 0) {
557         qobject_cast<MediaObjectInterface *>(m_backendObject)->seek(currentTime);
558     }
559 }
560 
_k_metaDataChanged(const QMultiMap<QString,QString> & newMetaData)561 void MediaObjectPrivate::_k_metaDataChanged(const QMultiMap<QString, QString> &newMetaData)
562 {
563     metaData = newMetaData;
564     emit q_func()->metaDataChanged();
565 }
566 
phononObjectDestroyed(MediaNodePrivate * bp)567 void MediaObjectPrivate::phononObjectDestroyed(MediaNodePrivate *bp)
568 {
569     // this method is called from Phonon::Base::~Base(), meaning the AudioPath
570     // dtor has already been called, also virtual functions don't work anymore
571     // (therefore qobject_cast can only downcast from Base)
572     Q_ASSERT(bp);
573     Q_UNUSED(bp);
574 }
575 
createPlayer(Phonon::Category category,const MediaSource & source)576 MediaObject *createPlayer(Phonon::Category category, const MediaSource &source)
577 {
578     MediaObject *mo = new MediaObject;
579     AudioOutput *ao = new AudioOutput(category, mo);
580     createPath(mo, ao);
581     if (isPlayable(source.type())) {
582         mo->setCurrentSource(source);
583     }
584     return mo;
585 }
586 
587 } //namespace Phonon
588 
589 #include "moc_mediaobject.cpp"
590 
591 #undef PHONON_CLASSNAME
592 #undef PHONON_INTERFACENAME
593 // vim: sw=4 tw=100 et
594