File indexing completed on 2025-02-09 05:31:49
0001 /* This file is part of the KDE project 0002 Copyright (C) 2005-2007 Matthias Kretz <kretz@kde.org> 0003 Copyright (C) 2011 Trever Fischer <tdfischer@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Lesser General Public 0007 License as published by the Free Software Foundation; either 0008 version 2.1 of the License, or (at your option) version 3, or any 0009 later version accepted by the membership of KDE e.V. (or its 0010 successor approved by the membership of KDE e.V.), Nokia Corporation 0011 (or its successors, if any) and the KDE Free Qt Foundation, which shall 0012 act as a proxy defined in Section 6 of version 3 of the license. 0013 0014 This library is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 Lesser General Public License for more details. 0018 0019 You should have received a copy of the GNU Lesser General Public 0020 License along with this library. If not, see <http://www.gnu.org/licenses/>. 0021 0022 */ 0023 #include "mediaobject.h" 0024 #include "mediaobject_p.h" 0025 0026 #include "factory_p.h" 0027 #include "mediaobjectinterface.h" 0028 #include "audiooutput.h" 0029 #include "phonondefs_p.h" 0030 #include "abstractmediastream.h" 0031 #include "abstractmediastream_p.h" 0032 #include "frontendinterface_p.h" 0033 0034 #include <QStringBuilder> 0035 #include <QStringList> 0036 #include <QDateTime> 0037 #include <QTimer> 0038 #include <QUrl> 0039 0040 #include "phononnamespace_p.h" 0041 #include "platform_p.h" 0042 #include "statesvalidator_p.h" 0043 0044 #define PHONON_CLASSNAME MediaObject 0045 #define PHONON_INTERFACENAME MediaObjectInterface 0046 0047 namespace Phonon 0048 { 0049 PHONON_OBJECT_IMPL 0050 0051 MediaObject::~MediaObject() 0052 { 0053 P_D(MediaObject); 0054 if (d->m_backendObject) { 0055 switch (state()) { 0056 case PlayingState: 0057 case BufferingState: 0058 case PausedState: 0059 stop(); 0060 break; 0061 case ErrorState: 0062 case StoppedState: 0063 case LoadingState: 0064 break; 0065 } 0066 } 0067 } 0068 0069 Phonon::State MediaObject::state() const 0070 { 0071 P_D(const MediaObject); 0072 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0073 if (d->errorOverride) { 0074 return d->state; 0075 } 0076 if (d->ignoreLoadingToBufferingStateChange) { 0077 return BufferingState; 0078 } 0079 if (d->ignoreErrorToLoadingStateChange) { 0080 return LoadingState; 0081 } 0082 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM 0083 if (!d->m_backendObject) { 0084 return d->state; 0085 } 0086 return INTERFACE_CALL(state()); 0087 } 0088 0089 PHONON_INTERFACE_SETTER(setTickInterval, tickInterval, qint32) 0090 PHONON_INTERFACE_GETTER(qint32, tickInterval, d->tickInterval) 0091 PHONON_INTERFACE_GETTER(bool, hasVideo, false) 0092 PHONON_INTERFACE_GETTER(bool, isSeekable, false) 0093 PHONON_INTERFACE_GETTER(qint64, currentTime, d->currentTime) 0094 0095 static inline bool isPlayable(const MediaSource::Type t) 0096 { 0097 return t != MediaSource::Invalid && t != MediaSource::Empty; 0098 } 0099 0100 void MediaObject::play() 0101 { 0102 P_D(MediaObject); 0103 if (d->backendObject() && isPlayable(d->mediaSource.type())) { 0104 INTERFACE_CALL(play()); 0105 } 0106 } 0107 0108 void MediaObject::pause() 0109 { 0110 P_D(MediaObject); 0111 if (d->backendObject() && isPlayable(d->mediaSource.type())) { 0112 INTERFACE_CALL(pause()); 0113 } 0114 } 0115 0116 void MediaObject::stop() 0117 { 0118 P_D(MediaObject); 0119 if (d->backendObject() && isPlayable(d->mediaSource.type())) { 0120 INTERFACE_CALL(stop()); 0121 } 0122 } 0123 0124 void MediaObject::seek(qint64 time) 0125 { 0126 P_D(MediaObject); 0127 if (d->backendObject() && isPlayable(d->mediaSource.type())) { 0128 INTERFACE_CALL(seek(time)); 0129 } 0130 } 0131 0132 QString MediaObject::errorString() const 0133 { 0134 if (state() == Phonon::ErrorState) { 0135 P_D(const MediaObject); 0136 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0137 if (d->errorOverride) { 0138 return d->errorString; 0139 } 0140 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM 0141 return INTERFACE_CALL(errorString()); 0142 } 0143 return QString(); 0144 } 0145 0146 ErrorType MediaObject::errorType() const 0147 { 0148 if (state() == Phonon::ErrorState) { 0149 P_D(const MediaObject); 0150 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0151 if (d->errorOverride) { 0152 return d->errorType; 0153 } 0154 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM 0155 return INTERFACE_CALL(errorType()); 0156 } 0157 return Phonon::NoError; 0158 } 0159 0160 QStringList MediaObject::metaData(Phonon::MetaData f) const 0161 { 0162 switch (f) { 0163 case ArtistMetaData: 0164 return metaData(QLatin1String("ARTIST")); 0165 case AlbumMetaData: 0166 return metaData(QLatin1String("ALBUM")); 0167 case TitleMetaData: 0168 return metaData(QLatin1String("TITLE")); 0169 case DateMetaData: 0170 return metaData(QLatin1String("DATE")); 0171 case GenreMetaData: 0172 return metaData(QLatin1String("GENRE")); 0173 case TracknumberMetaData: 0174 return metaData(QLatin1String("TRACKNUMBER")); 0175 case DescriptionMetaData: 0176 return metaData(QLatin1String("DESCRIPTION")); 0177 case MusicBrainzDiscIdMetaData: 0178 return metaData(QLatin1String("MUSICBRAINZ_DISCID")); 0179 } 0180 return QStringList(); 0181 } 0182 0183 QStringList MediaObject::metaData(const QString &key) const 0184 { 0185 P_D(const MediaObject); 0186 return d->metaData.values(key); 0187 } 0188 0189 QMultiMap<QString, QString> MediaObject::metaData() const 0190 { 0191 P_D(const MediaObject); 0192 return d->metaData; 0193 } 0194 0195 PHONON_INTERFACE_GETTER(qint32, prefinishMark, d->prefinishMark) 0196 PHONON_INTERFACE_SETTER(setPrefinishMark, prefinishMark, qint32) 0197 0198 PHONON_INTERFACE_GETTER(qint32, transitionTime, d->transitionTime) 0199 PHONON_INTERFACE_SETTER(setTransitionTime, transitionTime, qint32) 0200 0201 qint64 MediaObject::totalTime() const 0202 { 0203 P_D(const MediaObject); 0204 if (!d->m_backendObject) { 0205 return -1; 0206 } 0207 return INTERFACE_CALL(totalTime()); 0208 } 0209 0210 qint64 MediaObject::remainingTime() const 0211 { 0212 P_D(const MediaObject); 0213 if (!d->m_backendObject) { 0214 return -1; 0215 } 0216 qint64 ret = INTERFACE_CALL(remainingTime()); 0217 if (ret < 0) { 0218 return -1; 0219 } 0220 return ret; 0221 } 0222 0223 MediaSource MediaObject::currentSource() const 0224 { 0225 P_D(const MediaObject); 0226 return d->mediaSource; 0227 } 0228 0229 void MediaObject::setCurrentSource(const MediaSource &newSource) 0230 { 0231 P_D(MediaObject); 0232 if (!k_ptr->backendObject()) { 0233 d->mediaSource = newSource; 0234 return; 0235 } 0236 0237 pDebug() << Q_FUNC_INFO << newSource.type() << newSource.url() << newSource.deviceName(); 0238 0239 stop(); // first call stop as that often is the expected state 0240 // for setting a new URL 0241 0242 d->mediaSource = newSource; 0243 0244 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0245 d->abstractStream = nullptr; // abstractStream auto-deletes 0246 if (d->mediaSource.type() == MediaSource::Stream) { 0247 Q_ASSERT(d->mediaSource.stream()); 0248 d->mediaSource.stream()->d_func()->setMediaObjectPrivate(d); 0249 } 0250 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM 0251 0252 d->playingQueuedSource = false; 0253 0254 INTERFACE_CALL(setSource(d->mediaSource)); 0255 } 0256 0257 void MediaObject::clear() 0258 { 0259 P_D(MediaObject); 0260 d->sourceQueue.clear(); 0261 setCurrentSource(MediaSource()); 0262 } 0263 0264 QList<MediaSource> MediaObject::queue() const 0265 { 0266 P_D(const MediaObject); 0267 return d->sourceQueue; 0268 } 0269 0270 void MediaObject::setQueue(const QList<MediaSource> &sources) 0271 { 0272 P_D(MediaObject); 0273 d->sourceQueue.clear(); 0274 enqueue(sources); 0275 } 0276 0277 void MediaObject::setQueue(const QList<QUrl> &urls) 0278 { 0279 P_D(MediaObject); 0280 d->sourceQueue.clear(); 0281 enqueue(urls); 0282 } 0283 0284 void MediaObject::enqueue(const MediaSource &source) 0285 { 0286 P_D(MediaObject); 0287 if (!isPlayable(d->mediaSource.type())) { 0288 // the current source is nothing valid so this source needs to become the current one 0289 setCurrentSource(source); 0290 } else { 0291 d->sourceQueue << source; 0292 } 0293 } 0294 0295 void MediaObject::enqueue(const QList<MediaSource> &sources) 0296 { 0297 for (int i = 0; i < sources.count(); ++i) { 0298 enqueue(sources.at(i)); 0299 } 0300 } 0301 0302 void MediaObject::enqueue(const QList<QUrl> &urls) 0303 { 0304 for (int i = 0; i < urls.count(); ++i) { 0305 enqueue(urls.at(i)); 0306 } 0307 } 0308 0309 void MediaObject::clearQueue() 0310 { 0311 P_D(MediaObject); 0312 d->sourceQueue.clear(); 0313 } 0314 0315 bool MediaObjectPrivate::aboutToDeleteBackendObject() 0316 { 0317 //pDebug() << Q_FUNC_INFO; 0318 prefinishMark = pINTERFACE_CALL(prefinishMark()); 0319 transitionTime = pINTERFACE_CALL(transitionTime()); 0320 //pDebug() << Q_FUNC_INFO; 0321 if (m_backendObject) { 0322 state = pINTERFACE_CALL(state()); 0323 currentTime = pINTERFACE_CALL(currentTime()); 0324 tickInterval = pINTERFACE_CALL(tickInterval()); 0325 } 0326 return true; 0327 } 0328 0329 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0330 void MediaObjectPrivate::streamError(Phonon::ErrorType type, const QString &text) 0331 { 0332 P_Q(MediaObject); 0333 State lastState = q->state(); 0334 errorOverride = true; 0335 errorType = type; 0336 errorString = text; 0337 state = ErrorState; 0338 QMetaObject::invokeMethod(q, "stateChanged", Qt::QueuedConnection, Q_ARG(Phonon::State, Phonon::ErrorState), Q_ARG(Phonon::State, lastState)); 0339 //emit q->stateChanged(ErrorState, lastState); 0340 } 0341 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM 0342 0343 // TODO: this needs serious cleanup... 0344 void MediaObjectPrivate::_k_stateChanged(Phonon::State newstate, Phonon::State oldstate) 0345 { 0346 P_Q(MediaObject); 0347 0348 // AbstractMediaStream fallback stuff -------------------------------------- 0349 if (errorOverride) { 0350 errorOverride = false; 0351 if (newstate == ErrorState) { 0352 return; 0353 } 0354 oldstate = ErrorState; 0355 } 0356 0357 if (mediaSource.type() != MediaSource::Url) { 0358 // special handling only necessary for URLs because of the fallback 0359 emit q->stateChanged(newstate, oldstate); 0360 return; 0361 } 0362 0363 // backend MediaObject reached ErrorState, try a KioMediaSource 0364 if (newstate == Phonon::ErrorState && !abstractStream) { 0365 abstractStream = Platform::createMediaStream(mediaSource.url(), q); 0366 if (!abstractStream) { 0367 pDebug() << "backend MediaObject reached ErrorState, no KIO fallback available"; 0368 emit q->stateChanged(newstate, oldstate); 0369 return; 0370 } 0371 pDebug() << "backend MediaObject reached ErrorState, trying Platform::createMediaStream now"; 0372 ignoreLoadingToBufferingStateChange = false; 0373 ignoreErrorToLoadingStateChange = false; 0374 switch (oldstate) { 0375 case Phonon::BufferingState: 0376 // play() has already been called, we need to make sure it is called 0377 // on the backend with the KioMediaStream MediaSource now, too 0378 ignoreLoadingToBufferingStateChange = true; 0379 break; 0380 case Phonon::LoadingState: 0381 ignoreErrorToLoadingStateChange = true; 0382 // no extras 0383 break; 0384 default: 0385 pError() << "backend MediaObject reached ErrorState after " << oldstate 0386 << ". It seems a KioMediaStream will not help here, trying anyway."; 0387 emit q->stateChanged(Phonon::LoadingState, oldstate); 0388 break; 0389 } 0390 abstractStream->d_func()->setMediaObjectPrivate(this); 0391 MediaSource mediaSource(abstractStream); 0392 mediaSource.setAutoDelete(true); 0393 pINTERFACE_CALL(setSource(mediaSource)); 0394 if (oldstate == Phonon::BufferingState) { 0395 q->play(); 0396 } 0397 return; 0398 } else if (ignoreLoadingToBufferingStateChange && 0399 abstractStream && 0400 oldstate == Phonon::LoadingState) { 0401 if (newstate != Phonon::BufferingState) { 0402 emit q->stateChanged(newstate, Phonon::BufferingState); 0403 } 0404 return; 0405 } else if (ignoreErrorToLoadingStateChange && abstractStream && oldstate == ErrorState) { 0406 if (newstate != LoadingState) { 0407 emit q->stateChanged(newstate, Phonon::LoadingState); 0408 } 0409 return; 0410 } 0411 0412 emit q->stateChanged(newstate, oldstate); 0413 } 0414 0415 void MediaObjectPrivate::_k_aboutToFinish() 0416 { 0417 P_Q(MediaObject); 0418 pDebug() << Q_FUNC_INFO; 0419 0420 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0421 abstractStream = nullptr; // abstractStream auto-deletes 0422 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM 0423 0424 if (sourceQueue.isEmpty()) { 0425 emit q->aboutToFinish(); 0426 if (sourceQueue.isEmpty()) { 0427 return; 0428 } 0429 } 0430 0431 mediaSource = sourceQueue.head(); 0432 playingQueuedSource = true; 0433 pINTERFACE_CALL(setNextSource(mediaSource)); 0434 0435 if (validator) 0436 validator->sourceQueued(); 0437 } 0438 0439 void MediaObjectPrivate::_k_currentSourceChanged(const MediaSource &source) 0440 { 0441 P_Q(MediaObject); 0442 pDebug() << Q_FUNC_INFO; 0443 0444 if (!sourceQueue.isEmpty() && sourceQueue.head() == source) 0445 sourceQueue.dequeue(); 0446 0447 emit q->currentSourceChanged(source); 0448 } 0449 0450 void MediaObjectPrivate::setupBackendObject() 0451 { 0452 P_Q(MediaObject); 0453 Q_ASSERT(m_backendObject); 0454 0455 // Queue *everything* there is. That way the backend always is in a defined state. 0456 // If the signals were not queued, and the backend emitted something mid-execution 0457 // of whatever it is doing, an API consumer works with an undefined state. 0458 // This causes major headaches. If we must enforce implicit execution stop via 0459 // signals, they ought to be done in private slots. 0460 0461 qRegisterMetaType<MediaSource>("MediaSource"); 0462 qRegisterMetaType<QMultiMap<QString, QString> >("QMultiMap<QString, QString>"); 0463 0464 if (validateStates) 0465 validator = new StatesValidator(q); // Parented, and non-invasive to MO. 0466 0467 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0468 QObject::connect(m_backendObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), 0469 q, SLOT(_k_stateChanged(Phonon::State,Phonon::State)), Qt::QueuedConnection); 0470 #else 0471 QObject::connect(m_backendObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), 0472 q, SIGNAL(stateChanged(Phonon::State,Phonon::State)), Qt::QueuedConnection); 0473 #endif // QT_NO_PHONON_ABSTRACTMEDIASTREAM 0474 #ifndef QT_NO_PHONON_VIDEO 0475 QObject::connect(m_backendObject, SIGNAL(hasVideoChanged(bool)), 0476 q, SIGNAL(hasVideoChanged(bool)), Qt::QueuedConnection); 0477 #endif //QT_NO_PHONON_VIDEO 0478 0479 QObject::connect(m_backendObject, SIGNAL(tick(qint64)), 0480 q, SIGNAL(tick(qint64)), Qt::QueuedConnection); 0481 QObject::connect(m_backendObject, SIGNAL(seekableChanged(bool)), 0482 q, SIGNAL(seekableChanged(bool)), Qt::QueuedConnection); 0483 QObject::connect(m_backendObject, SIGNAL(bufferStatus(int)), 0484 q, SIGNAL(bufferStatus(int)), Qt::QueuedConnection); 0485 QObject::connect(m_backendObject, SIGNAL(finished()), 0486 q, SIGNAL(finished()), Qt::QueuedConnection); 0487 QObject::connect(m_backendObject, SIGNAL(aboutToFinish()), 0488 q, SLOT(_k_aboutToFinish()), Qt::QueuedConnection); 0489 QObject::connect(m_backendObject, SIGNAL(prefinishMarkReached(qint32)), 0490 q, SIGNAL(prefinishMarkReached(qint32)), Qt::QueuedConnection); 0491 QObject::connect(m_backendObject, SIGNAL(totalTimeChanged(qint64)), 0492 q, SIGNAL(totalTimeChanged(qint64)), Qt::QueuedConnection); 0493 QObject::connect(m_backendObject, SIGNAL(metaDataChanged(QMultiMap<QString,QString>)), 0494 q, SLOT(_k_metaDataChanged(QMultiMap<QString,QString>)), Qt::QueuedConnection); 0495 QObject::connect(m_backendObject, SIGNAL(currentSourceChanged(MediaSource)), 0496 q, SLOT(_k_currentSourceChanged(MediaSource)), Qt::QueuedConnection); 0497 0498 // set up attributes 0499 pINTERFACE_CALL(setTickInterval(tickInterval)); 0500 pINTERFACE_CALL(setPrefinishMark(prefinishMark)); 0501 pINTERFACE_CALL(setTransitionTime(transitionTime)); 0502 0503 switch(state) 0504 { 0505 case LoadingState: 0506 case StoppedState: 0507 case ErrorState: 0508 break; 0509 case PlayingState: 0510 case BufferingState: 0511 QTimer::singleShot(0, q, SLOT(_k_resumePlay())); 0512 break; 0513 case PausedState: 0514 QTimer::singleShot(0, q, SLOT(_k_resumePause())); 0515 break; 0516 } 0517 const State backendState = pINTERFACE_CALL(state()); 0518 if (state != backendState && state != ErrorState) { 0519 // careful: if state is ErrorState we might be switching from a 0520 // MediaObject to a ByteStream for KIO fallback. In that case the state 0521 // change to ErrorState was already suppressed. 0522 pDebug() << "emitting a state change because the backend object has been replaced"; 0523 emit q->stateChanged(backendState, state); 0524 state = backendState; 0525 } 0526 0527 #ifndef QT_NO_PHONON_MEDIACONTROLLER 0528 for (int i = 0 ; i < interfaceList.count(); ++i) { 0529 interfaceList.at(i)->_backendObjectChanged(); 0530 } 0531 #endif //QT_NO_PHONON_MEDIACONTROLLER 0532 0533 // set up attributes 0534 if (isPlayable(mediaSource.type())) { 0535 #ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM 0536 if (mediaSource.type() == MediaSource::Stream) { 0537 Q_ASSERT(mediaSource.stream()); 0538 mediaSource.stream()->d_func()->setMediaObjectPrivate(this); 0539 } 0540 #endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM 0541 pINTERFACE_CALL(setSource(mediaSource)); 0542 } 0543 } 0544 0545 void MediaObjectPrivate::_k_resumePlay() 0546 { 0547 qobject_cast<MediaObjectInterface *>(m_backendObject)->play(); 0548 if (currentTime > 0) { 0549 qobject_cast<MediaObjectInterface *>(m_backendObject)->seek(currentTime); 0550 } 0551 } 0552 0553 void MediaObjectPrivate::_k_resumePause() 0554 { 0555 pINTERFACE_CALL(pause()); 0556 if (currentTime > 0) { 0557 qobject_cast<MediaObjectInterface *>(m_backendObject)->seek(currentTime); 0558 } 0559 } 0560 0561 void MediaObjectPrivate::_k_metaDataChanged(const QMultiMap<QString, QString> &newMetaData) 0562 { 0563 metaData = newMetaData; 0564 emit q_func()->metaDataChanged(); 0565 } 0566 0567 void MediaObjectPrivate::phononObjectDestroyed(MediaNodePrivate *bp) 0568 { 0569 // this method is called from Phonon::Base::~Base(), meaning the AudioPath 0570 // dtor has already been called, also virtual functions don't work anymore 0571 // (therefore qobject_cast can only downcast from Base) 0572 Q_ASSERT(bp); 0573 Q_UNUSED(bp); 0574 } 0575 0576 MediaObject *createPlayer(Phonon::Category category, const MediaSource &source) 0577 { 0578 MediaObject *mo = new MediaObject; 0579 AudioOutput *ao = new AudioOutput(category, mo); 0580 createPath(mo, ao); 0581 if (isPlayable(source.type())) { 0582 mo->setCurrentSource(source); 0583 } 0584 return mo; 0585 } 0586 0587 } //namespace Phonon 0588 0589 #include "moc_mediaobject.cpp" 0590 0591 #undef PHONON_CLASSNAME 0592 #undef PHONON_INTERFACENAME 0593 // vim: sw=4 tw=100 et