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 <QStringBuilder>
35#include <QStringList>
36#include <QDateTime>
37#include <QTimer>
38#include <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
47namespace Phonon
48{
49PHONON_OBJECT_IMPL
50
51MediaObject::~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
69Phonon::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
89PHONON_INTERFACE_SETTER(setTickInterval, tickInterval, qint32)
90PHONON_INTERFACE_GETTER(qint32, tickInterval, d->tickInterval)
91PHONON_INTERFACE_GETTER(bool, hasVideo, false)
92PHONON_INTERFACE_GETTER(bool, isSeekable, false)
93PHONON_INTERFACE_GETTER(qint64, currentTime, d->currentTime)
94
95static inline bool isPlayable(const MediaSource::Type t)
96{
97 return t != MediaSource::Invalid && t != MediaSource::Empty;
98}
99
100void MediaObject::play()
101{
102 P_D(MediaObject);
103 if (d->backendObject() && isPlayable(t: d->mediaSource.type())) {
104 INTERFACE_CALL(play());
105 }
106}
107
108void MediaObject::pause()
109{
110 P_D(MediaObject);
111 if (d->backendObject() && isPlayable(t: d->mediaSource.type())) {
112 INTERFACE_CALL(pause());
113 }
114}
115
116void MediaObject::stop()
117{
118 P_D(MediaObject);
119 if (d->backendObject() && isPlayable(t: d->mediaSource.type())) {
120 INTERFACE_CALL(stop());
121 }
122}
123
124void MediaObject::seek(qint64 time)
125{
126 P_D(MediaObject);
127 if (d->backendObject() && isPlayable(t: d->mediaSource.type())) {
128 INTERFACE_CALL(seek(time));
129 }
130}
131
132QString 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
146ErrorType 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
160QStringList MediaObject::metaData(Phonon::MetaData f) const
161{
162 switch (f) {
163 case ArtistMetaData:
164 return metaData(key: QLatin1String("ARTIST"));
165 case AlbumMetaData:
166 return metaData(key: QLatin1String("ALBUM"));
167 case TitleMetaData:
168 return metaData(key: QLatin1String("TITLE"));
169 case DateMetaData:
170 return metaData(key: QLatin1String("DATE"));
171 case GenreMetaData:
172 return metaData(key: QLatin1String("GENRE"));
173 case TracknumberMetaData:
174 return metaData(key: QLatin1String("TRACKNUMBER"));
175 case DescriptionMetaData:
176 return metaData(key: QLatin1String("DESCRIPTION"));
177 case MusicBrainzDiscIdMetaData:
178 return metaData(key: QLatin1String("MUSICBRAINZ_DISCID"));
179 }
180 return QStringList();
181}
182
183QStringList MediaObject::metaData(const QString &key) const
184{
185 P_D(const MediaObject);
186 return d->metaData.values(key);
187}
188
189QMultiMap<QString, QString> MediaObject::metaData() const
190{
191 P_D(const MediaObject);
192 return d->metaData;
193}
194
195PHONON_INTERFACE_GETTER(qint32, prefinishMark, d->prefinishMark)
196PHONON_INTERFACE_SETTER(setPrefinishMark, prefinishMark, qint32)
197
198PHONON_INTERFACE_GETTER(qint32, transitionTime, d->transitionTime)
199PHONON_INTERFACE_SETTER(setTransitionTime, transitionTime, qint32)
200
201qint64 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
210qint64 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
223MediaSource MediaObject::currentSource() const
224{
225 P_D(const MediaObject);
226 return d->mediaSource;
227}
228
229void 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 = nullptr; // 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
257void MediaObject::clear()
258{
259 P_D(MediaObject);
260 d->sourceQueue.clear();
261 setCurrentSource(MediaSource());
262}
263
264QList<MediaSource> MediaObject::queue() const
265{
266 P_D(const MediaObject);
267 return d->sourceQueue;
268}
269
270void MediaObject::setQueue(const QList<MediaSource> &sources)
271{
272 P_D(MediaObject);
273 d->sourceQueue.clear();
274 enqueue(sources);
275}
276
277void MediaObject::setQueue(const QList<QUrl> &urls)
278{
279 P_D(MediaObject);
280 d->sourceQueue.clear();
281 enqueue(urls);
282}
283
284void MediaObject::enqueue(const MediaSource &source)
285{
286 P_D(MediaObject);
287 if (!isPlayable(t: 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
295void MediaObject::enqueue(const QList<MediaSource> &sources)
296{
297 for (int i = 0; i < sources.count(); ++i) {
298 enqueue(source: sources.at(i));
299 }
300}
301
302void MediaObject::enqueue(const QList<QUrl> &urls)
303{
304 for (int i = 0; i < urls.count(); ++i) {
305 enqueue(source: urls.at(i));
306 }
307}
308
309void MediaObject::clearQueue()
310{
311 P_D(MediaObject);
312 d->sourceQueue.clear();
313}
314
315bool 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
330void 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(obj: q, member: "stateChanged", c: 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...
344void 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(url: mediaSource.url(), parent: 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(newstate: 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, oldstate: Phonon::BufferingState);
403 }
404 return;
405 } else if (ignoreErrorToLoadingStateChange && abstractStream && oldstate == ErrorState) {
406 if (newstate != LoadingState) {
407 emit q->stateChanged(newstate, oldstate: Phonon::LoadingState);
408 }
409 return;
410 }
411
412 emit q->stateChanged(newstate, oldstate);
413}
414
415void MediaObjectPrivate::_k_aboutToFinish()
416{
417 P_Q(MediaObject);
418 pDebug() << Q_FUNC_INFO;
419
420#ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM
421 abstractStream = nullptr; // 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
439void 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(newSource: source);
448}
449
450void 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>(typeName: "MediaSource");
462 qRegisterMetaType<QMultiMap<QString, QString> >(typeName: "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(sender: m_backendObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)),
469 receiver: 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(sender: m_backendObject, SIGNAL(hasVideoChanged(bool)),
476 receiver: q, SIGNAL(hasVideoChanged(bool)), Qt::QueuedConnection);
477#endif //QT_NO_PHONON_VIDEO
478
479 QObject::connect(sender: m_backendObject, SIGNAL(tick(qint64)),
480 receiver: q, SIGNAL(tick(qint64)), Qt::QueuedConnection);
481 QObject::connect(sender: m_backendObject, SIGNAL(seekableChanged(bool)),
482 receiver: q, SIGNAL(seekableChanged(bool)), Qt::QueuedConnection);
483 QObject::connect(sender: m_backendObject, SIGNAL(bufferStatus(int)),
484 receiver: q, SIGNAL(bufferStatus(int)), Qt::QueuedConnection);
485 QObject::connect(sender: m_backendObject, SIGNAL(finished()),
486 receiver: q, SIGNAL(finished()), Qt::QueuedConnection);
487 QObject::connect(sender: m_backendObject, SIGNAL(aboutToFinish()),
488 receiver: q, SLOT(_k_aboutToFinish()), Qt::QueuedConnection);
489 QObject::connect(sender: m_backendObject, SIGNAL(prefinishMarkReached(qint32)),
490 receiver: q, SIGNAL(prefinishMarkReached(qint32)), Qt::QueuedConnection);
491 QObject::connect(sender: m_backendObject, SIGNAL(totalTimeChanged(qint64)),
492 receiver: q, SIGNAL(totalTimeChanged(qint64)), Qt::QueuedConnection);
493 QObject::connect(sender: m_backendObject, SIGNAL(metaDataChanged(QMultiMap<QString,QString>)),
494 receiver: q, SLOT(_k_metaDataChanged(QMultiMap<QString,QString>)), Qt::QueuedConnection);
495 QObject::connect(sender: m_backendObject, SIGNAL(currentSourceChanged(MediaSource)),
496 receiver: 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(msec: 0, receiver: q, SLOT(_k_resumePlay()));
512 break;
513 case PausedState:
514 QTimer::singleShot(msec: 0, receiver: 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(newstate: backendState, oldstate: 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(t: 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
545void MediaObjectPrivate::_k_resumePlay()
546{
547 qobject_cast<MediaObjectInterface *>(object: m_backendObject)->play();
548 if (currentTime > 0) {
549 qobject_cast<MediaObjectInterface *>(object: m_backendObject)->seek(milliseconds: currentTime);
550 }
551}
552
553void MediaObjectPrivate::_k_resumePause()
554{
555 pINTERFACE_CALL(pause());
556 if (currentTime > 0) {
557 qobject_cast<MediaObjectInterface *>(object: m_backendObject)->seek(milliseconds: currentTime);
558 }
559}
560
561void MediaObjectPrivate::_k_metaDataChanged(const QMultiMap<QString, QString> &newMetaData)
562{
563 metaData = newMetaData;
564 emit q_func()->metaDataChanged();
565}
566
567void 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
576MediaObject *createPlayer(Phonon::Category category, const MediaSource &source)
577{
578 MediaObject *mo = new MediaObject;
579 AudioOutput *ao = new AudioOutput(category, mo);
580 createPath(source: mo, sink: ao);
581 if (isPlayable(t: 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

source code of phonon/phonon/mediaobject.cpp