diff options
author | Lev Zelenskiy <lev.zelenskiy@nokia.com> | 2012-03-09 09:53:21 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-03-21 05:43:27 +0100 |
commit | 2c034beab5a8d72a148c47e85e020dc64c58068f (patch) | |
tree | 069cc8427d307f4c5ee359182a55be6ba7026ee6 /src/multimedia/playback/qmediaplayer.cpp | |
parent | 2ebea05283a2ddad831d5039a49817cefb7c7625 (diff) |
QMediaPlayer frontend: changes to playlist support.
Allow to load playlists using setMedia().
Use QNetworkMediaPlaylistProvider for playlist parsing.
Updated unit tests.
Change-Id: If4dba07be8b2e8a9e9549d5bed58e552dfb958b7
Reviewed-by: Dmytro Poplavskiy <dmytro.poplavskiy@nokia.com>
Diffstat (limited to 'src/multimedia/playback/qmediaplayer.cpp')
-rw-r--r-- | src/multimedia/playback/qmediaplayer.cpp | 403 |
1 files changed, 322 insertions, 81 deletions
diff --git a/src/multimedia/playback/qmediaplayer.cpp b/src/multimedia/playback/qmediaplayer.cpp index 0ff341438..a699e281f 100644 --- a/src/multimedia/playback/qmediaplayer.cpp +++ b/src/multimedia/playback/qmediaplayer.cpp @@ -49,7 +49,6 @@ #include <qmediaplaylist.h> #include <qmediaplaylistcontrol_p.h> #include <qmediaplaylistsourcecontrol_p.h> - #include <qmedianetworkaccesscontrol.h> #include <QtCore/qcoreevent.h> @@ -97,6 +96,8 @@ public: } _registerPlayerMetaTypes; } +#define MAX_NESTED_PLAYLISTS 16 + class QMediaPlayerPrivate : public QMediaObjectPrivate { Q_DECLARE_NON_CONST_PUBLIC(QMediaPlayer) @@ -105,47 +106,86 @@ public: QMediaPlayerPrivate() : provider(0) , control(0) - , playlistSourceControl(0) , state(QMediaPlayer::StoppedState) , error(QMediaPlayer::NoError) - , filterStates(false) , playlist(0) , networkAccessControl(0) + , nestedPlaylists(0) {} QMediaServiceProvider *provider; QMediaPlayerControl* control; - QMediaPlaylistSourceControl* playlistSourceControl; QMediaPlayer::State state; QMediaPlayer::Error error; QString errorString; - bool filterStates; QPointer<QObject> videoOutput; QMediaPlaylist *playlist; QMediaNetworkAccessControl *networkAccessControl; QVideoSurfaceOutput surfaceOutput; + QMediaContent rootMedia; + QMediaContent pendingPlaylist; + QMediaPlaylist *parentPlaylist(QMediaPlaylist *pls); + bool isInChain(QUrl url); + int nestedPlaylists; + + void setPlaylist(QMediaPlaylist *playlist); + void setPlaylistMedia(); + void loadPlaylist(); + void disconnectPlaylist(); + void connectPlaylist(); + void _q_stateChanged(QMediaPlayer::State state); void _q_mediaStatusChanged(QMediaPlayer::MediaStatus status); void _q_error(int error, const QString &errorString); void _q_updateMedia(const QMediaContent&); void _q_playlistDestroyed(); + void _q_handlePlaylistLoaded(); + void _q_handlePlaylistLoadFailed(); }; +QMediaPlaylist *QMediaPlayerPrivate::parentPlaylist(QMediaPlaylist *pls) +{ + // This function finds a parent playlist for an item in the active chain of playlists. + // Every item in the chain comes from currentMedia() of its parent. + // We don't need to travers the whole tree of playlists, + // but only the subtree of active ones. + for (QMediaPlaylist *current = rootMedia.playlist(); current && current != pls; current = current->currentMedia().playlist()) + if (current->currentMedia().playlist() == pls) + return current; + return 0; +} + +bool QMediaPlayerPrivate::isInChain(QUrl url) +{ + // Check whether a URL is already in the chain of playlists. + // Also see a comment in parentPlaylist(). + for (QMediaPlaylist *current = rootMedia.playlist(); current && current != playlist; current = current->currentMedia().playlist()) + if (current->currentMedia().canonicalUrl() == url) { + return true; + } + return false; +} + void QMediaPlayerPrivate::_q_stateChanged(QMediaPlayer::State ps) { Q_Q(QMediaPlayer); - if (filterStates) - return; - - if (playlist - && ps != state && ps == QMediaPlayer::StoppedState - && (control->mediaStatus() == QMediaPlayer::EndOfMedia || - control->mediaStatus() == QMediaPlayer::InvalidMedia)) { - playlist->next(); - ps = control->state(); + // Backend switches into stopped state every time new media is about to be loaded. + // If media player has a playlist loaded make sure player doesn' stop. + if (playlist && playlist->currentIndex() != -1 && ps != state && ps == QMediaPlayer::StoppedState) { + if (control->mediaStatus() == QMediaPlayer::EndOfMedia || + control->mediaStatus() == QMediaPlayer::InvalidMedia) { + // if media player is not stopped, and + // we have finished playback for the current media, + // advance to the next item in the playlist + Q_ASSERT(state != QMediaPlayer::StoppedState); + playlist->next(); + return; + } else if (control->mediaStatus() == QMediaPlayer::LoadingMedia) { + return; + } } if (ps != state) { @@ -182,10 +222,16 @@ void QMediaPlayerPrivate::_q_error(int error, const QString &errorString) { Q_Q(QMediaPlayer); - this->error = QMediaPlayer::Error(error); - this->errorString = errorString; + if (error == int(QMediaPlayer::MediaIsPlaylist)) { + loadPlaylist(); + } else { + this->error = QMediaPlayer::Error(error); + this->errorString = errorString; + emit q->error(this->error); - emit q->error(this->error); + if (playlist) + playlist->next(); + } } void QMediaPlayerPrivate::_q_updateMedia(const QMediaContent &media) @@ -195,9 +241,45 @@ void QMediaPlayerPrivate::_q_updateMedia(const QMediaContent &media) if (!control) return; + // check if the current playlist is a top-level playlist + Q_ASSERT(playlist); + if (media.isNull() && playlist != rootMedia.playlist()) { + // switch back to parent playlist + QMediaPlaylist *pls = parentPlaylist(playlist); + Q_ASSERT(pls); + disconnectPlaylist(); + playlist = pls; + connectPlaylist(); + + Q_ASSERT(!pendingPlaylist.playlist()); + nestedPlaylists--; + Q_ASSERT(nestedPlaylists >= 0); + + playlist->next(); + return; + } + + if (media.playlist()) { + if (nestedPlaylists < MAX_NESTED_PLAYLISTS) { + nestedPlaylists++; + Q_ASSERT(!pendingPlaylist.playlist()); + + // disconnect current playlist + disconnectPlaylist(); + // new playlist signals are connected + // in the call to setPlaylist() in _q_handlePlaylistLoaded() + playlist = media.playlist(); + emit q->currentMediaChanged(media); + _q_handlePlaylistLoaded(); + return; + } else if (playlist) { + playlist->next(); + } + return; + } + const QMediaPlayer::State currentState = state; - filterStates = true; control->setMedia(media, 0); if (!media.isNull()) { @@ -212,18 +294,8 @@ void QMediaPlayerPrivate::_q_updateMedia(const QMediaContent &media) break; } } - filterStates = false; - state = control->state(); - - if (state != currentState) { - if (state == QMediaPlayer::PlayingState) - q->addPropertyWatch("position"); - else - q->removePropertyWatch("position"); - - emit q->stateChanged(state); - } + _q_stateChanged(control->state()); } void QMediaPlayerPrivate::_q_playlistDestroyed() @@ -233,12 +305,151 @@ void QMediaPlayerPrivate::_q_playlistDestroyed() if (!control) return; - if (playlistSourceControl) - playlistSourceControl->setPlaylist(0); - control->setMedia(QMediaContent(), 0); } +void QMediaPlayerPrivate::setPlaylist(QMediaPlaylist *pls) +{ + disconnectPlaylist(); + playlist = pls; + + setPlaylistMedia(); +} + +void QMediaPlayerPrivate::setPlaylistMedia() +{ + // This function loads current playlist media into backend. + // If current media is a playlist, the function recursively + // loads media from the playlist. + // It also makes sure the correct playlist signals are connected. + Q_Q(QMediaPlayer); + + if (playlist) { + connectPlaylist(); + if (playlist->currentMedia().playlist()) { + if (nestedPlaylists < MAX_NESTED_PLAYLISTS) { + emit q->currentMediaChanged(playlist->currentMedia()); + // rewind nested playlist to start + playlist->currentMedia().playlist()->setCurrentIndex(0); + nestedPlaylists++; + setPlaylist(playlist->currentMedia().playlist()); + } else { + playlist->next(); + } + return; + } else if (control != 0) { + // If we've just switched to a new playlist, + // then last emited currentMediaChanged was a playlist. + // Make sure we emit currentMediaChanged if new playlist has + // the same media as the previous one: + // sample.m3u + // test.wav -- processed by backend + // nested.m3u -- processed by frontend + // test.wav -- processed by backend, + // media is not changed, + // frontend needs to emit currentMediaChanged + bool isSameMedia = (control->media() == playlist->currentMedia()); + control->setMedia(playlist->currentMedia(), 0); + if (isSameMedia) { + emit q->currentMediaChanged(control->media()); + } + } + } else { + q->setMedia(QMediaContent(), 0); + } +} + +void QMediaPlayerPrivate::loadPlaylist() +{ + Q_Q(QMediaPlayer); + Q_ASSERT(pendingPlaylist.isNull()); + + // Do not load a playlist if there are more than MAX_NESTED_PLAYLISTS in the chain already, + // or if the playlist URL is already in the chain, i.e. do not allow recursive playlists and loops. + if (nestedPlaylists < MAX_NESTED_PLAYLISTS && !q->currentMedia().canonicalUrl().isEmpty() && !isInChain(q->currentMedia().canonicalUrl())) { + pendingPlaylist = QMediaContent(new QMediaPlaylist, q->currentMedia().canonicalUrl(), true); + QObject::connect(pendingPlaylist.playlist(), SIGNAL(loaded()), q, SLOT(_q_handlePlaylistLoaded())); + QObject::connect(pendingPlaylist.playlist(), SIGNAL(loadFailed()), q, SLOT(_q_handlePlaylistLoadFailed())); + pendingPlaylist.playlist()->load(pendingPlaylist.canonicalUrl()); + } else if (playlist) { + playlist->next(); + } +} + +void QMediaPlayerPrivate::disconnectPlaylist() +{ + Q_Q(QMediaPlayer); + if (playlist) { + QObject::disconnect(playlist, SIGNAL(currentMediaChanged(QMediaContent)), + q, SLOT(_q_updateMedia(QMediaContent))); + QObject::disconnect(playlist, SIGNAL(destroyed()), q, SLOT(_q_playlistDestroyed())); + } +} + +void QMediaPlayerPrivate::connectPlaylist() +{ + Q_Q(QMediaPlayer); + if (playlist) { + QObject::connect(playlist, SIGNAL(currentMediaChanged(QMediaContent)), + q, SLOT(_q_updateMedia(QMediaContent))); + QObject::connect(playlist, SIGNAL(destroyed()), q, SLOT(_q_playlistDestroyed())); + } +} + +void QMediaPlayerPrivate::_q_handlePlaylistLoaded() +{ + Q_Q(QMediaPlayer); + + QMediaPlaylist *oldPlaylist = 0; + if (pendingPlaylist.playlist()) { + Q_ASSERT(!q->currentMedia().playlist()); + // if there is an active playlist + if (playlist) { + Q_ASSERT(playlist->currentIndex() >= 0); + oldPlaylist = playlist; + disconnectPlaylist(); + playlist->insertMedia(playlist->currentIndex() + 1, pendingPlaylist); + playlist->removeMedia(playlist->currentIndex()); + nestedPlaylists++; + } else { + Q_ASSERT(!rootMedia.playlist()); + rootMedia = pendingPlaylist; + emit q->mediaChanged(rootMedia); + } + + playlist = pendingPlaylist.playlist(); + emit q->currentMediaChanged(pendingPlaylist); + } + pendingPlaylist = QMediaContent(); + + playlist->next(); + setPlaylistMedia(); + + switch (state) { + case QMediaPlayer::PausedState: + control->pause(); + break; + case QMediaPlayer::PlayingState: + control->play(); + break; + case QMediaPlayer::StoppedState: + break; + } +} + +void QMediaPlayerPrivate::_q_handlePlaylistLoadFailed() +{ + pendingPlaylist = QMediaContent(); + + if (!control) + return; + + if (playlist) + playlist->next(); + else + control->setMedia(QMediaContent(), 0); +} + static QMediaService *playerService(QMediaPlayer::Flags flags) { QMediaServiceProvider *provider = QMediaServiceProvider::defaultServiceProvider(); @@ -277,10 +488,9 @@ QMediaPlayer::QMediaPlayer(QObject *parent, QMediaPlayer::Flags flags): d->error = ServiceMissingError; } else { d->control = qobject_cast<QMediaPlayerControl*>(d->service->requestControl(QMediaPlayerControl_iid)); - d->playlistSourceControl = qobject_cast<QMediaPlaylistSourceControl*>(d->service->requestControl(QMediaPlaylistSourceControl_iid)); d->networkAccessControl = qobject_cast<QMediaNetworkAccessControl*>(d->service->requestControl(QMediaNetworkAccessControl_iid)); if (d->control != 0) { - connect(d->control, SIGNAL(mediaChanged(QMediaContent)), SIGNAL(mediaChanged(QMediaContent))); + connect(d->control, SIGNAL(mediaChanged(QMediaContent)), SIGNAL(currentMediaChanged(QMediaContent))); connect(d->control, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(_q_stateChanged(QMediaPlayer::State))); connect(d->control, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), SLOT(_q_mediaStatusChanged(QMediaPlayer::MediaStatus))); @@ -330,10 +540,7 @@ QMediaContent QMediaPlayer::media() const { Q_D(const QMediaPlayer); - if (d->control != 0) - return d->control->media(); - - return QMediaContent(); + return d->rootMedia; } /*! @@ -356,44 +563,25 @@ const QIODevice *QMediaPlayer::mediaStream() const QMediaPlaylist *QMediaPlayer::playlist() const { - return d_func()->playlistSourceControl ? - d_func()->playlistSourceControl->playlist() : - d_func()->playlist; + Q_D(const QMediaPlayer); + + return d->rootMedia.playlist(); } -void QMediaPlayer::setPlaylist(QMediaPlaylist *playlist) +QMediaContent QMediaPlayer::currentMedia() const { - Q_D(QMediaPlayer); - - if (d->playlistSourceControl) { - if (d->playlistSourceControl->playlist()) - disconnect(d->playlist, SIGNAL(destroyed()), this, SLOT(_q_playlistDestroyed())); - - d->playlistSourceControl->setPlaylist(playlist); - - if (playlist) - connect(d->playlist, SIGNAL(destroyed()), this, SLOT(_q_playlistDestroyed())); - } else { - if (d->playlist) { - disconnect(d->playlist, SIGNAL(currentMediaChanged(QMediaContent)), - this, SLOT(_q_updateMedia(QMediaContent))); - disconnect(d->playlist, SIGNAL(destroyed()), this, SLOT(_q_playlistDestroyed())); - } - - d->playlist = playlist; + Q_D(const QMediaPlayer); - if (d->playlist) { - connect(d->playlist, SIGNAL(currentMediaChanged(QMediaContent)), - this, SLOT(_q_updateMedia(QMediaContent))); - connect(d->playlist, SIGNAL(destroyed()), this, SLOT(_q_playlistDestroyed())); + if (d->control != 0) + return d->control->media(); - if (d->control != 0) - d->control->setMedia(playlist->currentMedia(), 0); - } else { - setMedia(QMediaContent(), 0); - } + return QMediaContent(); +} - } +void QMediaPlayer::setPlaylist(QMediaPlaylist *playlist) +{ + QMediaContent m(playlist, QUrl(), false); + setMedia(m); } /*! @@ -563,8 +751,18 @@ void QMediaPlayer::play() } //if playlist control is available, the service should advance itself - if (d->playlist && d->playlist->currentIndex() == -1 && !d->playlist->isEmpty()) + if (d->rootMedia.playlist() && d->rootMedia.playlist()->currentIndex() == -1 && !d->rootMedia.playlist()->isEmpty()) { + + // switch to playing state + if (d->state != QMediaPlayer::PlayingState) + d->_q_stateChanged(QMediaPlayer::PlayingState); + + if (d->playlist != d->rootMedia.playlist()) + d->setPlaylist(d->rootMedia.playlist()); + Q_ASSERT(d->playlist == d->rootMedia.playlist()); + emit currentMediaChanged(d->rootMedia); d->playlist->setCurrentIndex(0); + } // Reset error conditions d->error = NoError; @@ -595,6 +793,17 @@ void QMediaPlayer::stop() if (d->control != 0) d->control->stop(); + + // If media player didn't stop in response to control. + // This happens if we have an active playlist and control + // media status is + // QMediaPlayer::LoadingMedia, QMediaPlayer::InvalidMedia, or QMediaPlayer::EndOfMedia + // see QMediaPlayerPrivate::_q_stateChanged() + if (d->playlist && d->state != QMediaPlayer::StoppedState) { + d->state = QMediaPlayer::StoppedState; + removePropertyWatch("position"); + emit stateChanged(QMediaPlayer::StoppedState); + } } void QMediaPlayer::setPosition(qint64 position) @@ -654,12 +863,24 @@ void QMediaPlayer::setPlaybackRate(qreal rate) void QMediaPlayer::setMedia(const QMediaContent &media, QIODevice *stream) { Q_D(QMediaPlayer); - - if (playlist() && playlist()->currentMedia() != media) - setPlaylist(0); - - if (d->control != 0) - d_func()->control->setMedia(media, stream); + stop(); + + QMediaContent oldMedia = d->rootMedia; + d->disconnectPlaylist(); + d->playlist = 0; + d->rootMedia = media; + d->nestedPlaylists = 0; + + if (oldMedia != media) + emit mediaChanged(d->rootMedia); + + if (media.playlist()) { + // reset playlist to the 1st item + media.playlist()->setCurrentIndex(0); + d->setPlaylist(media.playlist()); + } else if (d->control != 0) { + d->control->setMedia(media, stream); + } } /*! @@ -796,6 +1017,7 @@ QtMultimedia::AvailabilityError QMediaPlayer::availabilityError() const return QMediaObject::availabilityError(); } + // Enums /*! \enum QMediaPlayer::State @@ -841,6 +1063,7 @@ QtMultimedia::AvailabilityError QMediaPlayer::availabilityError() const \value NetworkError A network error occurred. \value AccessDeniedError There are not the appropriate permissions to play a media resource. \value ServiceMissingError A valid playback service was not found, playback cannot proceed. + \omitvalue MediaIsPlaylist */ // Signals @@ -869,9 +1092,17 @@ QtMultimedia::AvailabilityError QMediaPlayer::availabilityError() const /*! \fn void QMediaPlayer::mediaChanged(const QMediaContent &media); - Signals that the current playing content will be obtained from \a media. + Signals that the media source has been changed to \a media. + + \sa media(), currentMediaChanged() +*/ + +/*! + \fn void QMediaPlayer::currentMediaChanged(const QMediaContent &media); + + Signals that the current playing content has been changed to \a media. - \sa media() + \sa currentMedia(), mediaChanged() */ /*! @@ -916,7 +1147,17 @@ QtMultimedia::AvailabilityError QMediaPlayer::availabilityError() const information relating to the current media source and to cease all I/O operations related to that media. - \sa QMediaContent + \sa QMediaContent, currentMedia() +*/ + +/*! + \property QMediaPlayer::currentMedia + \brief the current active media content being played by the player object. + This value could be different from QMediaPlayer::media property if a playlist is used. + In this case currentMedia indicates the current media content being processed + by the player, while QMediaPlayer::media property contains the original playlist. + + \sa QMediaContent, media() */ /*! @@ -928,7 +1169,7 @@ QtMultimedia::AvailabilityError QMediaPlayer::availabilityError() const By default this property is set to null. - If the media playlist is used as a source, QMediaPlayer::media is updated with + If the media playlist is used as a source, QMediaPlayer::currentMedia is updated with a current playlist item. The current source should be selected with QMediaPlaylist::setCurrentIndex(int) instead of QMediaPlayer::setMedia(), otherwise the current playlist will be discarded. |