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