summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/windows/player
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/windows/player')
-rw-r--r--src/plugins/multimedia/windows/player/mfactivate.cpp17
-rw-r--r--src/plugins/multimedia/windows/player/mfactivate_p.h202
-rw-r--r--src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp55
-rw-r--r--src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h38
-rw-r--r--src/plugins/multimedia/windows/player/mfplayercontrol.cpp306
-rw-r--r--src/plugins/multimedia/windows/player/mfplayercontrol_p.h103
-rw-r--r--src/plugins/multimedia/windows/player/mfplayersession.cpp1736
-rw-r--r--src/plugins/multimedia/windows/player/mfplayersession_p.h240
-rw-r--r--src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp152
-rw-r--r--src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h47
10 files changed, 2896 insertions, 0 deletions
diff --git a/src/plugins/multimedia/windows/player/mfactivate.cpp b/src/plugins/multimedia/windows/player/mfactivate.cpp
new file mode 100644
index 000000000..644c96529
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfactivate.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mfactivate_p.h"
+
+#include <mfapi.h>
+
+MFAbstractActivate::MFAbstractActivate()
+{
+ MFCreateAttributes(&m_attributes, 0);
+}
+
+MFAbstractActivate::~MFAbstractActivate()
+{
+ if (m_attributes)
+ m_attributes->Release();
+}
diff --git a/src/plugins/multimedia/windows/player/mfactivate_p.h b/src/plugins/multimedia/windows/player/mfactivate_p.h
new file mode 100644
index 000000000..efe75474b
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfactivate_p.h
@@ -0,0 +1,202 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MFACTIVATE_H
+#define MFACTIVATE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfidl.h>
+#include <private/qcomobject_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtPrivate {
+
+template <>
+struct QComObjectTraits<IMFActivate>
+{
+ static constexpr bool isGuidOf(REFIID riid) noexcept
+ {
+ return QComObjectTraits<IMFActivate, IMFAttributes>::isGuidOf(riid);
+ }
+};
+
+} // namespace QtPrivate
+
+class MFAbstractActivate : public QComObject<IMFActivate>
+{
+public:
+ explicit MFAbstractActivate();
+
+ //from IMFAttributes
+ STDMETHODIMP GetItem(REFGUID guidKey, PROPVARIANT *pValue) override
+ {
+ return m_attributes->GetItem(guidKey, pValue);
+ }
+
+ STDMETHODIMP GetItemType(REFGUID guidKey, MF_ATTRIBUTE_TYPE *pType) override
+ {
+ return m_attributes->GetItemType(guidKey, pType);
+ }
+
+ STDMETHODIMP CompareItem(REFGUID guidKey, REFPROPVARIANT Value, BOOL *pbResult) override
+ {
+ return m_attributes->CompareItem(guidKey, Value, pbResult);
+ }
+
+ STDMETHODIMP Compare(IMFAttributes *pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL *pbResult) override
+ {
+ return m_attributes->Compare(pTheirs, MatchType, pbResult);
+ }
+
+ STDMETHODIMP GetUINT32(REFGUID guidKey, UINT32 *punValue) override
+ {
+ return m_attributes->GetUINT32(guidKey, punValue);
+ }
+
+ STDMETHODIMP GetUINT64(REFGUID guidKey, UINT64 *punValue) override
+ {
+ return m_attributes->GetUINT64(guidKey, punValue);
+ }
+
+ STDMETHODIMP GetDouble(REFGUID guidKey, double *pfValue) override
+ {
+ return m_attributes->GetDouble(guidKey, pfValue);
+ }
+
+ STDMETHODIMP GetGUID(REFGUID guidKey, GUID *pguidValue) override
+ {
+ return m_attributes->GetGUID(guidKey, pguidValue);
+ }
+
+ STDMETHODIMP GetStringLength(REFGUID guidKey, UINT32 *pcchLength) override
+ {
+ return m_attributes->GetStringLength(guidKey, pcchLength);
+ }
+
+ STDMETHODIMP GetString(REFGUID guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32 *pcchLength) override
+ {
+ return m_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength);
+ }
+
+ STDMETHODIMP GetAllocatedString(REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength) override
+ {
+ return m_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength);
+ }
+
+ STDMETHODIMP GetBlobSize(REFGUID guidKey, UINT32 *pcbBlobSize) override
+ {
+ return m_attributes->GetBlobSize(guidKey, pcbBlobSize);
+ }
+
+ STDMETHODIMP GetBlob(REFGUID guidKey, UINT8 *pBuf, UINT32 cbBufSize, UINT32 *pcbBlobSize) override
+ {
+ return m_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize);
+ }
+
+ STDMETHODIMP GetAllocatedBlob(REFGUID guidKey, UINT8 **ppBuf, UINT32 *pcbSize) override
+ {
+ return m_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize);
+ }
+
+ STDMETHODIMP GetUnknown(REFGUID guidKey, REFIID riid, LPVOID *ppv) override
+ {
+ return m_attributes->GetUnknown(guidKey, riid, ppv);
+ }
+
+ STDMETHODIMP SetItem(REFGUID guidKey, REFPROPVARIANT Value) override
+ {
+ return m_attributes->SetItem(guidKey, Value);
+ }
+
+ STDMETHODIMP DeleteItem(REFGUID guidKey) override
+ {
+ return m_attributes->DeleteItem(guidKey);
+ }
+
+ STDMETHODIMP DeleteAllItems() override
+ {
+ return m_attributes->DeleteAllItems();
+ }
+
+ STDMETHODIMP SetUINT32(REFGUID guidKey, UINT32 unValue) override
+ {
+ return m_attributes->SetUINT32(guidKey, unValue);
+ }
+
+ STDMETHODIMP SetUINT64(REFGUID guidKey, UINT64 unValue) override
+ {
+ return m_attributes->SetUINT64(guidKey, unValue);
+ }
+
+ STDMETHODIMP SetDouble(REFGUID guidKey, double fValue) override
+ {
+ return m_attributes->SetDouble(guidKey, fValue);
+ }
+
+ STDMETHODIMP SetGUID(REFGUID guidKey, REFGUID guidValue) override
+ {
+ return m_attributes->SetGUID(guidKey, guidValue);
+ }
+
+ STDMETHODIMP SetString(REFGUID guidKey, LPCWSTR wszValue) override
+ {
+ return m_attributes->SetString(guidKey, wszValue);
+ }
+
+ STDMETHODIMP SetBlob(REFGUID guidKey, const UINT8 *pBuf, UINT32 cbBufSize) override
+ {
+ return m_attributes->SetBlob(guidKey, pBuf, cbBufSize);
+ }
+
+ STDMETHODIMP SetUnknown(REFGUID guidKey, IUnknown *pUnknown) override
+ {
+ return m_attributes->SetUnknown(guidKey, pUnknown);
+ }
+
+ STDMETHODIMP LockStore() override
+ {
+ return m_attributes->LockStore();
+ }
+
+ STDMETHODIMP UnlockStore() override
+ {
+ return m_attributes->UnlockStore();
+ }
+
+ STDMETHODIMP GetCount(UINT32 *pcItems) override
+ {
+ return m_attributes->GetCount(pcItems);
+ }
+
+ STDMETHODIMP GetItemByIndex(UINT32 unIndex, GUID *pguidKey, PROPVARIANT *pValue) override
+ {
+ return m_attributes->GetItemByIndex(unIndex, pguidKey, pValue);
+ }
+
+ STDMETHODIMP CopyAllItems(IMFAttributes *pDest) override
+ {
+ return m_attributes->CopyAllItems(pDest);
+ }
+
+protected:
+ // Destructor is not public. Caller should call Release.
+ ~MFAbstractActivate() override;
+
+private:
+ IMFAttributes *m_attributes = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // MFACTIVATE_H
diff --git a/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp
new file mode 100644
index 000000000..109f7964b
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp
@@ -0,0 +1,55 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mfevrvideowindowcontrol_p.h"
+
+#include <qdebug.h>
+
+MFEvrVideoWindowControl::MFEvrVideoWindowControl(QVideoSink *parent)
+ : EvrVideoWindowControl(parent)
+ , m_currentActivate(NULL)
+ , m_evrSink(NULL)
+{
+}
+
+MFEvrVideoWindowControl::~MFEvrVideoWindowControl()
+{
+ clear();
+}
+
+void MFEvrVideoWindowControl::clear()
+{
+ setEvr(NULL);
+
+ if (m_evrSink)
+ m_evrSink->Release();
+ if (m_currentActivate) {
+ m_currentActivate->ShutdownObject();
+ m_currentActivate->Release();
+ }
+ m_evrSink = NULL;
+ m_currentActivate = NULL;
+}
+
+IMFActivate* MFEvrVideoWindowControl::createActivate()
+{
+ clear();
+
+ if (FAILED(MFCreateVideoRendererActivate(0, &m_currentActivate))) {
+ qWarning() << "Failed to create evr video renderer activate!";
+ return NULL;
+ }
+ if (FAILED(m_currentActivate->ActivateObject(IID_IMFMediaSink, (LPVOID*)(&m_evrSink)))) {
+ qWarning() << "Failed to activate evr media sink!";
+ return NULL;
+ }
+ if (!setEvr(m_evrSink))
+ return NULL;
+
+ return m_currentActivate;
+}
+
+void MFEvrVideoWindowControl::releaseActivate()
+{
+ clear();
+}
diff --git a/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h
new file mode 100644
index 000000000..1ac90e8ce
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MFEVRVIDEOWINDOWCONTROL_H
+#define MFEVRVIDEOWINDOWCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "evrvideowindowcontrol_p.h"
+
+QT_USE_NAMESPACE
+
+class MFEvrVideoWindowControl : public EvrVideoWindowControl
+{
+public:
+ MFEvrVideoWindowControl(QVideoSink *parent = 0);
+ ~MFEvrVideoWindowControl();
+
+ IMFActivate* createActivate();
+ void releaseActivate();
+
+private:
+ void clear();
+
+ IMFActivate *m_currentActivate;
+ IMFMediaSink *m_evrSink;
+};
+
+#endif // MFEVRVIDEOWINDOWCONTROL_H
diff --git a/src/plugins/multimedia/windows/player/mfplayercontrol.cpp b/src/plugins/multimedia/windows/player/mfplayercontrol.cpp
new file mode 100644
index 000000000..ae0022773
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfplayercontrol.cpp
@@ -0,0 +1,306 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mfplayercontrol_p.h"
+#include "mfplayersession_p.h"
+#include "mfvideorenderercontrol_p.h"
+#include <qdebug.h>
+
+//#define DEBUG_MEDIAFOUNDATION
+
+MFPlayerControl::MFPlayerControl(QMediaPlayer *player)
+ : QPlatformMediaPlayer(player)
+ , m_state(QMediaPlayer::StoppedState)
+ , m_stateDirty(false)
+ , m_videoAvailable(false)
+ , m_audioAvailable(false)
+ , m_duration(0)
+ , m_seekable(false)
+{
+ m_session = new MFPlayerSession(this);
+}
+
+MFPlayerControl::~MFPlayerControl()
+{
+ m_session->close();
+ m_session->setPlayerControl(nullptr);
+ m_session->Release();
+}
+
+void MFPlayerControl::setMedia(const QUrl &media, QIODevice *stream)
+{
+ if (m_state != QMediaPlayer::StoppedState) {
+ changeState(QMediaPlayer::StoppedState);
+ m_session->stop(true);
+ refreshState();
+ }
+
+ m_media = media;
+ m_stream = stream;
+ resetAudioVideoAvailable();
+ handleDurationUpdate(0);
+ handleSeekableUpdate(false);
+ m_session->load(media, stream);
+}
+
+void MFPlayerControl::play()
+{
+ if (m_state == QMediaPlayer::PlayingState)
+ return;
+ resetCurrentLoop();
+ if (QMediaPlayer::InvalidMedia == m_session->status())
+ m_session->load(m_media, m_stream);
+
+ switch (m_session->status()) {
+ case QMediaPlayer::NoMedia:
+ case QMediaPlayer::InvalidMedia:
+ return;
+ case QMediaPlayer::LoadedMedia:
+ case QMediaPlayer::BufferingMedia:
+ case QMediaPlayer::BufferedMedia:
+ case QMediaPlayer::EndOfMedia:
+ changeState(QMediaPlayer::PlayingState);
+ m_session->start();
+ break;
+ default: //Loading/Stalled
+ changeState(QMediaPlayer::PlayingState);
+ break;
+ }
+ refreshState();
+}
+
+void MFPlayerControl::pause()
+{
+ if (m_state == QMediaPlayer::PausedState)
+ return;
+
+ if (m_session->status() == QMediaPlayer::NoMedia ||
+ m_session->status() == QMediaPlayer::InvalidMedia)
+ return;
+
+ changeState(QMediaPlayer::PausedState);
+ m_session->pause();
+ refreshState();
+}
+
+void MFPlayerControl::stop()
+{
+ if (m_state == QMediaPlayer::StoppedState)
+ return;
+ changeState(QMediaPlayer::StoppedState);
+ m_session->stop();
+ refreshState();
+}
+
+QMediaMetaData MFPlayerControl::metaData() const
+{
+ return m_session->metaData();
+}
+
+void MFPlayerControl::setAudioOutput(QPlatformAudioOutput *output)
+{
+ m_session->setAudioOutput(output);
+}
+
+void MFPlayerControl::setVideoSink(QVideoSink *sink)
+{
+ m_session->setVideoSink(sink);
+}
+
+void MFPlayerControl::changeState(QMediaPlayer::PlaybackState state)
+{
+ if (m_state == state)
+ return;
+ m_state = state;
+ m_stateDirty = true;
+}
+
+void MFPlayerControl::refreshState()
+{
+ if (!m_stateDirty)
+ return;
+ m_stateDirty = false;
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MFPlayerControl::emit stateChanged" << m_state;
+#endif
+ stateChanged(m_state);
+}
+
+void MFPlayerControl::handleStatusChanged()
+{
+ QMediaPlayer::MediaStatus status = m_session->status();
+ switch (status) {
+ case QMediaPlayer::EndOfMedia:
+ if (doLoop()) {
+ setPosition(0);
+ m_session->start();
+ } else {
+ changeState(QMediaPlayer::StoppedState);
+ }
+ break;
+ case QMediaPlayer::InvalidMedia:
+ break;
+ case QMediaPlayer::LoadedMedia:
+ case QMediaPlayer::BufferingMedia:
+ case QMediaPlayer::BufferedMedia:
+ if (m_state == QMediaPlayer::PlayingState)
+ m_session->start();
+ break;
+ default:
+ break;
+ }
+ mediaStatusChanged(m_session->status());
+ refreshState();
+}
+
+void MFPlayerControl::handleTracksChanged()
+{
+ tracksChanged();
+}
+
+void MFPlayerControl::handleVideoAvailable()
+{
+ if (m_videoAvailable)
+ return;
+ m_videoAvailable = true;
+ videoAvailableChanged(m_videoAvailable);
+}
+
+void MFPlayerControl::handleAudioAvailable()
+{
+ if (m_audioAvailable)
+ return;
+ m_audioAvailable = true;
+ audioAvailableChanged(m_audioAvailable);
+}
+
+void MFPlayerControl::resetAudioVideoAvailable()
+{
+ bool videoDirty = false;
+ if (m_videoAvailable) {
+ m_videoAvailable = false;
+ videoDirty = true;
+ }
+ if (m_audioAvailable) {
+ m_audioAvailable = false;
+ audioAvailableChanged(m_audioAvailable);
+ }
+ if (videoDirty)
+ videoAvailableChanged(m_videoAvailable);
+}
+
+void MFPlayerControl::handleDurationUpdate(qint64 duration)
+{
+ if (m_duration == duration)
+ return;
+ m_duration = duration;
+ durationChanged(m_duration);
+}
+
+void MFPlayerControl::handleSeekableUpdate(bool seekable)
+{
+ if (m_seekable == seekable)
+ return;
+ m_seekable = seekable;
+ seekableChanged(m_seekable);
+}
+
+QMediaPlayer::PlaybackState MFPlayerControl::state() const
+{
+ return m_state;
+}
+
+QMediaPlayer::MediaStatus MFPlayerControl::mediaStatus() const
+{
+ return m_session->status();
+}
+
+qint64 MFPlayerControl::duration() const
+{
+ return m_duration;
+}
+
+qint64 MFPlayerControl::position() const
+{
+ return m_session->position();
+}
+
+void MFPlayerControl::setPosition(qint64 position)
+{
+ if (!m_seekable || position == m_session->position())
+ return;
+ m_session->setPosition(position);
+}
+
+float MFPlayerControl::bufferProgress() const
+{
+ return m_session->bufferProgress() / 100.;
+}
+
+bool MFPlayerControl::isAudioAvailable() const
+{
+ return m_audioAvailable;
+}
+
+bool MFPlayerControl::isVideoAvailable() const
+{
+ return m_videoAvailable;
+}
+
+bool MFPlayerControl::isSeekable() const
+{
+ return m_seekable;
+}
+
+QMediaTimeRange MFPlayerControl::availablePlaybackRanges() const
+{
+ return m_session->availablePlaybackRanges();
+}
+
+qreal MFPlayerControl::playbackRate() const
+{
+ return m_session->playbackRate();
+}
+
+void MFPlayerControl::setPlaybackRate(qreal rate)
+{
+ m_session->setPlaybackRate(rate);
+}
+
+QUrl MFPlayerControl::media() const
+{
+ return m_media;
+}
+
+const QIODevice* MFPlayerControl::mediaStream() const
+{
+ return m_stream;
+}
+
+void MFPlayerControl::handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal)
+{
+ if (isFatal)
+ stop();
+ error(int(errorCode), errorString);
+}
+
+void MFPlayerControl::setActiveTrack(TrackType type, int index)
+{
+ m_session->setActiveTrack(type, index);
+}
+
+int MFPlayerControl::activeTrack(TrackType type)
+{
+ return m_session->activeTrack(type);
+}
+
+int MFPlayerControl::trackCount(TrackType type)
+{
+ return m_session->trackCount(type);
+}
+
+QMediaMetaData MFPlayerControl::trackMetaData(TrackType type, int trackNumber)
+{
+ return m_session->trackMetaData(type, trackNumber);
+}
+
diff --git a/src/plugins/multimedia/windows/player/mfplayercontrol_p.h b/src/plugins/multimedia/windows/player/mfplayercontrol_p.h
new file mode 100644
index 000000000..db863afaa
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfplayercontrol_p.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MFPLAYERCONTROL_H
+#define MFPLAYERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qurl.h"
+#include "private/qplatformmediaplayer_p.h"
+
+#include <QtCore/qcoreevent.h>
+
+QT_BEGIN_NAMESPACE
+
+class MFPlayerSession;
+
+class MFPlayerControl : public QPlatformMediaPlayer
+{
+public:
+ MFPlayerControl(QMediaPlayer *player);
+ ~MFPlayerControl();
+
+ QMediaPlayer::PlaybackState state() const override;
+
+ QMediaPlayer::MediaStatus mediaStatus() const override;
+
+ qint64 duration() const override;
+
+ qint64 position() const override;
+ void setPosition(qint64 position) override;
+
+ float bufferProgress() const override;
+
+ bool isAudioAvailable() const override;
+ bool isVideoAvailable() const override;
+
+ bool isSeekable() const override;
+
+ QMediaTimeRange availablePlaybackRanges() const override;
+
+ qreal playbackRate() const override;
+ void setPlaybackRate(qreal rate) override;
+
+ QUrl media() const override;
+ const QIODevice *mediaStream() const override;
+ void setMedia(const QUrl &media, QIODevice *stream) override;
+
+ void play() override;
+ void pause() override;
+ void stop() override;
+
+ bool streamPlaybackSupported() const override { return true; }
+
+ QMediaMetaData metaData() const override;
+
+ void setAudioOutput(QPlatformAudioOutput *output) override;
+
+ void setVideoSink(QVideoSink *sink) override;
+
+ void setActiveTrack(TrackType type, int index) override;
+ int activeTrack(TrackType type) override;
+ int trackCount(TrackType type) override;
+ QMediaMetaData trackMetaData(TrackType type, int trackNumber) override;
+
+ void handleStatusChanged();
+ void handleTracksChanged();
+ void handleVideoAvailable();
+ void handleAudioAvailable();
+ void handleDurationUpdate(qint64 duration);
+ void handleSeekableUpdate(bool seekable);
+ void handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal);
+
+private:
+ void changeState(QMediaPlayer::PlaybackState state);
+ void resetAudioVideoAvailable();
+ void refreshState();
+
+ QMediaPlayer::PlaybackState m_state;
+ bool m_stateDirty;
+
+ bool m_videoAvailable;
+ bool m_audioAvailable;
+ qint64 m_duration;
+ bool m_seekable;
+
+ QIODevice *m_stream;
+ QUrl m_media;
+ MFPlayerSession *m_session;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/windows/player/mfplayersession.cpp b/src/plugins/multimedia/windows/player/mfplayersession.cpp
new file mode 100644
index 000000000..996ce35d8
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfplayersession.cpp
@@ -0,0 +1,1736 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "private/qplatformmediaplayer_p.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qbuffer.h>
+
+#include "private/qplatformaudiooutput_p.h"
+#include "qaudiooutput.h"
+
+#include "mfplayercontrol_p.h"
+#include "mfvideorenderercontrol_p.h"
+#include <mfmetadata_p.h>
+#include <private/qwindowsmfdefs_p.h>
+#include <private/qwindowsaudioutils_p.h>
+
+#include "mfplayersession_p.h"
+#include <mferror.h>
+#include <nserror.h>
+#include <winerror.h>
+#include "sourceresolver_p.h"
+#include <wmcodecdsp.h>
+
+#include <mmdeviceapi.h>
+#include <propvarutil.h>
+#include <functiondiscoverykeys_devpkey.h>
+
+//#define DEBUG_MEDIAFOUNDATION
+
+QT_BEGIN_NAMESPACE
+
+MFPlayerSession::MFPlayerSession(MFPlayerControl *playerControl)
+ : m_cRef(1),
+ m_playerControl(playerControl),
+ m_scrubbing(false),
+ m_restoreRate(1),
+ m_closing(false),
+ m_mediaTypes(0),
+ m_pendingRate(1),
+ m_status(QMediaPlayer::NoMedia)
+
+{
+ connect(this, &MFPlayerSession::sessionEvent, this, &MFPlayerSession::handleSessionEvent);
+
+ m_signalPositionChangeTimer.setInterval(10);
+ m_signalPositionChangeTimer.setTimerType(Qt::PreciseTimer);
+ m_signalPositionChangeTimer.callOnTimeout(this, &MFPlayerSession::timeout);
+
+ m_pendingState = NoPending;
+ ZeroMemory(&m_state, sizeof(m_state));
+ m_state.command = CmdStop;
+ m_state.prevCmd = CmdNone;
+ m_state.rate = 1.0f;
+ ZeroMemory(&m_request, sizeof(m_request));
+ m_request.command = CmdNone;
+ m_request.prevCmd = CmdNone;
+ m_request.rate = 1.0f;
+
+ m_videoRendererControl = new MFVideoRendererControl(this);
+}
+
+void MFPlayerSession::timeout()
+{
+ const qint64 pos = position();
+
+ if (pos != m_lastPosition) {
+ const bool updatePos = m_timeCounter++ % 10 == 0;
+ if (pos >= qint64(m_duration / 10000 - 20)) {
+ if (m_playerControl->doLoop()) {
+ m_session->Pause();
+ setPosition(0);
+ positionChanged(0);
+ } else {
+ if (updatePos)
+ positionChanged(pos);
+ }
+ } else {
+ if (updatePos)
+ positionChanged(pos);
+ }
+ m_lastPosition = pos;
+ }
+}
+
+void MFPlayerSession::close()
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "close";
+#endif
+
+ m_signalPositionChangeTimer.stop();
+ clear();
+ if (!m_session)
+ return;
+
+ HRESULT hr = S_OK;
+ if (m_session) {
+ m_closing = true;
+ hr = m_session->Close();
+ if (SUCCEEDED(hr)) {
+ DWORD dwWaitResult = WaitForSingleObject(m_hCloseEvent.get(), 2000);
+ if (dwWaitResult == WAIT_TIMEOUT) {
+ qWarning() << "session close time out!";
+ }
+ }
+ m_closing = false;
+ }
+
+ if (SUCCEEDED(hr)) {
+ if (m_session)
+ m_session->Shutdown();
+ if (m_sourceResolver)
+ m_sourceResolver->shutdown();
+ }
+ m_sourceResolver.Reset();
+
+ m_videoRendererControl->releaseActivate();
+// } else if (m_playerService->videoWindowControl()) {
+// m_playerService->videoWindowControl()->releaseActivate();
+// }
+
+ m_session.Reset();
+ m_hCloseEvent = {};
+ m_lastPosition = -1;
+ m_position = 0;
+}
+
+void MFPlayerSession::load(const QUrl &url, QIODevice *stream)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "load";
+#endif
+ clear();
+
+ if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver)
+ m_sourceResolver->cancel();
+
+ if (url.isEmpty() && !stream) {
+ close();
+ changeStatus(QMediaPlayer::NoMedia);
+ } else if (stream && (!stream->isReadable())) {
+ close();
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Invalid stream source."), true);
+ } else if (createSession()) {
+ changeStatus(QMediaPlayer::LoadingMedia);
+ m_sourceResolver->load(url, stream);
+ if (url.isLocalFile())
+ m_updateRoutingOnStart = true;
+ }
+ positionChanged(position());
+}
+
+void MFPlayerSession::handleSourceError(long hr)
+{
+ QString errorString;
+ QMediaPlayer::Error errorCode = QMediaPlayer::ResourceError;
+ switch (hr) {
+ case QMediaPlayer::FormatError:
+ errorCode = QMediaPlayer::FormatError;
+ errorString = tr("Attempting to play invalid Qt resource.");
+ break;
+ case NS_E_FILE_NOT_FOUND:
+ errorString = tr("The system cannot find the file specified.");
+ break;
+ case NS_E_SERVER_NOT_FOUND:
+ errorString = tr("The specified server could not be found.");
+ break;
+ case MF_E_UNSUPPORTED_BYTESTREAM_TYPE:
+ errorCode = QMediaPlayer::FormatError;
+ errorString = tr("Unsupported media type.");
+ break;
+ case MF_E_UNSUPPORTED_SCHEME:
+ errorCode = QMediaPlayer::ResourceError;
+ errorString = tr("Unsupported URL scheme.");
+ break;
+ case QMM_WININET_E_CANNOT_CONNECT:
+ errorCode = QMediaPlayer::NetworkError;
+ errorString = tr("Connection to server could not be established.");
+ break;
+ default:
+ qWarning() << "handleSourceError:"
+ << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hr);
+ errorString = tr("Failed to load source.");
+ break;
+ }
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(errorCode, errorString, true);
+}
+
+void MFPlayerSession::handleMediaSourceReady()
+{
+ if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver
+ || m_sourceResolver.Get() != sender())
+ return;
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "handleMediaSourceReady";
+#endif
+ HRESULT hr = S_OK;
+ IMFMediaSource* mediaSource = m_sourceResolver->mediaSource();
+
+ DWORD dwCharacteristics = 0;
+ mediaSource->GetCharacteristics(&dwCharacteristics);
+ seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics);
+
+ ComPtr<IMFPresentationDescriptor> sourcePD;
+ hr = mediaSource->CreatePresentationDescriptor(&sourcePD);
+ if (SUCCEEDED(hr)) {
+ m_duration = 0;
+ m_metaData = MFMetaData::fromNative(mediaSource);
+ metaDataChanged();
+ sourcePD->GetUINT64(MF_PD_DURATION, &m_duration);
+ //convert from 100 nanosecond to milisecond
+ durationUpdate(qint64(m_duration / 10000));
+ setupPlaybackTopology(mediaSource, sourcePD.Get());
+ tracksChanged();
+ } else {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Cannot create presentation descriptor."), true);
+ }
+}
+
+bool MFPlayerSession::getStreamInfo(IMFStreamDescriptor *stream,
+ MFPlayerSession::MediaType *type,
+ QString *name,
+ QString *language,
+ GUID *format) const
+{
+ if (!stream || !type || !name || !language || !format)
+ return false;
+
+ *type = Unknown;
+ *name = QString();
+ *language = QString();
+
+ ComPtr<IMFMediaTypeHandler> typeHandler;
+
+ if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler))) {
+
+ UINT32 len = 0;
+ if (SUCCEEDED(stream->GetStringLength(QMM_MF_SD_STREAM_NAME, &len)) && len > 0) {
+ WCHAR *wstr = new WCHAR[len+1];
+ if (SUCCEEDED(stream->GetString(QMM_MF_SD_STREAM_NAME, wstr, len+1, &len))) {
+ *name = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
+ }
+ delete []wstr;
+ }
+ if (SUCCEEDED(stream->GetStringLength(QMM_MF_SD_LANGUAGE, &len)) && len > 0) {
+ WCHAR *wstr = new WCHAR[len+1];
+ if (SUCCEEDED(stream->GetString(QMM_MF_SD_LANGUAGE, wstr, len+1, &len))) {
+ *language = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
+ }
+ delete []wstr;
+ }
+
+ GUID guidMajorType;
+ if (SUCCEEDED(typeHandler->GetMajorType(&guidMajorType))) {
+ if (guidMajorType == MFMediaType_Audio)
+ *type = Audio;
+ else if (guidMajorType == MFMediaType_Video)
+ *type = Video;
+ }
+
+ ComPtr<IMFMediaType> mediaType;
+ if (SUCCEEDED(typeHandler->GetCurrentMediaType(&mediaType))) {
+ mediaType->GetGUID(MF_MT_SUBTYPE, format);
+ }
+ }
+
+ return *type != Unknown;
+}
+
+void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD)
+{
+ HRESULT hr = S_OK;
+ // Get the number of streams in the media source.
+ DWORD cSourceStreams = 0;
+ hr = sourcePD->GetStreamDescriptorCount(&cSourceStreams);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Failed to get stream count."), true);
+ return;
+ }
+
+ ComPtr<IMFTopology> topology;
+ hr = MFCreateTopology(&topology);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Failed to create topology."), true);
+ return;
+ }
+
+ // For each stream, create the topology nodes and add them to the topology.
+ DWORD succeededCount = 0;
+ for (DWORD i = 0; i < cSourceStreams; i++) {
+ BOOL selected = FALSE;
+ bool streamAdded = false;
+ ComPtr<IMFStreamDescriptor> streamDesc;
+
+ HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &selected, &streamDesc);
+ if (SUCCEEDED(hr)) {
+ // The media might have multiple audio and video streams,
+ // only use one of each kind, and only if it is selected by default.
+ MediaType mediaType = Unknown;
+ QString streamName;
+ QString streamLanguage;
+ GUID format = GUID_NULL;
+
+ if (getStreamInfo(streamDesc.Get(), &mediaType, &streamName, &streamLanguage,
+ &format)) {
+
+ QPlatformMediaPlayer::TrackType trackType = (mediaType == Audio) ?
+ QPlatformMediaPlayer::AudioStream : QPlatformMediaPlayer::VideoStream;
+
+ QLocale::Language lang = streamLanguage.isEmpty() ?
+ QLocale::Language::AnyLanguage : QLocale(streamLanguage).language();
+
+ QMediaMetaData metaData;
+ metaData.insert(QMediaMetaData::Title, streamName);
+ metaData.insert(QMediaMetaData::Language, lang);
+
+ m_trackInfo[trackType].metaData.append(metaData);
+ m_trackInfo[trackType].nativeIndexes.append(i);
+ m_trackInfo[trackType].format = format;
+
+ if (((m_mediaTypes & mediaType) == 0) && selected) { // Check if this type isn't already added
+ ComPtr<IMFTopologyNode> sourceNode =
+ addSourceNode(topology.Get(), source, sourcePD, streamDesc.Get());
+ if (sourceNode) {
+ ComPtr<IMFTopologyNode> outputNode =
+ addOutputNode(mediaType, topology.Get(), 0);
+ if (outputNode) {
+ sourceNode->GetTopoNodeID(&m_trackInfo[trackType].sourceNodeId);
+ outputNode->GetTopoNodeID(&m_trackInfo[trackType].outputNodeId);
+
+ hr = sourceNode->ConnectOutput(0, outputNode.Get(), 0);
+
+ if (FAILED(hr)) {
+ error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false);
+ } else {
+ m_trackInfo[trackType].currentIndex = m_trackInfo[trackType].nativeIndexes.count() - 1;
+ streamAdded = true;
+ succeededCount++;
+ m_mediaTypes |= mediaType;
+ switch (mediaType) {
+ case Audio:
+ audioAvailable();
+ break;
+ case Video:
+ videoAvailable();
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ // remove the source node if the output node cannot be created
+ topology->RemoveNode(sourceNode.Get());
+ }
+ }
+ }
+ }
+
+ if (selected && !streamAdded)
+ sourcePD->DeselectStream(i);
+ }
+ }
+
+ if (succeededCount == 0) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Unable to play."), true);
+ } else {
+ if (m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId != TOPOID(-1))
+ topology = insertMFT(topology, m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId);
+
+ hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology.Get());
+ if (SUCCEEDED(hr)) {
+ m_updatingTopology = true;
+ } else {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true);
+ }
+ }
+}
+
+ComPtr<IMFTopologyNode> MFPlayerSession::addSourceNode(IMFTopology *topology,
+ IMFMediaSource *source,
+ IMFPresentationDescriptor *presentationDesc,
+ IMFStreamDescriptor *streamDesc)
+{
+ ComPtr<IMFTopologyNode> node;
+ HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node);
+ if (SUCCEEDED(hr)) {
+ hr = node->SetUnknown(MF_TOPONODE_SOURCE, source);
+ if (SUCCEEDED(hr)) {
+ hr = node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, presentationDesc);
+ if (SUCCEEDED(hr)) {
+ hr = node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, streamDesc);
+ if (SUCCEEDED(hr)) {
+ hr = topology->AddNode(node.Get());
+ if (SUCCEEDED(hr))
+ return node;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+ComPtr<IMFTopologyNode> MFPlayerSession::addOutputNode(MediaType mediaType, IMFTopology *topology,
+ DWORD sinkID)
+{
+ ComPtr<IMFTopologyNode> node;
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node)))
+ return NULL;
+
+ ComPtr<IMFActivate> activate;
+ if (mediaType == Audio) {
+ if (m_audioOutput) {
+ auto id = m_audioOutput->device.id();
+ if (id.isEmpty()) {
+ qInfo() << "No audio output";
+ return NULL;
+ }
+
+ HRESULT hr = MFCreateAudioRendererActivate(&activate);
+ if (FAILED(hr)) {
+ qWarning() << "Failed to create audio renderer activate";
+ return NULL;
+ }
+
+ QString s = QString::fromUtf8(id);
+ hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, (LPCWSTR)s.utf16());
+ if (FAILED(hr)) {
+ qWarning() << "Failed to set attribute for audio device"
+ << m_audioOutput->device.description();
+ return NULL;
+ }
+ }
+ } else if (mediaType == Video) {
+ activate = m_videoRendererControl->createActivate();
+
+ QSize resolution = m_metaData.value(QMediaMetaData::Resolution).toSize();
+
+ if (resolution.isValid())
+ m_videoRendererControl->setCropRect(QRect(QPoint(), resolution));
+
+ } else {
+ // Unknown stream type.
+ error(QMediaPlayer::FormatError, tr("Unknown stream type."), false);
+ }
+
+ if (!activate || FAILED(node->SetObject(activate.Get()))
+ || FAILED(node->SetUINT32(MF_TOPONODE_STREAMID, sinkID))
+ || FAILED(node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE))
+ || FAILED(topology->AddNode(node.Get()))) {
+ node.Reset();
+ }
+
+ if (activate && mediaType == Audio)
+ activate.Reset();
+
+ return node;
+}
+
+// BindOutputNode
+// Sets the IMFStreamSink pointer on an output node.
+// IMFActivate pointer in the output node must be converted to an
+// IMFStreamSink pointer before the topology loader resolves the topology.
+HRESULT BindOutputNode(IMFTopologyNode *pNode)
+{
+ ComPtr<IUnknown> nodeObject;
+ ComPtr<IMFActivate> activate;
+ ComPtr<IMFStreamSink> stream;
+ ComPtr<IMFMediaSink> sink;
+
+ HRESULT hr = pNode->GetObject(&nodeObject);
+ if (FAILED(hr))
+ return hr;
+
+ hr = nodeObject->QueryInterface(IID_PPV_ARGS(&activate));
+ if (SUCCEEDED(hr)) {
+ DWORD dwStreamID = 0;
+
+ // Try to create the media sink.
+ hr = activate->ActivateObject(IID_PPV_ARGS(&sink));
+ if (SUCCEEDED(hr))
+ dwStreamID = MFGetAttributeUINT32(pNode, MF_TOPONODE_STREAMID, 0);
+
+ if (SUCCEEDED(hr)) {
+ // First check if the media sink already has a stream sink with the requested ID.
+ hr = sink->GetStreamSinkById(dwStreamID, &stream);
+ if (FAILED(hr)) {
+ // Create the stream sink.
+ hr = sink->AddStreamSink(dwStreamID, NULL, &stream);
+ }
+ }
+
+ // Replace the node's object pointer with the stream sink.
+ if (SUCCEEDED(hr)) {
+ hr = pNode->SetObject(stream.Get());
+ }
+ } else {
+ hr = nodeObject->QueryInterface(IID_PPV_ARGS(&stream));
+ }
+
+ return hr;
+}
+
+// BindOutputNodes
+// Sets the IMFStreamSink pointers on all of the output nodes in a topology.
+HRESULT BindOutputNodes(IMFTopology *pTopology)
+{
+ ComPtr<IMFCollection> collection;
+
+ // Get the collection of output nodes.
+ HRESULT hr = pTopology->GetOutputNodeCollection(&collection);
+
+ // Enumerate all of the nodes in the collection.
+ if (SUCCEEDED(hr)) {
+ DWORD cNodes;
+ hr = collection->GetElementCount(&cNodes);
+
+ if (SUCCEEDED(hr)) {
+ for (DWORD i = 0; i < cNodes; i++) {
+ ComPtr<IUnknown> element;
+ hr = collection->GetElement(i, &element);
+ if (FAILED(hr))
+ break;
+
+ ComPtr<IMFTopologyNode> node;
+ hr = element->QueryInterface(IID_IMFTopologyNode, &node);
+ if (FAILED(hr))
+ break;
+
+ // Bind this node.
+ hr = BindOutputNode(node.Get());
+ if (FAILED(hr))
+ break;
+ }
+ }
+ }
+
+ return hr;
+}
+
+// This method binds output nodes to complete the topology,
+// then loads the topology and inserts MFT between the output node
+// and a filter connected to the output node.
+ComPtr<IMFTopology> MFPlayerSession::insertMFT(const ComPtr<IMFTopology> &topology,
+ TOPOID outputNodeId)
+{
+ bool isNewTopology = false;
+
+ ComPtr<IMFTopoLoader> topoLoader;
+ ComPtr<IMFTopology> resolvedTopology;
+ ComPtr<IMFCollection> outputNodes;
+
+ do {
+ if (FAILED(BindOutputNodes(topology.Get())))
+ break;
+
+ if (FAILED(MFCreateTopoLoader(&topoLoader)))
+ break;
+
+ if (FAILED(topoLoader->Load(topology.Get(), &resolvedTopology, NULL))) {
+ // Topology could not be resolved, adding ourselves a color converter
+ // to the topology might solve the problem
+ insertColorConverter(topology.Get(), outputNodeId);
+ if (FAILED(topoLoader->Load(topology.Get(), &resolvedTopology, NULL)))
+ break;
+ }
+
+ if (insertResizer(resolvedTopology.Get()))
+ isNewTopology = true;
+ } while (false);
+
+ if (isNewTopology) {
+ return resolvedTopology;
+ }
+
+ return topology;
+}
+
+// This method checks if the topology contains a color converter transform (CColorConvertDMO),
+// if it does it inserts a resizer transform (CResizerDMO) to handle dynamic frame size change
+// of the video stream.
+// Returns true if it inserted a resizer
+bool MFPlayerSession::insertResizer(IMFTopology *topology)
+{
+ bool inserted = false;
+ WORD elementCount = 0;
+ ComPtr<IMFTopologyNode> node;
+ ComPtr<IUnknown> object;
+ ComPtr<IWMColorConvProps> colorConv;
+ ComPtr<IMFTransform> resizer;
+ ComPtr<IMFTopologyNode> resizerNode;
+ ComPtr<IMFTopologyNode> inputNode;
+
+ HRESULT hr = topology->GetNodeCount(&elementCount);
+ if (FAILED(hr))
+ return false;
+
+ for (WORD i = 0; i < elementCount; ++i) {
+ node.Reset();
+ object.Reset();
+
+ if (FAILED(topology->GetNode(i, &node)))
+ break;
+
+ MF_TOPOLOGY_TYPE nodeType;
+ if (FAILED(node->GetNodeType(&nodeType)))
+ break;
+
+ if (nodeType != MF_TOPOLOGY_TRANSFORM_NODE)
+ continue;
+
+ if (FAILED(node->GetObject(&object)))
+ break;
+
+ if (FAILED(object->QueryInterface(IID_PPV_ARGS(&colorConv))))
+ continue;
+
+ if (FAILED(CoCreateInstance(CLSID_CResizerDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform,
+ &resizer)))
+ break;
+
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &resizerNode)))
+ break;
+
+ if (FAILED(resizerNode->SetObject(resizer.Get())))
+ break;
+
+ if (FAILED(topology->AddNode(resizerNode.Get())))
+ break;
+
+ DWORD outputIndex = 0;
+ if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) {
+ topology->RemoveNode(resizerNode.Get());
+ break;
+ }
+
+ if (FAILED(inputNode->ConnectOutput(0, resizerNode.Get(), 0))) {
+ topology->RemoveNode(resizerNode.Get());
+ break;
+ }
+
+ if (FAILED(resizerNode->ConnectOutput(0, node.Get(), 0))) {
+ inputNode->ConnectOutput(0, node.Get(), 0);
+ topology->RemoveNode(resizerNode.Get());
+ break;
+ }
+
+ inserted = true;
+ break;
+ }
+
+ return inserted;
+}
+
+// This method inserts a color converter (CColorConvertDMO) in the topology,
+// typically to convert to RGB format.
+// Usually this converter is automatically inserted when the topology is resolved but
+// for some reason it fails to do so in some cases, we then do it ourselves.
+void MFPlayerSession::insertColorConverter(IMFTopology *topology, TOPOID outputNodeId)
+{
+ ComPtr<IMFCollection> outputNodes;
+
+ if (FAILED(topology->GetOutputNodeCollection(&outputNodes)))
+ return;
+
+ DWORD elementCount = 0;
+ if (FAILED(outputNodes->GetElementCount(&elementCount)))
+ return;
+
+ for (DWORD n = 0; n < elementCount; n++) {
+ ComPtr<IUnknown> element;
+ ComPtr<IMFTopologyNode> node;
+ ComPtr<IMFTopologyNode> inputNode;
+ ComPtr<IMFTopologyNode> mftNode;
+ ComPtr<IMFTransform> converter;
+
+ do {
+ if (FAILED(outputNodes->GetElement(n, &element)))
+ break;
+
+ if (FAILED(element->QueryInterface(IID_IMFTopologyNode, &node)))
+ break;
+
+ TOPOID id;
+ if (FAILED(node->GetTopoNodeID(&id)))
+ break;
+
+ if (id != outputNodeId)
+ break;
+
+ DWORD outputIndex = 0;
+ if (FAILED(node->GetInput(0, &inputNode, &outputIndex)))
+ break;
+
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode)))
+ break;
+
+ if (FAILED(CoCreateInstance(CLSID_CColorConvertDMO, NULL, CLSCTX_INPROC_SERVER,
+ IID_IMFTransform, &converter)))
+ break;
+
+ if (FAILED(mftNode->SetObject(converter.Get())))
+ break;
+
+ if (FAILED(topology->AddNode(mftNode.Get())))
+ break;
+
+ if (FAILED(inputNode->ConnectOutput(0, mftNode.Get(), 0)))
+ break;
+
+ if (FAILED(mftNode->ConnectOutput(0, node.Get(), 0)))
+ break;
+
+ } while (false);
+ }
+}
+
+void MFPlayerSession::stop(bool immediate)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "stop";
+#endif
+ if (!immediate && m_pendingState != NoPending) {
+ m_request.setCommand(CmdStop);
+ } else {
+ if (m_state.command == CmdStop)
+ return;
+
+ if (m_scrubbing)
+ scrub(false);
+
+ if (SUCCEEDED(m_session->Stop())) {
+
+ m_state.setCommand(CmdStop);
+ m_pendingState = CmdPending;
+ if (m_status != QMediaPlayer::EndOfMedia) {
+ m_position = 0;
+ positionChanged(0);
+ }
+ } else {
+ error(QMediaPlayer::ResourceError, tr("Failed to stop."), true);
+ }
+ }
+}
+
+void MFPlayerSession::start()
+{
+ if (status() == QMediaPlayer::LoadedMedia && m_updateRoutingOnStart) {
+ m_updateRoutingOnStart = false;
+ updateOutputRouting();
+ }
+
+ if (m_status == QMediaPlayer::EndOfMedia) {
+ m_position = 0; // restart from the beginning
+ positionChanged(0);
+ }
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "start";
+#endif
+
+ if (m_pendingState != NoPending) {
+ m_request.setCommand(CmdStart);
+ } else {
+ if (m_state.command == CmdStart)
+ return;
+
+ if (m_scrubbing) {
+ scrub(false);
+ m_position = position() * 10000;
+ }
+
+ if (m_restorePosition >= 0) {
+ m_position = m_restorePosition;
+ if (!m_updatingTopology)
+ m_restorePosition = -1;
+ }
+
+ PROPVARIANT varStart;
+ InitPropVariantFromInt64(m_position, &varStart);
+
+ if (SUCCEEDED(m_session->Start(&GUID_NULL, &varStart))) {
+ m_state.setCommand(CmdStart);
+ m_pendingState = CmdPending;
+ } else {
+ error(QMediaPlayer::ResourceError, tr("failed to start playback"), true);
+ }
+ PropVariantClear(&varStart);
+ }
+}
+
+void MFPlayerSession::pause()
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "pause";
+#endif
+ if (m_pendingState != NoPending) {
+ m_request.setCommand(CmdPause);
+ } else {
+ if (m_state.command == CmdPause)
+ return;
+
+ if (SUCCEEDED(m_session->Pause())) {
+ m_state.setCommand(CmdPause);
+ m_pendingState = CmdPending;
+ } else {
+ error(QMediaPlayer::ResourceError, tr("Failed to pause."), false);
+ }
+ if (m_status == QMediaPlayer::EndOfMedia) {
+ setPosition(0);
+ positionChanged(0);
+ }
+ }
+}
+
+void MFPlayerSession::changeStatus(QMediaPlayer::MediaStatus newStatus)
+{
+ if (m_status == newStatus)
+ return;
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MFPlayerSession::changeStatus" << newStatus;
+#endif
+ m_status = newStatus;
+ statusChanged();
+}
+
+QMediaPlayer::MediaStatus MFPlayerSession::status() const
+{
+ return m_status;
+}
+
+bool MFPlayerSession::createSession()
+{
+ close();
+
+ Q_ASSERT(m_session == NULL);
+
+ HRESULT hr = MFCreateMediaSession(NULL, &m_session);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Unable to create mediasession."), true);
+ return false;
+ }
+
+ m_hCloseEvent = EventHandle{ CreateEvent(NULL, FALSE, FALSE, NULL) };
+
+ hr = m_session->BeginGetEvent(this, m_session.Get());
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::ResourceError, tr("Unable to pull session events."), false);
+ close();
+ return false;
+ }
+
+ m_sourceResolver = makeComObject<SourceResolver>();
+ QObject::connect(m_sourceResolver.Get(), &SourceResolver::mediaSourceReady, this,
+ &MFPlayerSession::handleMediaSourceReady);
+ QObject::connect(m_sourceResolver.Get(), &SourceResolver::error, this,
+ &MFPlayerSession::handleSourceError);
+
+ m_position = 0;
+ return true;
+}
+
+qint64 MFPlayerSession::position()
+{
+ if (m_request.command == CmdSeek || m_request.command == CmdSeekResume)
+ return m_request.start;
+
+ if (m_pendingState == SeekPending)
+ return m_state.start;
+
+ if (m_state.command == CmdStop)
+ return m_position / 10000;
+
+ if (m_presentationClock) {
+ MFTIME time, sysTime;
+ if (FAILED(m_presentationClock->GetCorrelatedTime(0, &time, &sysTime)))
+ return m_position / 10000;
+ return qint64(time / 10000);
+ }
+ return m_position / 10000;
+}
+
+void MFPlayerSession::setPosition(qint64 position)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "setPosition";
+#endif
+ if (m_pendingState != NoPending) {
+ m_request.setCommand(CmdSeek);
+ m_request.start = position;
+ } else {
+ setPositionInternal(position, CmdNone);
+ }
+}
+
+void MFPlayerSession::setPositionInternal(qint64 position, Command requestCmd)
+{
+ if (m_status == QMediaPlayer::EndOfMedia)
+ changeStatus(QMediaPlayer::LoadedMedia);
+ if (m_state.command == CmdStop && requestCmd != CmdSeekResume) {
+ m_position = position * 10000;
+ // Even though the position is not actually set on the session yet,
+ // report it to have changed anyway for UI controls to be updated
+ positionChanged(this->position());
+ return;
+ }
+
+ if (m_state.command == CmdPause)
+ scrub(true);
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "setPositionInternal";
+#endif
+
+ PROPVARIANT varStart;
+ varStart.vt = VT_I8;
+ varStart.hVal.QuadPart = LONGLONG(position * 10000);
+ if (SUCCEEDED(m_session->Start(NULL, &varStart))) {
+ PropVariantClear(&varStart);
+ // Store the pending state.
+ m_state.setCommand(CmdStart);
+ m_state.start = position;
+ m_pendingState = SeekPending;
+ } else {
+ error(QMediaPlayer::ResourceError, tr("Failed to seek."), true);
+ }
+}
+
+qreal MFPlayerSession::playbackRate() const
+{
+ if (m_scrubbing)
+ return m_restoreRate;
+ return m_state.rate;
+}
+
+void MFPlayerSession::setPlaybackRate(qreal rate)
+{
+ if (m_scrubbing) {
+ m_restoreRate = rate;
+ playbackRateChanged(rate);
+ return;
+ }
+ setPlaybackRateInternal(rate);
+}
+
+void MFPlayerSession::setPlaybackRateInternal(qreal rate)
+{
+ if (rate == m_request.rate)
+ return;
+
+ m_pendingRate = rate;
+ if (!m_rateSupport)
+ return;
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "setPlaybackRate";
+#endif
+ BOOL isThin = FALSE;
+
+ //from MSDN http://msdn.microsoft.com/en-us/library/aa965220%28v=vs.85%29.aspx
+ //Thinning applies primarily to video streams.
+ //In thinned mode, the source drops delta frames and deliver only key frames.
+ //At very high playback rates, the source might skip some key frames (for example, deliver every other key frame).
+
+ if (FAILED(m_rateSupport->IsRateSupported(FALSE, rate, NULL))) {
+ isThin = TRUE;
+ if (FAILED(m_rateSupport->IsRateSupported(isThin, rate, NULL))) {
+ qWarning() << "unable to set playbackrate = " << rate;
+ m_pendingRate = m_request.rate = m_state.rate;
+ return;
+ }
+ }
+ if (m_pendingState != NoPending) {
+ m_request.rate = rate;
+ m_request.isThin = isThin;
+ // Remember the current transport state (play, paused, etc), so that we
+ // can restore it after the rate change, if necessary. However, if
+ // anothercommand is already pending, that one takes precedent.
+ if (m_request.command == CmdNone)
+ m_request.setCommand(m_state.command);
+ } else {
+ //No pending operation. Commit the new rate.
+ commitRateChange(rate, isThin);
+ }
+}
+
+void MFPlayerSession::commitRateChange(qreal rate, BOOL isThin)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "commitRateChange";
+#endif
+ Q_ASSERT(m_pendingState == NoPending);
+ MFTIME hnsSystemTime = 0;
+ MFTIME hnsClockTime = 0;
+ Command cmdNow = m_state.command;
+ bool resetPosition = false;
+ // Allowed rate transitions:
+ // Positive <-> negative: Stopped
+ // Negative <-> zero: Stopped
+ // Postive <-> zero: Paused or stopped
+ if ((rate > 0 && m_state.rate <= 0) || (rate < 0 && m_state.rate >= 0)) {
+ if (cmdNow == CmdStart) {
+ // Get the current clock position. This will be the restart time.
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+ Q_ASSERT(hnsSystemTime != 0);
+
+ if (rate < 0 || m_state.rate < 0)
+ m_request.setCommand(CmdSeekResume);
+ else if (isThin || m_state.isThin)
+ m_request.setCommand(CmdStartAndSeek);
+ else
+ m_request.setCommand(CmdStart);
+
+ // We need to stop only when dealing with negative rates
+ if (rate >= 0 && m_state.rate >= 0)
+ pause();
+ else
+ stop();
+
+ // If we deal with negative rates, we stopped the session and consequently
+ // reset the position to zero. We then need to resume to the current position.
+ m_request.start = hnsClockTime / 10000;
+ } else if (cmdNow == CmdPause) {
+ if (rate < 0 || m_state.rate < 0) {
+ // The current state is paused.
+ // For this rate change, the session must be stopped. However, the
+ // session cannot transition back from stopped to paused.
+ // Therefore, this rate transition is not supported while paused.
+ qWarning() << "Unable to change rate from positive to negative or vice versa in paused state";
+ rate = m_state.rate;
+ isThin = m_state.isThin;
+ goto done;
+ }
+
+ // This happens when resuming playback after scrubbing in pause mode.
+ // This transition requires the session to be paused. Even though our
+ // internal state is set to paused, the session might not be so we need
+ // to enforce it
+ if (rate > 0 && m_state.rate == 0) {
+ m_state.setCommand(CmdNone);
+ pause();
+ }
+ }
+ } else if (rate == 0 && m_state.rate > 0) {
+ if (cmdNow != CmdPause) {
+ // Transition to paused.
+ // This transisition requires the paused state.
+ // Pause and set the rate.
+ pause();
+
+ // Request: Switch back to current state.
+ m_request.setCommand(cmdNow);
+ }
+ } else if (rate == 0 && m_state.rate < 0) {
+ // Changing rate from negative to zero requires to stop the session
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+
+ m_request.setCommand(CmdSeekResume);
+
+ stop();
+
+ // Resume to the current position (stop() will reset the position to 0)
+ m_request.start = hnsClockTime / 10000;
+ } else if (!isThin && m_state.isThin) {
+ if (cmdNow == CmdStart) {
+ // When thinning, only key frames are read and presented. Going back
+ // to normal playback requires to reset the current position to force
+ // the pipeline to decode the actual frame at the current position
+ // (which might be earlier than the last decoded key frame)
+ resetPosition = true;
+ } else if (cmdNow == CmdPause) {
+ // If paused, don't reset the position until we resume, otherwise
+ // a new frame will be rendered
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+ m_request.setCommand(CmdSeekResume);
+ m_request.start = hnsClockTime / 10000;
+ }
+
+ }
+
+ // Set the rate.
+ if (FAILED(m_rateControl->SetRate(isThin, rate))) {
+ qWarning() << "failed to set playbackrate = " << rate;
+ rate = m_state.rate;
+ isThin = m_state.isThin;
+ goto done;
+ }
+
+ if (resetPosition) {
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+ setPosition(hnsClockTime / 10000);
+ }
+
+done:
+ // Adjust our current rate and requested rate.
+ m_pendingRate = m_request.rate = m_state.rate = rate;
+ if (rate != 0)
+ m_state.isThin = isThin;
+ playbackRateChanged(rate);
+}
+
+void MFPlayerSession::scrub(bool enableScrub)
+{
+ if (m_scrubbing == enableScrub)
+ return;
+
+ m_scrubbing = enableScrub;
+
+ if (!canScrub()) {
+ if (!enableScrub)
+ m_pendingRate = m_restoreRate;
+ return;
+ }
+
+ if (enableScrub) {
+ // Enter scrubbing mode. Cache the rate.
+ m_restoreRate = m_request.rate;
+ setPlaybackRateInternal(0.0f);
+ } else {
+ // Leaving scrubbing mode. Restore the old rate.
+ setPlaybackRateInternal(m_restoreRate);
+ }
+}
+
+void MFPlayerSession::setVolume(float volume)
+{
+ if (m_volume == volume)
+ return;
+ m_volume = volume;
+
+ if (!m_muted)
+ setVolumeInternal(volume);
+}
+
+void MFPlayerSession::setMuted(bool muted)
+{
+ if (m_muted == muted)
+ return;
+ m_muted = muted;
+
+ setVolumeInternal(muted ? 0 : m_volume);
+}
+
+void MFPlayerSession::setVolumeInternal(float volume)
+{
+ if (m_volumeControl) {
+ quint32 channelCount = 0;
+ if (!SUCCEEDED(m_volumeControl->GetChannelCount(&channelCount))
+ || channelCount == 0)
+ return;
+
+ for (quint32 i = 0; i < channelCount; ++i)
+ m_volumeControl->SetChannelVolume(i, volume);
+ }
+}
+
+float MFPlayerSession::bufferProgress()
+{
+ if (!m_netsourceStatistics)
+ return 0;
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ PROPERTYKEY key;
+ key.fmtid = MFNETSOURCE_STATISTICS;
+ key.pid = MFNETSOURCE_BUFFERPROGRESS_ID;
+ int progress = -1;
+ // GetValue returns S_FALSE if the property is not available, which has
+ // a value > 0. We therefore can't use the SUCCEEDED macro here.
+ if (m_netsourceStatistics->GetValue(key, &var) == S_OK) {
+ progress = var.lVal;
+ PropVariantClear(&var);
+ }
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "bufferProgress: progress = " << progress;
+#endif
+
+ return progress/100.;
+}
+
+QMediaTimeRange MFPlayerSession::availablePlaybackRanges()
+{
+ // defaults to the whole media
+ qint64 start = 0;
+ qint64 end = qint64(m_duration / 10000);
+
+ if (m_netsourceStatistics) {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ PROPERTYKEY key;
+ key.fmtid = MFNETSOURCE_STATISTICS;
+ key.pid = MFNETSOURCE_SEEKRANGESTART_ID;
+ // GetValue returns S_FALSE if the property is not available, which has
+ // a value > 0. We therefore can't use the SUCCEEDED macro here.
+ if (m_netsourceStatistics->GetValue(key, &var) == S_OK) {
+ start = qint64(var.uhVal.QuadPart / 10000);
+ PropVariantClear(&var);
+ PropVariantInit(&var);
+ key.pid = MFNETSOURCE_SEEKRANGEEND_ID;
+ if (m_netsourceStatistics->GetValue(key, &var) == S_OK) {
+ end = qint64(var.uhVal.QuadPart / 10000);
+ PropVariantClear(&var);
+ }
+ }
+ }
+
+ return QMediaTimeRange(start, end);
+}
+
+HRESULT MFPlayerSession::QueryInterface(REFIID riid, void** ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IMFAsyncCallback) {
+ *ppvObject = static_cast<IMFAsyncCallback*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+}
+
+ULONG MFPlayerSession::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+ULONG MFPlayerSession::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0) {
+ deleteLater();
+
+ // In rare cases the session has queued events to be run between deleteLater and deleting,
+ // so we set the parent control to nullptr in order to prevent crashes in the cases.
+ m_playerControl = nullptr;
+ }
+ return cRef;
+}
+
+HRESULT MFPlayerSession::Invoke(IMFAsyncResult *pResult)
+{
+ if (pResult->GetStateNoAddRef() != m_session.Get())
+ return S_OK;
+
+ ComPtr<IMFMediaEvent> pEvent;
+ // Get the event from the event queue.
+ HRESULT hr = m_session->EndGetEvent(pResult, &pEvent);
+ if (FAILED(hr)) {
+ return S_OK;
+ }
+
+ MediaEventType meType = MEUnknown;
+ hr = pEvent->GetType(&meType);
+ if (FAILED(hr)) {
+ return S_OK;
+ }
+
+ if (meType == MESessionClosed) {
+ SetEvent(m_hCloseEvent.get());
+ return S_OK;
+ } else {
+ hr = m_session->BeginGetEvent(this, m_session.Get());
+ if (FAILED(hr)) {
+ return S_OK;
+ }
+ }
+
+ if (!m_closing) {
+ emit sessionEvent(pEvent);
+ }
+ return S_OK;
+}
+
+void MFPlayerSession::handleSessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent)
+{
+ HRESULT hrStatus = S_OK;
+ HRESULT hr = sessionEvent->GetStatus(&hrStatus);
+ if (FAILED(hr) || !m_session) {
+ return;
+ }
+
+ MediaEventType meType = MEUnknown;
+ hr = sessionEvent->GetType(&meType);
+#ifdef DEBUG_MEDIAFOUNDATION
+ if (FAILED(hrStatus))
+ qDebug() << "handleSessionEvent: MediaEventType = " << meType << "Failed";
+ else
+ qDebug() << "handleSessionEvent: MediaEventType = " << meType;
+#endif
+
+ switch (meType) {
+ case MENonFatalError: {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ sessionEvent->GetValue(&var);
+ qWarning() << "handleSessionEvent: non fatal error = " << var.ulVal;
+ PropVariantClear(&var);
+ error(QMediaPlayer::ResourceError, tr("Media session non-fatal error."), false);
+ }
+ break;
+ case MESourceUnknown:
+ changeStatus(QMediaPlayer::InvalidMedia);
+ break;
+ case MEError:
+ if (hrStatus == MF_E_ALREADY_INITIALIZED) {
+ // Workaround for a possible WMF issue that causes an error
+ // with some specific videos, which play fine otherwise.
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "handleSessionEvent: ignoring MF_E_ALREADY_INITIALIZED";
+#endif
+ break;
+ }
+ changeStatus(QMediaPlayer::InvalidMedia);
+ qWarning() << "handleSessionEvent: serious error = "
+ << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hrStatus);
+ switch (hrStatus) {
+ case MF_E_NET_READ:
+ error(QMediaPlayer::NetworkError, tr("Error reading from the network."), true);
+ break;
+ case MF_E_NET_WRITE:
+ error(QMediaPlayer::NetworkError, tr("Error writing to the network."), true);
+ break;
+ case NS_E_FIREWALL:
+ error(QMediaPlayer::NetworkError, tr("Network packets might be blocked by a firewall."), true);
+ break;
+ case MF_E_MEDIAPROC_WRONGSTATE:
+ error(QMediaPlayer::ResourceError, tr("Media session state error."), true);
+ break;
+ case MF_E_INVALID_STREAM_DATA:
+ error(QMediaPlayer::ResourceError, tr("Invalid stream data."), true);
+ break;
+ default:
+ error(QMediaPlayer::ResourceError, tr("Media session serious error."), true);
+ break;
+ }
+ break;
+ case MESessionRateChanged:
+ // If the rate change succeeded, we've already got the rate
+ // cached. If it failed, try to get the actual rate.
+ if (FAILED(hrStatus)) {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ if (SUCCEEDED(sessionEvent->GetValue(&var)) && (var.vt == VT_R4)) {
+ m_state.rate = var.fltVal;
+ }
+ playbackRateChanged(playbackRate());
+ }
+ break;
+ case MESessionScrubSampleComplete :
+ if (m_scrubbing)
+ updatePendingCommands(CmdStart);
+ break;
+ case MESessionStarted:
+ if (m_status == QMediaPlayer::EndOfMedia
+ || m_status == QMediaPlayer::LoadedMedia) {
+ // If the session started, then enough data is buffered to play
+ changeStatus(QMediaPlayer::BufferedMedia);
+ }
+
+ updatePendingCommands(CmdStart);
+ // playback started, we can now set again the procAmpValues if they have been
+ // changed previously (these are lost when loading a new media)
+// if (m_playerService->videoWindowControl()) {
+// m_playerService->videoWindowControl()->applyImageControls();
+// }
+ m_signalPositionChangeTimer.start();
+ break;
+ case MESessionStopped:
+ if (m_status != QMediaPlayer::EndOfMedia) {
+ m_position = 0;
+
+ // Reset to Loaded status unless we are loading a new media
+ // or changing the playback rate to negative values (stop required)
+ if (m_status != QMediaPlayer::LoadingMedia && m_request.command != CmdSeekResume)
+ changeStatus(QMediaPlayer::LoadedMedia);
+ }
+ updatePendingCommands(CmdStop);
+ m_signalPositionChangeTimer.stop();
+ break;
+ case MESessionPaused:
+ m_position = position() * 10000;
+ updatePendingCommands(CmdPause);
+ m_signalPositionChangeTimer.stop();
+ if (m_status == QMediaPlayer::LoadedMedia)
+ setPosition(position());
+ break;
+ case MEReconnectStart:
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MEReconnectStart" << ((hrStatus == S_OK) ? "OK" : "Failed");
+#endif
+ break;
+ case MEReconnectEnd:
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MEReconnectEnd" << ((hrStatus == S_OK) ? "OK" : "Failed");
+#endif
+ break;
+ case MESessionTopologySet:
+ if (FAILED(hrStatus)) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ error(QMediaPlayer::FormatError, tr("Unsupported media, a codec is missing."), true);
+ } else {
+ // Topology is resolved and successfuly set, this happens only after loading a new media.
+ // Make sure we always start the media from the beginning
+ m_lastPosition = -1;
+ m_position = 0;
+ positionChanged(0);
+ changeStatus(QMediaPlayer::LoadedMedia);
+ }
+ break;
+ }
+
+ if (FAILED(hrStatus)) {
+ return;
+ }
+
+ switch (meType) {
+ case MEBufferingStarted:
+ changeStatus(QMediaPlayer::StalledMedia);
+ bufferProgressChanged(bufferProgress());
+ break;
+ case MEBufferingStopped:
+ changeStatus(QMediaPlayer::BufferedMedia);
+ bufferProgressChanged(bufferProgress());
+ break;
+ case MESessionEnded:
+ m_pendingState = NoPending;
+ m_state.command = CmdStop;
+ m_state.prevCmd = CmdNone;
+ m_request.command = CmdNone;
+ m_request.prevCmd = CmdNone;
+
+ //keep reporting the final position after end of media
+ m_position = qint64(m_duration);
+ positionChanged(position());
+
+ changeStatus(QMediaPlayer::EndOfMedia);
+ break;
+ case MEEndOfPresentationSegment:
+ break;
+ case MESessionTopologyStatus: {
+ UINT32 status;
+ if (SUCCEEDED(sessionEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status))) {
+ if (status == MF_TOPOSTATUS_READY) {
+ ComPtr<IMFClock> clock;
+ if (SUCCEEDED(m_session->GetClock(&clock))) {
+ clock->QueryInterface(IID_IMFPresentationClock, &m_presentationClock);
+ }
+
+ if (SUCCEEDED(MFGetService(m_session.Get(), MF_RATE_CONTROL_SERVICE,
+ IID_PPV_ARGS(&m_rateControl)))) {
+ if (SUCCEEDED(MFGetService(m_session.Get(), MF_RATE_CONTROL_SERVICE,
+ IID_PPV_ARGS(&m_rateSupport)))) {
+ if (SUCCEEDED(m_rateSupport->IsRateSupported(TRUE, 0, NULL)))
+ m_canScrub = true;
+ }
+ BOOL isThin = FALSE;
+ float rate = 1;
+ if (SUCCEEDED(m_rateControl->GetRate(&isThin, &rate))) {
+ if (m_pendingRate != rate) {
+ m_state.rate = m_request.rate = rate;
+ setPlaybackRate(m_pendingRate);
+ }
+ }
+ }
+ MFGetService(m_session.Get(), MFNETSOURCE_STATISTICS_SERVICE,
+ IID_PPV_ARGS(&m_netsourceStatistics));
+
+ if (SUCCEEDED(MFGetService(m_session.Get(), MR_STREAM_VOLUME_SERVICE,
+ IID_PPV_ARGS(&m_volumeControl))))
+ setVolumeInternal(m_muted ? 0 : m_volume);
+
+ m_updatingTopology = false;
+ stop();
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void MFPlayerSession::updatePendingCommands(Command command)
+{
+ positionChanged(position());
+ if (m_state.command != command || m_pendingState == NoPending)
+ return;
+
+ // Seek while paused completed
+ if (m_pendingState == SeekPending && m_state.prevCmd == CmdPause) {
+ m_pendingState = NoPending;
+ // A seek operation actually restarts playback. If scrubbing is possible, playback rate
+ // is set to 0.0 at this point and we just need to reset the current state to Pause.
+ // If scrubbing is not possible, the playback rate was not changed and we explicitly need
+ // to re-pause playback.
+ if (!canScrub())
+ pause();
+ else
+ m_state.setCommand(CmdPause);
+ }
+
+ m_pendingState = NoPending;
+
+ //First look for rate changes.
+ if (m_request.rate != m_state.rate) {
+ commitRateChange(m_request.rate, m_request.isThin);
+ }
+
+ // Now look for new requests.
+ if (m_pendingState == NoPending) {
+ switch (m_request.command) {
+ case CmdStart:
+ start();
+ break;
+ case CmdPause:
+ pause();
+ break;
+ case CmdStop:
+ stop();
+ break;
+ case CmdSeek:
+ case CmdSeekResume:
+ setPositionInternal(m_request.start, m_request.command);
+ break;
+ case CmdStartAndSeek:
+ start();
+ setPositionInternal(m_request.start, m_request.command);
+ break;
+ default:
+ break;
+ }
+ m_request.setCommand(CmdNone);
+ }
+
+}
+
+bool MFPlayerSession::canScrub() const
+{
+ return m_canScrub && m_rateSupport && m_rateControl;
+}
+
+void MFPlayerSession::clear()
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MFPlayerSession::clear";
+#endif
+ m_mediaTypes = 0;
+ m_canScrub = false;
+
+ m_pendingState = NoPending;
+ m_state.command = CmdStop;
+ m_state.prevCmd = CmdNone;
+ m_request.command = CmdNone;
+ m_request.prevCmd = CmdNone;
+
+ for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
+ m_trackInfo[i].metaData.clear();
+ m_trackInfo[i].nativeIndexes.clear();
+ m_trackInfo[i].currentIndex = -1;
+ m_trackInfo[i].sourceNodeId = TOPOID(-1);
+ m_trackInfo[i].outputNodeId = TOPOID(-1);
+ m_trackInfo[i].format = GUID_NULL;
+ }
+
+ if (!m_metaData.isEmpty()) {
+ m_metaData.clear();
+ metaDataChanged();
+ }
+
+ m_presentationClock.Reset();
+ m_rateControl.Reset();
+ m_rateSupport.Reset();
+ m_volumeControl.Reset();
+ m_netsourceStatistics.Reset();
+}
+
+void MFPlayerSession::setAudioOutput(QPlatformAudioOutput *device)
+{
+ if (m_audioOutput == device)
+ return;
+
+ if (m_audioOutput)
+ m_audioOutput->q->disconnect(this);
+
+ m_audioOutput = device;
+ if (m_audioOutput) {
+ setMuted(m_audioOutput->q->isMuted());
+ setVolume(m_audioOutput->q->volume());
+ updateOutputRouting();
+ connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &MFPlayerSession::updateOutputRouting);
+ connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &MFPlayerSession::setVolume);
+ connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &MFPlayerSession::setMuted);
+ }
+}
+
+void MFPlayerSession::updateOutputRouting()
+{
+ int currentAudioTrack = m_trackInfo[QPlatformMediaPlayer::AudioStream].currentIndex;
+ if (currentAudioTrack > -1)
+ setActiveTrack(QPlatformMediaPlayer::AudioStream, currentAudioTrack);
+}
+
+void MFPlayerSession::setVideoSink(QVideoSink *sink)
+{
+ m_videoRendererControl->setSink(sink);
+}
+
+void MFPlayerSession::setActiveTrack(QPlatformMediaPlayer::TrackType type, int index)
+{
+ if (!m_session)
+ return;
+
+ // Only audio track selection is currently supported.
+ if (type != QPlatformMediaPlayer::AudioStream)
+ return;
+
+ const auto &nativeIndexes = m_trackInfo[type].nativeIndexes;
+
+ if (index < -1 || index >= nativeIndexes.count())
+ return;
+
+ // Updating the topology fails if there is a HEVC video stream,
+ // which causes other issues. Ignoring the change, for now.
+ if (m_trackInfo[QPlatformMediaPlayer::VideoStream].format == MFVideoFormat_HEVC)
+ return;
+
+ ComPtr<IMFTopology> topology;
+
+ if (SUCCEEDED(m_session->GetFullTopology(QMM_MFSESSION_GETFULLTOPOLOGY_CURRENT, 0, &topology))) {
+
+ m_restorePosition = position() * 10000;
+
+ if (m_state.command == CmdStart)
+ stop();
+
+ if (m_trackInfo[type].outputNodeId != TOPOID(-1)) {
+ ComPtr<IMFTopologyNode> node;
+ if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].outputNodeId, &node))) {
+ topology->RemoveNode(node.Get());
+ m_trackInfo[type].outputNodeId = TOPOID(-1);
+ }
+ }
+ if (m_trackInfo[type].sourceNodeId != TOPOID(-1)) {
+ ComPtr<IMFTopologyNode> node;
+ if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].sourceNodeId, &node))) {
+ topology->RemoveNode(node.Get());
+ m_trackInfo[type].sourceNodeId = TOPOID(-1);
+ }
+ }
+
+ IMFMediaSource *mediaSource = m_sourceResolver->mediaSource();
+
+ ComPtr<IMFPresentationDescriptor> sourcePD;
+ if (SUCCEEDED(mediaSource->CreatePresentationDescriptor(&sourcePD))) {
+
+ if (m_trackInfo[type].currentIndex >= 0 && m_trackInfo[type].currentIndex < nativeIndexes.count())
+ sourcePD->DeselectStream(nativeIndexes.at(m_trackInfo[type].currentIndex));
+
+ m_trackInfo[type].currentIndex = index;
+
+ if (index == -1) {
+ m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology.Get());
+ } else {
+ int nativeIndex = nativeIndexes.at(index);
+ sourcePD->SelectStream(nativeIndex);
+
+ ComPtr<IMFStreamDescriptor> streamDesc;
+ BOOL selected = FALSE;
+
+ if (SUCCEEDED(sourcePD->GetStreamDescriptorByIndex(nativeIndex, &selected, &streamDesc))) {
+ ComPtr<IMFTopologyNode> sourceNode = addSourceNode(
+ topology.Get(), mediaSource, sourcePD.Get(), streamDesc.Get());
+ if (sourceNode) {
+ ComPtr<IMFTopologyNode> outputNode =
+ addOutputNode(MFPlayerSession::Audio, topology.Get(), 0);
+ if (outputNode) {
+ if (SUCCEEDED(sourceNode->ConnectOutput(0, outputNode.Get(), 0))) {
+ sourceNode->GetTopoNodeID(&m_trackInfo[type].sourceNodeId);
+ outputNode->GetTopoNodeID(&m_trackInfo[type].outputNodeId);
+ m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE,
+ topology.Get());
+ }
+ }
+ }
+ }
+ }
+ m_updatingTopology = true;
+ }
+ }
+}
+
+int MFPlayerSession::activeTrack(QPlatformMediaPlayer::TrackType type)
+{
+ if (type >= QPlatformMediaPlayer::NTrackTypes)
+ return -1;
+ return m_trackInfo[type].currentIndex;
+}
+
+int MFPlayerSession::trackCount(QPlatformMediaPlayer::TrackType type)
+{
+ if (type >= QPlatformMediaPlayer::NTrackTypes)
+ return -1;
+ return m_trackInfo[type].metaData.count();
+}
+
+QMediaMetaData MFPlayerSession::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber)
+{
+ if (type >= QPlatformMediaPlayer::NTrackTypes)
+ return {};
+
+ if (trackNumber < 0 || trackNumber >= m_trackInfo[type].metaData.count())
+ return {};
+
+ return m_trackInfo[type].metaData.at(trackNumber);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_mfplayersession_p.cpp"
diff --git a/src/plugins/multimedia/windows/player/mfplayersession_p.h b/src/plugins/multimedia/windows/player/mfplayersession_p.h
new file mode 100644
index 000000000..50141a7fb
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfplayersession_p.h
@@ -0,0 +1,240 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MFPLAYERSESSION_H
+#define MFPLAYERSESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include "qmediaplayer.h"
+#include "qmediatimerange.h"
+
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qwaitcondition.h>
+#include <QtMultimedia/qaudioformat.h>
+#include <QtMultimedia/qvideoframeformat.h>
+#include <qaudiodevice.h>
+#include <qtimer.h>
+#include "mfplayercontrol_p.h"
+#include <private/qcomptr_p.h>
+#include <evrhelpers_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QUrl;
+
+class SourceResolver;
+class MFVideoRendererControl;
+class MFPlayerControl;
+class MFPlayerService;
+class AudioSampleGrabberCallback;
+class MFTransform;
+
+class MFPlayerSession : public QObject, public IMFAsyncCallback
+{
+ Q_OBJECT
+ friend class SourceResolver;
+public:
+ MFPlayerSession(MFPlayerControl *playerControl = 0);
+
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) override;
+
+ STDMETHODIMP_(ULONG) AddRef(void) override;
+
+ STDMETHODIMP_(ULONG) Release(void) override;
+
+ STDMETHODIMP Invoke(IMFAsyncResult *pResult) override;
+
+ STDMETHODIMP GetParameters(DWORD *pdwFlags, DWORD *pdwQueue) override
+ {
+ Q_UNUSED(pdwFlags);
+ Q_UNUSED(pdwQueue);
+ return E_NOTIMPL;
+ }
+
+ void load(const QUrl &media, QIODevice *stream);
+ void stop(bool immediate = false);
+ void start();
+ void pause();
+
+ QMediaPlayer::MediaStatus status() const;
+ qint64 position();
+ void setPosition(qint64 position);
+ qreal playbackRate() const;
+ void setPlaybackRate(qreal rate);
+ float bufferProgress();
+ QMediaTimeRange availablePlaybackRanges();
+
+ void changeStatus(QMediaPlayer::MediaStatus newStatus);
+
+ void close();
+
+ void setAudioOutput(QPlatformAudioOutput *device);
+
+ QMediaMetaData metaData() const { return m_metaData; }
+
+ void setVideoSink(QVideoSink *sink);
+
+ void setActiveTrack(QPlatformMediaPlayer::TrackType type, int index);
+ int activeTrack(QPlatformMediaPlayer::TrackType type);
+ int trackCount(QPlatformMediaPlayer::TrackType);
+ QMediaMetaData trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber);
+
+ void setPlayerControl(MFPlayerControl *playerControl) { m_playerControl = playerControl; }
+
+ void statusChanged() { if (m_playerControl) m_playerControl->handleStatusChanged(); }
+ void tracksChanged() { if (m_playerControl) m_playerControl->handleTracksChanged(); }
+ void audioAvailable() { if (m_playerControl) m_playerControl->handleAudioAvailable(); }
+ void videoAvailable() { if (m_playerControl) m_playerControl->handleVideoAvailable(); }
+ void durationUpdate(qint64 duration) { if (m_playerControl) m_playerControl->handleDurationUpdate(duration); }
+ void seekableUpdate(bool seekable) { if (m_playerControl) m_playerControl->handleSeekableUpdate(seekable); }
+ void error(QMediaPlayer::Error error, QString errorString, bool isFatal) { if (m_playerControl) m_playerControl->handleError(error, errorString, isFatal); }
+ void playbackRateChanged(qreal rate) { if (m_playerControl) m_playerControl->playbackRateChanged(rate); }
+ void bufferProgressChanged(float percentFilled) { if (m_playerControl) m_playerControl->bufferProgressChanged(percentFilled); }
+ void metaDataChanged() { if (m_playerControl) m_playerControl->metaDataChanged(); }
+ void positionChanged(qint64 position) { if (m_playerControl) m_playerControl->positionChanged(position); }
+
+public Q_SLOTS:
+ void setVolume(float volume);
+ void setMuted(bool muted);
+ void updateOutputRouting();
+
+Q_SIGNALS:
+ void sessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent);
+
+private Q_SLOTS:
+ void handleMediaSourceReady();
+ void handleSessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent);
+ void handleSourceError(long hr);
+ void timeout();
+
+private:
+ long m_cRef;
+ MFPlayerControl *m_playerControl = nullptr;
+ MFVideoRendererControl *m_videoRendererControl = nullptr;
+ ComPtr<IMFMediaSession> m_session;
+ ComPtr<IMFPresentationClock> m_presentationClock;
+ ComPtr<IMFRateControl> m_rateControl;
+ ComPtr<IMFRateSupport> m_rateSupport;
+ ComPtr<IMFAudioStreamVolume> m_volumeControl;
+ ComPtr<IPropertyStore> m_netsourceStatistics;
+ qint64 m_position = 0;
+ qint64 m_restorePosition = -1;
+ qint64 m_timeCounter = 0;
+ UINT64 m_duration = 0;
+ bool m_updatingTopology = false;
+ bool m_updateRoutingOnStart = false;
+
+ enum Command
+ {
+ CmdNone = 0,
+ CmdStop,
+ CmdStart,
+ CmdPause,
+ CmdSeek,
+ CmdSeekResume,
+ CmdStartAndSeek
+ };
+
+ void clear();
+ void setPositionInternal(qint64 position, Command requestCmd);
+ void setPlaybackRateInternal(qreal rate);
+ void commitRateChange(qreal rate, BOOL isThin);
+ bool canScrub() const;
+ void scrub(bool enableScrub);
+ bool m_scrubbing;
+ float m_restoreRate;
+
+ ComPtr<SourceResolver> m_sourceResolver;
+ EventHandle m_hCloseEvent;
+ bool m_closing;
+
+ enum MediaType
+ {
+ Unknown = 0,
+ Audio = 1,
+ Video = 2,
+ };
+ DWORD m_mediaTypes;
+
+ enum PendingState
+ {
+ NoPending = 0,
+ CmdPending,
+ SeekPending,
+ };
+
+ struct SeekState
+ {
+ void setCommand(Command cmd) {
+ prevCmd = command;
+ command = cmd;
+ }
+ Command command;
+ Command prevCmd;
+ float rate; // Playback rate
+ BOOL isThin; // Thinned playback?
+ qint64 start; // Start position
+ };
+ SeekState m_state; // Current nominal state.
+ SeekState m_request; // Pending request.
+ PendingState m_pendingState;
+ float m_pendingRate;
+ void updatePendingCommands(Command command);
+
+ struct TrackInfo
+ {
+ QList<QMediaMetaData> metaData;
+ QList<int> nativeIndexes;
+ int currentIndex = -1;
+ TOPOID sourceNodeId = -1;
+ TOPOID outputNodeId = -1;
+ GUID format = GUID_NULL;
+ };
+ TrackInfo m_trackInfo[QPlatformMediaPlayer::NTrackTypes];
+
+ QMediaPlayer::MediaStatus m_status;
+ bool m_canScrub;
+ float m_volume = 1.;
+ bool m_muted = false;
+
+ QPlatformAudioOutput *m_audioOutput = nullptr;
+ QMediaMetaData m_metaData;
+
+ void setVolumeInternal(float volume);
+
+ bool createSession();
+ void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD);
+ bool getStreamInfo(IMFStreamDescriptor *stream, MFPlayerSession::MediaType *type, QString *name, QString *language, GUID *format) const;
+ ComPtr<IMFTopologyNode> addSourceNode(IMFTopology *topology, IMFMediaSource *source,
+ IMFPresentationDescriptor *presentationDesc,
+ IMFStreamDescriptor *streamDesc);
+ ComPtr<IMFTopologyNode> addOutputNode(MediaType mediaType, IMFTopology *topology, DWORD sinkID);
+
+ QAudioFormat audioFormatForMFMediaType(IMFMediaType *mediaType) const;
+
+ ComPtr<IMFTopology> insertMFT(const ComPtr<IMFTopology> &topology, TOPOID outputNodeId);
+ bool insertResizer(IMFTopology *topology);
+ void insertColorConverter(IMFTopology *topology, TOPOID outputNodeId);
+
+ QTimer m_signalPositionChangeTimer;
+ qint64 m_lastPosition = -1;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp b/src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp
new file mode 100644
index 000000000..7c79c3a8a
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp
@@ -0,0 +1,152 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mfvideorenderercontrol_p.h"
+#include "mfactivate_p.h"
+
+#include "evrcustompresenter_p.h"
+
+#include <private/qplatformvideosink_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class EVRCustomPresenterActivate : public MFAbstractActivate
+{
+public:
+ EVRCustomPresenterActivate(QVideoSink *sink);
+
+ STDMETHODIMP ActivateObject(REFIID riid, void **ppv) override;
+ STDMETHODIMP ShutdownObject() override;
+ STDMETHODIMP DetachObject() override;
+
+ void setSink(QVideoSink *sink);
+ void setCropRect(QRect cropRect);
+
+private:
+ // Destructor is not public. Caller should call Release.
+ ~EVRCustomPresenterActivate() override { }
+
+ EVRCustomPresenter *m_presenter;
+ QVideoSink *m_videoSink;
+ QRect m_cropRect;
+ QMutex m_mutex;
+};
+
+
+MFVideoRendererControl::MFVideoRendererControl(QObject *parent)
+ : QObject(parent)
+{
+}
+
+MFVideoRendererControl::~MFVideoRendererControl()
+{
+ releaseActivate();
+}
+
+void MFVideoRendererControl::releaseActivate()
+{
+ if (m_sink)
+ m_sink->platformVideoSink()->setVideoFrame(QVideoFrame());
+
+ if (m_presenterActivate) {
+ m_presenterActivate->ShutdownObject();
+ m_presenterActivate->Release();
+ m_presenterActivate = NULL;
+ }
+
+ if (m_currentActivate) {
+ m_currentActivate->ShutdownObject();
+ m_currentActivate->Release();
+ }
+ m_currentActivate = NULL;
+}
+
+void MFVideoRendererControl::setSink(QVideoSink *sink)
+{
+ m_sink = sink;
+
+ if (m_presenterActivate)
+ m_presenterActivate->setSink(m_sink);
+}
+
+void MFVideoRendererControl::setCropRect(const QRect &cropRect)
+{
+ if (m_presenterActivate)
+ m_presenterActivate->setCropRect(cropRect);
+}
+
+IMFActivate* MFVideoRendererControl::createActivate()
+{
+ releaseActivate();
+
+ if (m_sink) {
+ // Create the EVR media sink, but replace the presenter with our own
+ if (SUCCEEDED(MFCreateVideoRendererActivate(::GetShellWindow(), &m_currentActivate))) {
+ m_presenterActivate = new EVRCustomPresenterActivate(m_sink);
+ m_currentActivate->SetUnknown(MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE, m_presenterActivate);
+ }
+ }
+
+ return m_currentActivate;
+}
+
+EVRCustomPresenterActivate::EVRCustomPresenterActivate(QVideoSink *sink)
+ : MFAbstractActivate()
+ , m_presenter(0)
+ , m_videoSink(sink)
+{ }
+
+HRESULT EVRCustomPresenterActivate::ActivateObject(REFIID riid, void **ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+ QMutexLocker locker(&m_mutex);
+ if (!m_presenter) {
+ m_presenter = new EVRCustomPresenter(m_videoSink);
+ m_presenter->setCropRect(m_cropRect);
+ }
+ return m_presenter->QueryInterface(riid, ppv);
+}
+
+HRESULT EVRCustomPresenterActivate::ShutdownObject()
+{
+ // The presenter does not implement IMFShutdown so
+ // this function is the same as DetachObject()
+ return DetachObject();
+}
+
+HRESULT EVRCustomPresenterActivate::DetachObject()
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_presenter) {
+ m_presenter->Release();
+ m_presenter = 0;
+ }
+ return S_OK;
+}
+
+void EVRCustomPresenterActivate::setSink(QVideoSink *sink)
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_videoSink == sink)
+ return;
+
+ m_videoSink = sink;
+
+ if (m_presenter)
+ m_presenter->setSink(sink);
+}
+
+void EVRCustomPresenterActivate::setCropRect(QRect cropRect)
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_cropRect == cropRect)
+ return;
+
+ m_cropRect = cropRect;
+
+ if (m_presenter)
+ m_presenter->setCropRect(cropRect);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h b/src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h
new file mode 100644
index 000000000..ed5195240
--- /dev/null
+++ b/src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MFVIDEORENDERERCONTROL_H
+#define MFVIDEORENDERERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qobject.h>
+#include <qpointer.h>
+#include <qrect.h>
+#include <mfobjects.h>
+
+QT_BEGIN_NAMESPACE
+class EVRCustomPresenterActivate;
+class QVideoSink;
+
+class MFVideoRendererControl : public QObject
+{
+public:
+ MFVideoRendererControl(QObject *parent = 0);
+ ~MFVideoRendererControl();
+
+ void setSink(QVideoSink *surface);
+ void setCropRect(const QRect &cropRect);
+
+ IMFActivate* createActivate();
+ void releaseActivate();
+
+private:
+ QPointer<QVideoSink> m_sink;
+ IMFActivate *m_currentActivate = nullptr;
+ EVRCustomPresenterActivate *m_presenterActivate = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif