File indexing completed on 2024-04-14 04:38:25

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