diff options
author | Yoann Lopes <yoann.lopes@theqtcompany.com> | 2015-02-18 16:58:02 +0100 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@theqtcompany.com> | 2015-04-07 17:21:47 +0000 |
commit | 63cff37741dca71f3db45ee06bc5bb06488c51f4 (patch) | |
tree | 4feeb36d8f95a24b5dc5dc12c99f471f824de38b /src/multimedia | |
parent | 4d17db19f895ddaa778120c346d8a6a33a710194 (diff) |
QMediaPlayer: handle resource files in a cross-platform way.
It was the backend's responsibility to handle resource files in an
appropriate way. In practice, it was either not handled at all,
or implemented in an almost identical manner in every backend
that does handle it.
This is now dealt with in QMediaPlayer, always passing to the
backend something it will be able to play. If the backend has the
StreamPlayback capability, we pass a QFile from which it streams
the data. If it doesn't, we copy the resource to a temporary
file and pass its path to the backend.
Task-number: QTBUG-36175
Task-number: QTBUG-42263
Task-number: QTBUG-43839
Change-Id: I57b355c72692d02661baeaf74e66581ca0a0bd1d
Reviewed-by: Andrew Knight <qt@panimo.net>
Reviewed-by: Peng Wu <peng.wu@intopalo.com>
Reviewed-by: Christian Stromme <christian.stromme@theqtcompany.com>
Diffstat (limited to 'src/multimedia')
-rw-r--r-- | src/multimedia/controls/qmediaplayercontrol.cpp | 5 | ||||
-rw-r--r-- | src/multimedia/playback/qmediaplayer.cpp | 157 | ||||
-rw-r--r-- | src/multimedia/playback/qmediaplayer.h | 1 | ||||
-rw-r--r-- | src/multimedia/qmediaserviceprovider.cpp | 52 | ||||
-rw-r--r-- | src/multimedia/qmediaserviceprovider_p.h | 2 |
5 files changed, 179 insertions, 38 deletions
diff --git a/src/multimedia/controls/qmediaplayercontrol.cpp b/src/multimedia/controls/qmediaplayercontrol.cpp index 1eccb7627..9ea6fde82 100644 --- a/src/multimedia/controls/qmediaplayercontrol.cpp +++ b/src/multimedia/controls/qmediaplayercontrol.cpp @@ -315,6 +315,11 @@ QMediaPlayerControl::QMediaPlayerControl(QObject *parent): Setting the media to a null QMediaContent will cause the control to discard all information relating to the current media source and to cease all I/O operations related to that media. + + Qt resource files are never passed as is. If the service supports + QMediaServiceProviderHint::StreamPlayback, a \a stream is supplied, pointing to an opened + QFile. Otherwise, the resource is copied into a temporary file and \a media contains the + url to that file. */ /*! diff --git a/src/multimedia/playback/qmediaplayer.cpp b/src/multimedia/playback/qmediaplayer.cpp index aae4c7efa..b43faa2b9 100644 --- a/src/multimedia/playback/qmediaplayer.cpp +++ b/src/multimedia/playback/qmediaplayer.cpp @@ -48,6 +48,8 @@ #include <QtCore/qtimer.h> #include <QtCore/qdebug.h> #include <QtCore/qpointer.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qtemporaryfile.h> QT_BEGIN_NAMESPACE @@ -103,22 +105,30 @@ public: : provider(0) , control(0) , state(QMediaPlayer::StoppedState) + , status(QMediaPlayer::UnknownMediaStatus) , error(QMediaPlayer::NoError) + , ignoreNextStatusChange(-1) , playlist(0) , networkAccessControl(0) + , hasStreamPlaybackFeature(false) , nestedPlaylists(0) {} QMediaServiceProvider *provider; QMediaPlayerControl* control; QMediaPlayer::State state; + QMediaPlayer::MediaStatus status; QMediaPlayer::Error error; QString errorString; + int ignoreNextStatusChange; QPointer<QObject> videoOutput; QMediaPlaylist *playlist; QMediaNetworkAccessControl *networkAccessControl; QVideoSurfaceOutput surfaceOutput; + bool hasStreamPlaybackFeature; + QMediaContent qrcMedia; + QScopedPointer<QFile> qrcFile; QMediaContent rootMedia; QMediaContent pendingPlaylist; @@ -126,6 +136,8 @@ public: bool isInChain(QUrl url); int nestedPlaylists; + void setMedia(const QMediaContent &media, QIODevice *stream = 0); + void setPlaylist(QMediaPlaylist *playlist); void setPlaylistMedia(); void loadPlaylist(); @@ -137,6 +149,7 @@ public: void _q_error(int error, const QString &errorString); void _q_updateMedia(const QMediaContent&); void _q_playlistDestroyed(); + void _q_handleMediaChanged(const QMediaContent&); void _q_handlePlaylistLoaded(); void _q_handlePlaylistLoadFailed(); }; @@ -196,22 +209,30 @@ void QMediaPlayerPrivate::_q_stateChanged(QMediaPlayer::State ps) } } -void QMediaPlayerPrivate::_q_mediaStatusChanged(QMediaPlayer::MediaStatus status) +void QMediaPlayerPrivate::_q_mediaStatusChanged(QMediaPlayer::MediaStatus s) { Q_Q(QMediaPlayer); - switch (status) { - case QMediaPlayer::StalledMedia: - case QMediaPlayer::BufferingMedia: - q->addPropertyWatch("bufferStatus"); - emit q->mediaStatusChanged(status); - break; - default: - q->removePropertyWatch("bufferStatus"); - emit q->mediaStatusChanged(status); - break; + if (int(s) == ignoreNextStatusChange) { + ignoreNextStatusChange = -1; + return; } + if (s != status) { + status = s; + + switch (s) { + case QMediaPlayer::StalledMedia: + case QMediaPlayer::BufferingMedia: + q->addPropertyWatch("bufferStatus"); + break; + default: + q->removePropertyWatch("bufferStatus"); + break; + } + + emit q->mediaStatusChanged(s); + } } void QMediaPlayerPrivate::_q_error(int error, const QString &errorString) @@ -276,7 +297,7 @@ void QMediaPlayerPrivate::_q_updateMedia(const QMediaContent &media) const QMediaPlayer::State currentState = state; - control->setMedia(media, 0); + setMedia(media, 0); if (!media.isNull()) { switch (currentState) { @@ -297,11 +318,76 @@ void QMediaPlayerPrivate::_q_updateMedia(const QMediaContent &media) void QMediaPlayerPrivate::_q_playlistDestroyed() { playlist = 0; + setMedia(QMediaContent(), 0); +} + +void QMediaPlayerPrivate::setMedia(const QMediaContent &media, QIODevice *stream) +{ + Q_Q(QMediaPlayer); if (!control) return; - control->setMedia(QMediaContent(), 0); + QScopedPointer<QFile> file; + + // Backends can't play qrc files directly. + // If the backend supports StreamPlayback, we pass a QFile for that resource. + // If it doesn't, we copy the data to a temporary file and pass its path. + if (!media.isNull() && !stream && media.canonicalUrl().scheme() == QLatin1String("qrc")) { + qrcMedia = media; + + file.reset(new QFile(QLatin1Char(':') + media.canonicalUrl().path())); + if (!file->open(QFile::ReadOnly)) { + QMetaObject::invokeMethod(q, "_q_error", Qt::QueuedConnection, + Q_ARG(int, QMediaPlayer::ResourceError), + Q_ARG(QString, QObject::tr("Attempting to play invalid Qt resource"))); + QMetaObject::invokeMethod(q, "_q_mediaStatusChanged", Qt::QueuedConnection, + Q_ARG(QMediaPlayer::MediaStatus, QMediaPlayer::InvalidMedia)); + file.reset(); + // Ignore the next NoMedia status change, we just want to clear the current media + // on the backend side since we can't load the new one and we want to be in the + // InvalidMedia status. + ignoreNextStatusChange = QMediaPlayer::NoMedia; + control->setMedia(QMediaContent(), 0); + + } else if (hasStreamPlaybackFeature) { + control->setMedia(media, file.data()); + } else { + QTemporaryFile *tempFile = new QTemporaryFile; + + // Preserve original file extension, some backends might not load the file if it doesn't + // have an extension. + const QString suffix = QFileInfo(*file).suffix(); + if (!suffix.isEmpty()) + tempFile->setFileTemplate(tempFile->fileTemplate() + QLatin1Char('.') + suffix); + + // Copy the qrc data into the temporary file + tempFile->open(); + char buffer[4096]; + while (true) { + qint64 len = file->read(buffer, sizeof(buffer)); + if (len < 1) + break; + tempFile->write(buffer, len); + } + tempFile->close(); + + file.reset(tempFile); + control->setMedia(QMediaContent(QUrl::fromLocalFile(file->fileName())), 0); + } + } else { + qrcMedia = QMediaContent(); + control->setMedia(media, stream); + } + + qrcFile.swap(file); // Cleans up any previous file +} + +void QMediaPlayerPrivate::_q_handleMediaChanged(const QMediaContent &media) +{ + Q_Q(QMediaPlayer); + + emit q->currentMediaChanged(qrcMedia.isNull() ? media : qrcMedia); } void QMediaPlayerPrivate::setPlaylist(QMediaPlaylist *pls) @@ -333,7 +419,7 @@ void QMediaPlayerPrivate::setPlaylistMedia() playlist->next(); } return; - } else if (control != 0) { + } else { // If we've just switched to a new playlist, // then last emitted currentMediaChanged was a playlist. // Make sure we emit currentMediaChanged if new playlist has @@ -344,14 +430,14 @@ void QMediaPlayerPrivate::setPlaylistMedia() // 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); + bool isSameMedia = (q->currentMedia() == playlist->currentMedia()); + setMedia(playlist->currentMedia(), 0); if (isSameMedia) { - emit q->currentMediaChanged(control->media()); + emit q->currentMediaChanged(q->currentMedia()); } } } else { - q->setMedia(QMediaContent(), 0); + setMedia(QMediaContent(), 0); } } @@ -441,7 +527,7 @@ void QMediaPlayerPrivate::_q_handlePlaylistLoadFailed() if (playlist) playlist->next(); else - control->setMedia(QMediaContent(), 0); + setMedia(QMediaContent(), 0); } static QMediaService *playerService(QMediaPlayer::Flags flags) @@ -484,7 +570,7 @@ QMediaPlayer::QMediaPlayer(QObject *parent, QMediaPlayer::Flags flags): d->control = qobject_cast<QMediaPlayerControl*>(d->service->requestControl(QMediaPlayerControl_iid)); d->networkAccessControl = qobject_cast<QMediaNetworkAccessControl*>(d->service->requestControl(QMediaNetworkAccessControl_iid)); if (d->control != 0) { - connect(d->control, SIGNAL(mediaChanged(QMediaContent)), SIGNAL(currentMediaChanged(QMediaContent))); + connect(d->control, SIGNAL(mediaChanged(QMediaContent)), SLOT(_q_handleMediaChanged(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))); @@ -500,11 +586,16 @@ QMediaPlayer::QMediaPlayer(QObject *parent, QMediaPlayer::Flags flags): connect(d->control, SIGNAL(playbackRateChanged(qreal)), SIGNAL(playbackRateChanged(qreal))); connect(d->control, SIGNAL(bufferStatusChanged(int)), SIGNAL(bufferStatusChanged(int))); - if (d->control->state() == PlayingState) + d->state = d->control->state(); + d->status = d->control->mediaStatus(); + + if (d->state == PlayingState) addPropertyWatch("position"); - if (d->control->mediaStatus() == StalledMedia || d->control->mediaStatus() == BufferingMedia) + if (d->status == StalledMedia || d->status == BufferingMedia) addPropertyWatch("bufferStatus"); + + d->hasStreamPlaybackFeature = d->provider->supportedFeatures(d->service).testFlag(QMediaServiceProviderHint::StreamPlayback); } if (d->networkAccessControl != 0) { connect(d->networkAccessControl, SIGNAL(configurationChanged(QNetworkConfiguration)), @@ -549,7 +640,9 @@ const QIODevice *QMediaPlayer::mediaStream() const { Q_D(const QMediaPlayer); - if (d->control != 0) + // When playing a resource file, we might have passed a QFile to the backend. Hide it from + // the user. + if (d->control && d->qrcMedia.isNull()) return d->control->mediaStream(); return 0; @@ -566,7 +659,12 @@ QMediaContent QMediaPlayer::currentMedia() const { Q_D(const QMediaPlayer); - if (d->control != 0) + // When playing a resource file, don't return the backend's current media, which + // can be a temporary file. + if (!d->qrcMedia.isNull()) + return d->qrcMedia; + + if (d->control) return d->control->media(); return QMediaContent(); @@ -600,12 +698,7 @@ QMediaPlayer::State QMediaPlayer::state() const QMediaPlayer::MediaStatus QMediaPlayer::mediaStatus() const { - Q_D(const QMediaPlayer); - - if (d->control != 0) - return d->control->mediaStatus(); - - return QMediaPlayer::UnknownMediaStatus; + return d_func()->status; } qint64 QMediaPlayer::duration() const @@ -877,8 +970,8 @@ void QMediaPlayer::setMedia(const QMediaContent &media, QIODevice *stream) // reset playlist to the 1st item media.playlist()->setCurrentIndex(0); d->setPlaylist(media.playlist()); - } else if (d->control != 0) { - d->control->setMedia(media, stream); + } else { + d->setMedia(media, stream); } } diff --git a/src/multimedia/playback/qmediaplayer.h b/src/multimedia/playback/qmediaplayer.h index b1215eee1..735f11130 100644 --- a/src/multimedia/playback/qmediaplayer.h +++ b/src/multimedia/playback/qmediaplayer.h @@ -202,6 +202,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_error(int, const QString &)) Q_PRIVATE_SLOT(d_func(), void _q_updateMedia(const QMediaContent&)) Q_PRIVATE_SLOT(d_func(), void _q_playlistDestroyed()) + Q_PRIVATE_SLOT(d_func(), void _q_handleMediaChanged(const QMediaContent&)) Q_PRIVATE_SLOT(d_func(), void _q_handlePlaylistLoaded()) Q_PRIVATE_SLOT(d_func(), void _q_handlePlaylistLoadFailed()) }; diff --git a/src/multimedia/qmediaserviceprovider.cpp b/src/multimedia/qmediaserviceprovider.cpp index 563af846a..658679c56 100644 --- a/src/multimedia/qmediaserviceprovider.cpp +++ b/src/multimedia/qmediaserviceprovider.cpp @@ -299,7 +299,14 @@ Q_GLOBAL_STATIC_WITH_ARGS(QMediaPluginLoader, loader, class QPluginServiceProvider : public QMediaServiceProvider { - QMap<QMediaService*, QMediaServiceProviderPlugin*> pluginMap; + struct MediaServiceData { + QByteArray type; + QMediaServiceProviderPlugin *plugin; + + MediaServiceData() : plugin(0) { } + }; + + QMap<const QMediaService*, MediaServiceData> mediaServiceData; public: QMediaService* requestService(const QByteArray &type, const QMediaServiceProviderHint &hint) @@ -416,8 +423,12 @@ public: if (plugin != 0) { QMediaService *service = plugin->create(key); - if (service != 0) - pluginMap.insert(service, plugin); + if (service != 0) { + MediaServiceData d; + d.type = type; + d.plugin = plugin; + mediaServiceData.insert(service, d); + } return service; } @@ -430,11 +441,28 @@ public: void releaseService(QMediaService *service) { if (service != 0) { - QMediaServiceProviderPlugin *plugin = pluginMap.take(service); + MediaServiceData d = mediaServiceData.take(service); + + if (d.plugin != 0) + d.plugin->release(service); + } + } + + QMediaServiceProviderHint::Features supportedFeatures(const QMediaService *service) const + { + if (service) { + MediaServiceData d = mediaServiceData.value(service); + + if (d.plugin) { + QMediaServiceFeaturesInterface *iface = + qobject_cast<QMediaServiceFeaturesInterface*>(d.plugin); - if (plugin != 0) - plugin->release(service); + if (iface) + return iface->supportedFeatures(d.type); + } } + + return QMediaServiceProviderHint::Features(); } QMultimedia::SupportEstimate hasSupport(const QByteArray &serviceType, @@ -661,6 +689,18 @@ Q_GLOBAL_STATIC(QPluginServiceProvider, pluginProvider); */ /*! + \fn QMediaServiceProvider::supportedFeatures(const QMediaService *service) const + + Returns the features supported by a given \a service. +*/ +QMediaServiceProviderHint::Features QMediaServiceProvider::supportedFeatures(const QMediaService *service) const +{ + Q_UNUSED(service); + + return QMediaServiceProviderHint::Features(0); +} + +/*! \fn QMultimedia::SupportEstimate QMediaServiceProvider::hasSupport(const QByteArray &serviceType, const QString &mimeType, const QStringList& codecs, int flags) const Returns how confident a media service provider is that is can provide a \a diff --git a/src/multimedia/qmediaserviceprovider_p.h b/src/multimedia/qmediaserviceprovider_p.h index 62ee510c2..4230c427d 100644 --- a/src/multimedia/qmediaserviceprovider_p.h +++ b/src/multimedia/qmediaserviceprovider_p.h @@ -53,6 +53,8 @@ public: virtual QMediaService* requestService(const QByteArray &type, const QMediaServiceProviderHint &hint = QMediaServiceProviderHint()) = 0; virtual void releaseService(QMediaService *service) = 0; + virtual QMediaServiceProviderHint::Features supportedFeatures(const QMediaService *service) const; + virtual QMultimedia::SupportEstimate hasSupport(const QByteArray &serviceType, const QString &mimeType, const QStringList& codecs, |