| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwinrtmediaplayercontrol.h" |
| #include "qwinrtplayerrenderercontrol.h" |
| |
| #include <QtCore/QCoreApplication> |
| #include <QtCore/QFile> |
| #include <QtCore/qfunctions_winrt.h> |
| #include <QtCore/QSize> |
| #include <QtCore/QTimerEvent> |
| #include <QtCore/QUrl> |
| #include <QtMultimedia/QMediaPlaylist> |
| #include <QtConcurrent/QtConcurrentRun> |
| |
| #include <dxgi.h> |
| #include <OleAuto.h> |
| #include <mfapi.h> |
| #include <mfmediaengine.h> |
| |
| #include <wrl.h> |
| using namespace Microsoft::WRL; |
| |
| QT_BEGIN_NAMESPACE |
| |
| #define QT_WINRT_MEDIAPLAYER_STREAM_ID "__qtmultimedia_winrt_player_stream" |
| |
| class MediaEngineNotify; |
| class MediaEngineSources; |
| class MediaEngineByteStream; |
| class QWinRTMediaPlayerControlPrivate |
| { |
| public: |
| QMediaPlayer::State state; |
| QMediaPlayer::MediaStatus mediaStatus; |
| qint64 duration; |
| qint64 position; |
| qreal playbackRate; |
| int volume; |
| bool muted; |
| int bufferStatus; |
| bool seekable; |
| bool hasVideo; |
| bool hasAudio; |
| |
| QMediaContent media; |
| QScopedPointer<QIODevice, QWinRTMediaPlayerControlPrivate> stream; |
| QWinRTPlayerRendererControl *videoRenderer; |
| |
| ComPtr<MediaEngineNotify> notifier; |
| ComPtr<MediaEngineSources> sources; |
| ComPtr<MediaEngineByteStream> streamProvider; |
| ComPtr<IMFAttributes> configuration; |
| ComPtr<IMFMediaEngineEx> engine; |
| |
| quint32 resetToken; |
| ComPtr<IMFDXGIDeviceManager> manager; |
| |
| // Automatically delete streams created by the player |
| static inline void cleanup(QIODevice *device) |
| { |
| if (device && device->property(QT_WINRT_MEDIAPLAYER_STREAM_ID).toBool()) |
| device->deleteLater(); |
| } |
| |
| // Allows for deferred cleanup of the engine, which tends to block on shutdown |
| static void cleanup(QWinRTMediaPlayerControlPrivate *d); |
| static void shutdown(QWinRTMediaPlayerControlPrivate *d); |
| }; |
| |
| class MediaEngineNotify : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IMFMediaEngineNotify> |
| { |
| public: |
| MediaEngineNotify(QWinRTMediaPlayerControl *q_ptr, QWinRTMediaPlayerControlPrivate *d_ptr) |
| : q(q_ptr), d(d_ptr) |
| { |
| } |
| |
| HRESULT __stdcall EventNotify(DWORD event, DWORD_PTR param1, DWORD param2) |
| { |
| QMediaPlayer::State newState = d->state; |
| QMediaPlayer::MediaStatus newStatus = d->mediaStatus; |
| |
| switch (event) { |
| // Media change events |
| case MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA: { |
| const bool hasAudio = d->engine->HasAudio(); |
| if (d->hasAudio != hasAudio) { |
| d->hasAudio = hasAudio; |
| emit q->audioAvailableChanged(d->hasAudio); |
| } |
| |
| const bool hasVideo = d->engine->HasVideo(); |
| if (d->hasVideo != hasVideo) { |
| d->hasVideo = hasVideo; |
| emit q->audioAvailableChanged(d->hasAudio); |
| } |
| |
| if (hasVideo) { |
| HRESULT hr; |
| DWORD width, height; |
| hr = d->engine->GetNativeVideoSize(&width, &height); |
| if (FAILED(hr)) |
| break; |
| d->videoRenderer->setSize(QSize(int(width), int(height))); |
| } |
| |
| newStatus = QMediaPlayer::LoadedMedia; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_LOADSTART: |
| case MF_MEDIA_ENGINE_EVENT_PROGRESS: { |
| newStatus = QMediaPlayer::LoadingMedia; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_CANPLAY: |
| d->bufferStatus = 100; // Fired when buffering is not used |
| newStatus = d->state == QMediaPlayer::StoppedState ? QMediaPlayer::LoadedMedia |
| : QMediaPlayer::BufferedMedia; |
| break; |
| case MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED: |
| case MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED: { |
| PROPVARIANT stat; |
| HRESULT hr = d->engine->GetStatistics(MF_MEDIA_ENGINE_STATISTIC_BUFFER_PROGRESS, &stat); |
| if (SUCCEEDED(hr)) { |
| d->bufferStatus = stat.lVal; |
| PropVariantClear(&stat); |
| } |
| newStatus = d->state == QMediaPlayer::StoppedState ? QMediaPlayer::LoadedMedia |
| : (d->bufferStatus == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia); |
| break; |
| } |
| //case MF_MEDIA_ENGINE_EVENT_SUSPEND: ??? |
| //case MF_MEDIA_ENGINE_EVENT_ABORT: ??? |
| case MF_MEDIA_ENGINE_EVENT_EMPTIED: { |
| newState = QMediaPlayer::StoppedState; |
| break; |
| } |
| // Transport controls |
| case MF_MEDIA_ENGINE_EVENT_PLAY: { |
| // If the media is already loaded, the playing event may not occur after stop |
| if (d->mediaStatus != QMediaPlayer::LoadedMedia) |
| break; |
| Q_FALLTHROUGH(); |
| } |
| case MF_MEDIA_ENGINE_EVENT_PLAYING: { |
| newState = QMediaPlayer::PlayingState; |
| newStatus = d->bufferStatus < 100 ? QMediaPlayer::BufferingMedia |
| : QMediaPlayer::BufferedMedia; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_PAUSE: { |
| newState = QMediaPlayer::PausedState; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_STALLED: { |
| newStatus = QMediaPlayer::StalledMedia; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_WAITING: { |
| newStatus = QMediaPlayer::StalledMedia; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_ENDED: { |
| newState = QMediaPlayer::StoppedState; |
| newStatus = QMediaPlayer::EndOfMedia; |
| break; |
| } |
| // Media attributes |
| case MF_MEDIA_ENGINE_EVENT_DURATIONCHANGE: { |
| double duration = d->engine->GetDuration() * 1000; |
| if (!qFuzzyCompare(d->duration, duration)) { |
| d->duration = qint64(duration); |
| emit q->durationChanged(d->duration); |
| } |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: { |
| double position = d->engine->GetCurrentTime() * 1000; |
| if (!qFuzzyCompare(d->position, position)) { |
| d->position = qint64(position); |
| emit q->positionChanged(d->position); |
| } |
| // Stopped state: paused at beginning |
| if (qFuzzyIsNull(position) && d->state == QMediaPlayer::PausedState) |
| newState = QMediaPlayer::StoppedState; |
| break; |
| } |
| case MF_MEDIA_ENGINE_EVENT_RATECHANGE: { |
| double playbackRate = d->engine->GetPlaybackRate(); |
| if (!qFuzzyCompare(d->playbackRate, playbackRate)) { |
| d->playbackRate = playbackRate; |
| emit q->playbackRateChanged(d->playbackRate); |
| } |
| break; |
| } |
| // Error handling |
| case MF_MEDIA_ENGINE_EVENT_ERROR: { |
| newState = QMediaPlayer::StoppedState; |
| newStatus = QMediaPlayer::InvalidMedia; |
| switch (param1) { |
| default: |
| case MF_MEDIA_ENGINE_ERR_NOERROR: |
| newStatus = QMediaPlayer::UnknownMediaStatus; |
| emit q->error(QMediaPlayer::ResourceError, qt_error_string(int(param2))); |
| break; |
| case MF_MEDIA_ENGINE_ERR_ABORTED: |
| if (d->mediaStatus == QMediaPlayer::StalledMedia || d->mediaStatus == QMediaPlayer::BufferingMedia) |
| d->mediaStatus = QMediaPlayer::LoadedMedia; |
| emit q->error(QMediaPlayer::ResourceError, QStringLiteral("The process of fetching the media resource was stopped at the user's request.")); |
| break; |
| case MF_MEDIA_ENGINE_ERR_NETWORK: |
| if (d->mediaStatus == QMediaPlayer::StalledMedia || d->mediaStatus == QMediaPlayer::BufferingMedia) |
| d->mediaStatus = QMediaPlayer::LoadedMedia; |
| emit q->error(QMediaPlayer::NetworkError, QStringLiteral("A network error occurred while fetching the media resource.")); |
| break; |
| case MF_MEDIA_ENGINE_ERR_DECODE: |
| emit q->error(QMediaPlayer::FormatError, QStringLiteral("An error occurred while decoding the media resource.")); |
| break; |
| case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED: |
| emit q->error(QMediaPlayer::FormatError, QStringLiteral("The media resource is not supported.")); |
| break; |
| case MF_MEDIA_ENGINE_ERR_ENCRYPTED: |
| emit q->error(QMediaPlayer::FormatError, QStringLiteral("An error occurred while encrypting the media resource.")); |
| break; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (d->videoRenderer) |
| d->videoRenderer->setActive(d->state == QMediaPlayer::PlayingState && |
| d->videoRenderer->size().isValid()); |
| |
| const QMediaPlayer::MediaStatus oldMediaStatus = d->mediaStatus; |
| const QMediaPlayer::State oldState = d->state; |
| d->mediaStatus = newStatus; |
| d->state = newState; |
| |
| if (d->mediaStatus != oldMediaStatus) |
| emit q->mediaStatusChanged(d->mediaStatus); |
| |
| if (d->state != oldState) |
| emit q->stateChanged(d->state); |
| |
| return S_OK; |
| } |
| |
| private: |
| QWinRTMediaPlayerControl *q; |
| QWinRTMediaPlayerControlPrivate *d; |
| }; |
| |
| class MediaEngineSources : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IMFMediaEngineSrcElements> |
| { |
| public: |
| MediaEngineSources(QWinRTMediaPlayerControlPrivate *d_ptr) |
| : d(d_ptr) |
| { |
| } |
| |
| DWORD __stdcall GetLength() |
| { |
| return DWORD(d->media.resources().length()); |
| } |
| |
| HRESULT __stdcall GetURL(DWORD index, BSTR *url) |
| { |
| const QString resourceUrl = d->media.resources().value(int(index)).url().toString(); |
| *url = SysAllocString((const OLECHAR *)resourceUrl.utf16()); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetType(DWORD index, BSTR *type) |
| { |
| const QString resourceType = d->media.resources().value(int(index)).mimeType(); |
| *type = SysAllocString((const OLECHAR *)resourceType.utf16()); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetMedia(DWORD index, BSTR *media) |
| { |
| Q_UNUSED(index); |
| *media = nullptr; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall AddElement(BSTR url, BSTR type, BSTR media) |
| { |
| Q_UNUSED(url); |
| Q_UNUSED(type); |
| Q_UNUSED(media); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall RemoveAllElements() |
| { |
| return E_NOTIMPL; |
| } |
| |
| private: |
| QWinRTMediaPlayerControlPrivate *d; |
| }; |
| |
| class MediaEngineReadResult : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IUnknown> |
| { |
| public: |
| MediaEngineReadResult(BYTE *bytes, ULONG maxLength) |
| : bytes(bytes), maxLength(maxLength) { } |
| |
| void read(QIODevice *device) |
| { |
| bytesRead = ULONG(device->read(reinterpret_cast<char *>(bytes), maxLength)); |
| } |
| |
| BYTE *bytes; |
| ULONG maxLength; |
| ULONG bytesRead; |
| }; |
| |
| class MediaEngineByteStream : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IMFByteStream> |
| { |
| public: |
| MediaEngineByteStream(QWinRTMediaPlayerControlPrivate *d_ptr) |
| : d(d_ptr) |
| { |
| } |
| |
| HRESULT __stdcall GetCapabilities(DWORD *capabilities) |
| { |
| *capabilities |= MFBYTESTREAM_IS_READABLE; |
| if (!d->stream->isSequential()) |
| *capabilities |= MFBYTESTREAM_IS_SEEKABLE; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetLength(QWORD *length) |
| { |
| *length = QWORD(d->stream->size()); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall SetLength(QWORD length) |
| { |
| Q_UNUSED(length); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall GetCurrentPosition(QWORD *position) |
| { |
| *position = QWORD(d->stream->pos()); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall SetCurrentPosition(QWORD position) |
| { |
| const qint64 pos = qint64(position); |
| if (pos >= d->stream->size()) { |
| // MSDN states we should return E_INVALIDARG, but that immediately |
| // stops playback and does not play remaining buffers in the queue. |
| // For some formats this can cause losing up to 5 seconds of the |
| // end of the stream. |
| return S_FALSE; |
| } |
| |
| const bool ok = d->stream->seek(pos); |
| return ok ? S_OK : S_FALSE; |
| } |
| |
| HRESULT __stdcall IsEndOfStream(BOOL *endOfStream) |
| { |
| *endOfStream = d->stream->atEnd() ? TRUE : FALSE; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall Read(BYTE *bytes, ULONG maxlen, ULONG *bytesRead) |
| { |
| *bytesRead = ULONG(d->stream->read(reinterpret_cast<char *>(bytes), maxlen)); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall BeginRead(BYTE *bytes, ULONG maxLength, IMFAsyncCallback *callback, IUnknown *state) |
| { |
| ComPtr<MediaEngineReadResult> readResult = Make<MediaEngineReadResult>(bytes, maxLength); |
| HRESULT hr; |
| hr = MFCreateAsyncResult(readResult.Get(), callback, state, &asyncResult); |
| RETURN_HR_IF_FAILED("Failed to create read callback result"); |
| finishRead(); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall EndRead(IMFAsyncResult *result, ULONG *bytesRead) |
| { |
| HRESULT hr; |
| ComPtr<MediaEngineReadResult> readResult; |
| hr = result->GetObject(&readResult); |
| RETURN_HR_IF_FAILED("Failed to get read result"); |
| |
| *bytesRead = readResult->bytesRead; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall Write(const BYTE *bytes, ULONG maxlen, ULONG *bytesWritten) |
| { |
| Q_UNUSED(bytes); |
| Q_UNUSED(maxlen); |
| Q_UNUSED(bytesWritten); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall BeginWrite(const BYTE *bytes, ULONG maxlen, IMFAsyncCallback *callback, IUnknown *state) |
| { |
| Q_UNUSED(bytes); |
| Q_UNUSED(maxlen); |
| Q_UNUSED(callback); |
| Q_UNUSED(state); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall EndWrite(IMFAsyncResult *result, ULONG *bytesWritten) |
| { |
| Q_UNUSED(result); |
| Q_UNUSED(bytesWritten); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall Seek(MFBYTESTREAM_SEEK_ORIGIN origin, LONGLONG offset, DWORD flags, QWORD *position) |
| { |
| Q_UNUSED(flags); |
| |
| qint64 pos = offset; |
| if (origin == msoCurrent) |
| pos += d->stream->pos(); |
| |
| const bool ok = d->stream->seek(pos); |
| *position = QWORD(d->stream->pos()); |
| |
| return ok ? S_OK : E_FAIL; |
| } |
| |
| HRESULT __stdcall Flush() |
| { |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall Close() |
| { |
| if (asyncResult) |
| finishRead(); |
| |
| if (d->stream->property(QT_WINRT_MEDIAPLAYER_STREAM_ID).toBool()) |
| d->stream->close(); |
| |
| return S_OK; |
| } |
| |
| void finishRead() |
| { |
| if (!asyncResult) |
| return; |
| |
| HRESULT hr; |
| ComPtr<MediaEngineReadResult> readResult; |
| hr = asyncResult->GetObject(&readResult); |
| RETURN_VOID_IF_FAILED("Failed to get read result object"); |
| readResult->read(d->stream.data()); |
| hr = MFInvokeCallback(asyncResult.Get()); |
| RETURN_VOID_IF_FAILED("Failed to invoke read callback"); |
| asyncResult.Reset(); |
| } |
| |
| private: |
| QWinRTMediaPlayerControlPrivate *d; |
| |
| ComPtr<IMFAsyncResult> asyncResult; |
| }; |
| |
| void QWinRTMediaPlayerControlPrivate::cleanup(QWinRTMediaPlayerControlPrivate *d) |
| { |
| QtConcurrent::run(&QWinRTMediaPlayerControlPrivate::shutdown, d); |
| } |
| |
| void QWinRTMediaPlayerControlPrivate::shutdown(QWinRTMediaPlayerControlPrivate *d) |
| { |
| d->engine->Shutdown(); |
| delete d; |
| } |
| |
| QWinRTMediaPlayerControl::QWinRTMediaPlayerControl(IMFMediaEngineClassFactory *factory, QObject *parent) |
| : QMediaPlayerControl(parent), d_ptr(new QWinRTMediaPlayerControlPrivate) |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| d->state = QMediaPlayer::StoppedState; |
| d->mediaStatus = QMediaPlayer::NoMedia; |
| d->duration = 0; |
| d->position = 0; |
| d->playbackRate = 1.0; |
| d->volume = 100; |
| d->bufferStatus = 0; |
| d->muted = false; |
| d->seekable = false; |
| d->hasAudio = false; |
| d->hasVideo = false; |
| d->videoRenderer = nullptr; |
| d->notifier = Make<MediaEngineNotify>(this, d); |
| |
| HRESULT hr; |
| hr = MFCreateDXGIDeviceManager(&d->resetToken, &d->manager); |
| RETURN_VOID_IF_FAILED("Failed to create DXGI device manager"); |
| |
| hr = MFCreateAttributes(&d->configuration, 1); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->configuration->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, d->notifier.Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->configuration->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, d->manager.Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->configuration->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| ComPtr<IMFMediaEngine> engine; |
| hr = factory->CreateInstance(0, d->configuration.Get(), &engine); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = engine.As(&d->engine); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| hr = d->engine->SetVolume(1.0); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| d->sources = Make<MediaEngineSources>(d); |
| hr = d->engine->SetSourceElements(d->sources.Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| d->streamProvider = Make<MediaEngineByteStream>(d); |
| } |
| |
| QMediaPlayer::State QWinRTMediaPlayerControl::state() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->state; |
| } |
| |
| QMediaPlayer::MediaStatus QWinRTMediaPlayerControl::mediaStatus() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->mediaStatus; |
| } |
| |
| qint64 QWinRTMediaPlayerControl::duration() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->duration; |
| } |
| |
| qint64 QWinRTMediaPlayerControl::position() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->position; |
| } |
| |
| void QWinRTMediaPlayerControl::setPosition(qint64 position) |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (d->position == position) |
| return; |
| |
| HRESULT hr; |
| hr = d->engine->SetCurrentTime(double(position)/1000); |
| RETURN_VOID_IF_FAILED("Failed to seek to new position"); |
| |
| d->position = position; |
| emit positionChanged(d->position); |
| |
| if (d->mediaStatus == QMediaPlayer::EndOfMedia) { |
| d->mediaStatus = QMediaPlayer::LoadedMedia; |
| emit mediaStatusChanged(d->mediaStatus); |
| } |
| } |
| |
| int QWinRTMediaPlayerControl::volume() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->volume; |
| } |
| |
| void QWinRTMediaPlayerControl::setVolume(int volume) |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (d->volume == volume) |
| return; |
| |
| HRESULT hr; |
| hr = d->engine->SetVolume(double(volume)/100); |
| RETURN_VOID_IF_FAILED("Failed to set volume"); |
| |
| d->volume = volume; |
| emit volumeChanged(d->volume); |
| } |
| |
| bool QWinRTMediaPlayerControl::isMuted() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->muted; |
| } |
| |
| void QWinRTMediaPlayerControl::setMuted(bool muted) |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (d->muted == muted) |
| return; |
| |
| HRESULT hr; |
| hr = d->engine->SetMuted(muted); |
| RETURN_VOID_IF_FAILED("Failed to mute volume"); |
| |
| d->muted = muted; |
| emit mutedChanged(d->muted); |
| } |
| |
| int QWinRTMediaPlayerControl::bufferStatus() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->bufferStatus; |
| } |
| |
| bool QWinRTMediaPlayerControl::isAudioAvailable() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->hasAudio; |
| } |
| |
| bool QWinRTMediaPlayerControl::isVideoAvailable() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->hasVideo; |
| } |
| |
| bool QWinRTMediaPlayerControl::isSeekable() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->seekable; |
| } |
| |
| QMediaTimeRange QWinRTMediaPlayerControl::availablePlaybackRanges() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return QMediaTimeRange(0, d->duration); |
| } |
| |
| qreal QWinRTMediaPlayerControl::playbackRate() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->playbackRate; |
| } |
| |
| void QWinRTMediaPlayerControl::setPlaybackRate(qreal rate) |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (qFuzzyCompare(d->playbackRate, rate)) |
| return; |
| |
| HRESULT hr; |
| hr = d->engine->SetPlaybackRate(rate); |
| RETURN_VOID_IF_FAILED("Failed to set playback rate"); |
| } |
| |
| QMediaContent QWinRTMediaPlayerControl::media() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->media; |
| } |
| |
| const QIODevice *QWinRTMediaPlayerControl::mediaStream() const |
| { |
| Q_D(const QWinRTMediaPlayerControl); |
| return d->stream.data(); |
| } |
| |
| void QWinRTMediaPlayerControl::setMedia(const QMediaContent &media, QIODevice *stream) |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (d->media == media && d->stream.data() == stream) |
| return; |
| |
| d->media = media; |
| d->stream.reset(stream); |
| if (d->hasAudio != false) { |
| d->hasAudio = false; |
| emit audioAvailableChanged(d->hasAudio); |
| } |
| if (d->hasVideo != false) { |
| d->hasVideo = false; |
| emit videoAvailableChanged(d->hasVideo); |
| } |
| if (d->seekable != false) { |
| d->seekable = false; |
| emit seekableChanged(d->seekable); |
| } |
| if (d->bufferStatus != 0) { |
| d->bufferStatus = 0; |
| emit bufferStatusChanged(d->bufferStatus); |
| } |
| if (d->position != 0) { |
| d->position = 0; |
| emit positionChanged(d->position); |
| } |
| if (d->duration != 0) { |
| d->duration = 0; |
| emit durationChanged(d->duration); |
| } |
| QMediaPlayer::MediaStatus mediaStatus = media.isNull() ? QMediaPlayer::NoMedia |
| : QMediaPlayer::LoadingMedia; |
| if (d->mediaStatus != mediaStatus) { |
| d->mediaStatus = mediaStatus; |
| emit mediaStatusChanged(d->mediaStatus); |
| } |
| emit mediaChanged(media); |
| |
| QString urlString = media.request().url().toString(); |
| if (!d->stream) { |
| // If we can read the file via Qt, use the byte stream approach |
| if (media.request().url().isLocalFile()) { |
| urlString = media.request().url().toLocalFile(); |
| QScopedPointer<QFile> file(new QFile(urlString)); |
| if (file->open(QFile::ReadOnly)) { |
| file->setProperty(QT_WINRT_MEDIAPLAYER_STREAM_ID, true); |
| d->stream.reset(file.take()); |
| } |
| } |
| } |
| |
| HRESULT hr; |
| if (d->stream) { |
| hr = d->engine->SetSourceFromByteStream(d->streamProvider.Get(), |
| reinterpret_cast<BSTR>(urlString.data())); |
| if (FAILED(hr)) { |
| emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); |
| return; |
| } |
| if (d->videoRenderer) |
| d->videoRenderer->ensureReady(); |
| return; |
| } |
| |
| // Let Windows handle all other URLs |
| hr = d->engine->SetSource(nullptr); // Resets the byte stream |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->engine->Load(); |
| if (FAILED(hr)) |
| emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); |
| |
| if (d->media.isNull() && d->stream.isNull()) |
| return; |
| |
| // Resume play/pause/stop |
| switch (d->state) { |
| case QMediaPlayer::StoppedState: |
| stop(); |
| break; |
| case QMediaPlayer::PlayingState: |
| play(); |
| break; |
| case QMediaPlayer::PausedState: |
| pause(); |
| break; |
| } |
| } |
| |
| void QWinRTMediaPlayerControl::play() |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (d->state != QMediaPlayer::PlayingState) { |
| d->state = QMediaPlayer::PlayingState; |
| emit stateChanged(d->state); |
| } |
| |
| if (d->media.isNull() && d->stream.isNull()) |
| return; |
| |
| if (d->videoRenderer) |
| d->videoRenderer->ensureReady(); |
| |
| HRESULT hr = d->engine->Play(); |
| if (FAILED(hr)) { |
| emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); |
| return; |
| } |
| } |
| |
| void QWinRTMediaPlayerControl::pause() |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (d->state != QMediaPlayer::PausedState) { |
| d->state = QMediaPlayer::PausedState; |
| emit stateChanged(d->state); |
| } |
| |
| if (d->media.isNull() && d->stream.isNull()) |
| return; |
| |
| HRESULT hr; |
| hr = d->engine->Pause(); |
| if (FAILED(hr)) { |
| emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); |
| return; |
| } |
| } |
| |
| void QWinRTMediaPlayerControl::stop() |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| const QMediaPlayer::MediaStatus oldMediaStatus = d->mediaStatus; |
| const QMediaPlayer::State oldState = d->state; |
| |
| d->state = QMediaPlayer::StoppedState; |
| |
| if (d->mediaStatus == QMediaPlayer::BufferedMedia |
| || d->mediaStatus == QMediaPlayer::BufferingMedia) { |
| d->mediaStatus = QMediaPlayer::LoadedMedia; |
| } |
| |
| if (d->mediaStatus != oldMediaStatus) |
| emit mediaStatusChanged(d->mediaStatus); |
| |
| if (d->state != oldState) |
| emit stateChanged(d->state); |
| |
| if (d->media.isNull() && d->stream.isNull()) |
| return; |
| |
| setPosition(0); |
| |
| HRESULT hr; |
| hr = d->engine->Pause(); |
| if (FAILED(hr)) { |
| emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); |
| return; |
| } |
| } |
| |
| QVideoRendererControl *QWinRTMediaPlayerControl::videoRendererControl() |
| { |
| Q_D(QWinRTMediaPlayerControl); |
| |
| if (!d->videoRenderer) { |
| d->videoRenderer = new QWinRTPlayerRendererControl(d->engine.Get(), d->manager.Get(), |
| d->resetToken, this); |
| } |
| |
| return d->videoRenderer; |
| } |
| |
| QT_END_NAMESPACE |