Phonon

mediaobject.cpp
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
52{
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
101{
103 if (d->backendObject() && isPlayable(d->mediaSource.type())) {
105 }
106}
107
109{
111 if (d->backendObject() && isPlayable(d->mediaSource.type())) {
113 }
114}
115
117{
119 if (d->backendObject() && isPlayable(d->mediaSource.type())) {
121 }
122}
123
124void MediaObject::seek(qint64 time)
125{
127 if (d->backendObject() && isPlayable(d->mediaSource.type())) {
128 INTERFACE_CALL(seek(time));
129 }
130}
131
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(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"));
174 return metaData(QLatin1String("TRACKNUMBER"));
176 return metaData(QLatin1String("DESCRIPTION"));
178 return metaData(QLatin1String("MUSICBRAINZ_DISCID"));
179 }
180 return QStringList();
181}
182
184{
185 P_D(const MediaObject);
186 return d->metaData.values(key);
187}
188
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
211{
212 P_D(const MediaObject);
213 if (!d->m_backendObject) {
214 return -1;
215 }
217 if (ret < 0) {
218 return -1;
219 }
220 return ret;
221}
222
224{
225 P_D(const MediaObject);
226 return d->mediaSource;
227}
228
230{
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
258{
260 d->sourceQueue.clear();
262}
263
265{
266 P_D(const MediaObject);
267 return d->sourceQueue;
268}
269
271{
273 d->sourceQueue.clear();
274 enqueue(sources);
275}
276
278{
280 d->sourceQueue.clear();
281 enqueue(urls);
282}
283
285{
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
296{
297 for (int i = 0; i < sources.count(); ++i) {
298 enqueue(sources.at(i));
299 }
300}
301
303{
304 for (int i = 0; i < urls.count(); ++i) {
305 enqueue(urls.at(i));
306 }
307}
308
310{
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(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...
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(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
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(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>("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
545void 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
553void MediaObjectPrivate::_k_resumePause()
554{
555 pINTERFACE_CALL(pause());
556 if (currentTime > 0) {
557 qobject_cast<MediaObjectInterface *>(m_backendObject)->seek(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(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
Interface for media playback of a given URL.
Definition mediaobject.h:94
void setCurrentSource(const MediaSource &source)
Set the media source the MediaObject should use.
void clearQueue()
Clears the queue of sources.
void setQueue(const QList< MediaSource > &sources)
Set the MediaSources to play when the current media has finished.
void stop()
Requests playback to stop.
State state() const
Get the current state.
void pause()
Requests playback to pause.
QList< MediaSource > queue() const
Returns the queued media sources.
QMultiMap< QString, QString > metaData() const
Returns all meta data.
ErrorType errorType() const
Tells your program what to do about the error.
void play()
Requests playback of the media data to start.
void seek(qint64 time)
Requests a seek to the time indicated.
void enqueue(const MediaSource &source)
Appends one source to the queue.
QString errorString() const
Returns a human-readable description of the last error that occurred.
~MediaObject() override
Destroys the MediaObject.
qint64 remainingTime() const
Get the remaining time (in milliseconds) of the file currently being played.
void clear()
Stops and removes all playing and enqueued media sources.
MediaSource currentSource() const
Returns the current media source.
Note that all constructors of this class are implicit, so that you can simply write.
Definition mediasource.h:63
@ Url
The MediaSource object describes a URL, which can be both a local file and a file on the network.
Definition mediasource.h:85
@ Stream
The MediaSource object describes a data stream.
Definition mediasource.h:97
@ Invalid
The MediaSource object does not describe any valid source.
Definition mediasource.h:76
@ Empty
An empty MediaSource.
Q_SCRIPTABLE Q_NOREPLY void pause()
Type type(const QSqlDatabase &db)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QueuedConnection
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:24 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.