[WinCairo][Video][MediaFoundation] Video should be rendered in provided graphics...
authorpeavo@outlook.com <peavo@outlook.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 9 Nov 2015 21:50:46 +0000 (21:50 +0000)
committerpeavo@outlook.com <peavo@outlook.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 9 Nov 2015 21:50:46 +0000 (21:50 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150941

Reviewed by Brent Fulgham.

On WinCairo, we currently render video in a child window of the main browser window.
This makes it difficult to render things on top of the video, like video controls and
context menus. We should render the video in the graphics context provided by the paint
method. This is done by implementing a custom EVR (Enhanced Video Renderer) presenter
for Media Foundation.

* platform/graphics/win/MediaPlayerPrivateMediaFoundation.cpp:
(MFCreateMediaType):
(WebCore::MediaPlayerPrivateMediaFoundation::MediaPlayerPrivateMediaFoundation):
(WebCore::MediaPlayerPrivateMediaFoundation::registerMediaEngine):
(WebCore::MediaPlayerPrivateMediaFoundation::isAvailable):
(WebCore::MediaPlayerPrivateMediaFoundation::setSize):
(WebCore::MediaPlayerPrivateMediaFoundation::paint):
(WebCore::MediaPlayerPrivateMediaFoundation::createSession):
(WebCore::MediaPlayerPrivateMediaFoundation::endGetEvent):
(WebCore::MediaPlayerPrivateMediaFoundation::createVideoWindow):
(WebCore::MediaPlayerPrivateMediaFoundation::destroyVideoWindow):
(WebCore::MediaPlayerPrivateMediaFoundation::invalidateFrameView):
(WebCore::MediaPlayerPrivateMediaFoundation::addListener):
(WebCore::MediaPlayerPrivateMediaFoundation::createOutputNode):
(WebCore::MediaPlayerPrivateMediaFoundation::onTopologySet):
(WebCore::MediaPlayerPrivateMediaFoundation::AsyncCallback::onMediaPlayerDeleted):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CustomVideoPresenter):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::~CustomVideoPresenter):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::QueryInterface):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::AddRef):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Release):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockStart):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockStop):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockPause):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockRestart):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockSetRate):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ProcessMessage):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCurrentMediaType):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetDeviceID):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::InitServicePointers):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ReleaseServicePointers):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetService):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ActivateObject):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DetachObject):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ShutdownObject):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetVideoWindow):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetVideoWindow):
(WebCore::setMixerSourceRect):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetVideoPosition):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetVideoPosition):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::RepaintVideo):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Invoke):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::onMediaPlayerDeleted):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::paintCurrentFrame):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isActive):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::configureMixer):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::flush):
(WebCore::areMediaTypesEqual):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::setMediaType):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::checkShutdown):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::renegotiateMediaType):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processInputNotify):
(WebCore::MFOffsetToFloat):
(WebCore::MakeOffset):
(WebCore::MakeArea):
(WebCore::validateVideoArea):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::beginStreaming):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::endStreaming):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::checkEndOfStream):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isMediaTypeSupported):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::createOptimalVideoType):
(WebCore::correctAspectRatio):
(WebCore::GetVideoDisplayArea):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::calculateOutputRectangle):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processOutputLoop):
(WebCore::setDesiredSampleTime):
(WebCore::clearDesiredSampleTime):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processOutput):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::deliverSample):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::trackSample):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::releaseResources):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::onSampleFree):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::notifyEvent):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::getSample):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::returnSample):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::areSamplesPending):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::initialize):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::clear):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::setFrameRate):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::startScheduler):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::stopScheduler):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::flush):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::scheduleSample):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::processSamplesInQueue):
(WebCore::MFTimeToMilliseconds):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::processSample):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::schedulerThreadProc):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::schedulerThreadProcPrivate):
(WebCore::findAdapter):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::Direct3DPresenter):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::~Direct3DPresenter):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getService):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::checkFormat):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::setVideoWindow):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::setDestinationRect):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createVideoSamples):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::releaseResources):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::checkDeviceState):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::presentSample):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::paintCurrentFrame):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::initializeD3D):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createD3DDevice):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createD3DSample):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::presentSwapChain):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getSwapChainPresentParameters):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::updateDestRect):
* platform/graphics/win/MediaPlayerPrivateMediaFoundation.h:
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::VideoSamplePool):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::~VideoSamplePool):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::VideoScheduler):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::~VideoScheduler):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::setPresenter):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::setClockRate):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::lastSampleTime):
(WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::frameDuration):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getVideoWindow):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getDestinationRect):
(WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::refreshRate):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetItem):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetItemType):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CompareItem):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Compare):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetUINT32):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetUINT64):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetDouble):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetGUID):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetStringLength):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetString):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetAllocatedString):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetBlobSize):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetBlob):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetAllocatedBlob):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetUnknown):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetItem):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DeleteItem):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DeleteAllItems):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetUINT32):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetUINT64):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetDouble):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetGUID):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetString):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetBlob):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetUnknown):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::LockStore):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::UnlockStore):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCount):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetItemByIndex):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CopyAllItems):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetNativeVideoSize):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetIdealVideoSize):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetAspectRatioMode):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetAspectRatioMode):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCurrentImage):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetBorderColor):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetBorderColor):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetRenderingPrefs):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetRenderingPrefs):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetFullscreen):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetFullscreen):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetParameters):
(WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isScrubbing):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@192176 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/win/MediaPlayerPrivateMediaFoundation.cpp
Source/WebCore/platform/graphics/win/MediaPlayerPrivateMediaFoundation.h

index a368cf2..0eafbfb 100644 (file)
@@ -1,3 +1,178 @@
+2015-11-09  Per Arne Vollan  <peavo@outlook.com>
+
+        [WinCairo][Video][MediaFoundation] Video should be rendered in provided graphics context.
+        https://bugs.webkit.org/show_bug.cgi?id=150941
+
+        Reviewed by Brent Fulgham.
+
+        On WinCairo, we currently render video in a child window of the main browser window.
+        This makes it difficult to render things on top of the video, like video controls and
+        context menus. We should render the video in the graphics context provided by the paint
+        method. This is done by implementing a custom EVR (Enhanced Video Renderer) presenter
+        for Media Foundation.
+
+        * platform/graphics/win/MediaPlayerPrivateMediaFoundation.cpp:
+        (MFCreateMediaType):
+        (WebCore::MediaPlayerPrivateMediaFoundation::MediaPlayerPrivateMediaFoundation):
+        (WebCore::MediaPlayerPrivateMediaFoundation::registerMediaEngine):
+        (WebCore::MediaPlayerPrivateMediaFoundation::isAvailable):
+        (WebCore::MediaPlayerPrivateMediaFoundation::setSize):
+        (WebCore::MediaPlayerPrivateMediaFoundation::paint):
+        (WebCore::MediaPlayerPrivateMediaFoundation::createSession):
+        (WebCore::MediaPlayerPrivateMediaFoundation::endGetEvent):
+        (WebCore::MediaPlayerPrivateMediaFoundation::createVideoWindow):
+        (WebCore::MediaPlayerPrivateMediaFoundation::destroyVideoWindow):
+        (WebCore::MediaPlayerPrivateMediaFoundation::invalidateFrameView):
+        (WebCore::MediaPlayerPrivateMediaFoundation::addListener):
+        (WebCore::MediaPlayerPrivateMediaFoundation::createOutputNode):
+        (WebCore::MediaPlayerPrivateMediaFoundation::onTopologySet):
+        (WebCore::MediaPlayerPrivateMediaFoundation::AsyncCallback::onMediaPlayerDeleted):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CustomVideoPresenter):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::~CustomVideoPresenter):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::QueryInterface):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::AddRef):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Release):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockStart):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockStop):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockPause):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockRestart):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockSetRate):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ProcessMessage):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCurrentMediaType):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetDeviceID):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::InitServicePointers):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ReleaseServicePointers):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetService):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ActivateObject):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DetachObject):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ShutdownObject):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetVideoWindow):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetVideoWindow):
+        (WebCore::setMixerSourceRect):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetVideoPosition):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetVideoPosition):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::RepaintVideo):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Invoke):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::onMediaPlayerDeleted):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::paintCurrentFrame):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isActive):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::configureMixer):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::flush):
+        (WebCore::areMediaTypesEqual):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::setMediaType):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::checkShutdown):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::renegotiateMediaType):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processInputNotify):
+        (WebCore::MFOffsetToFloat):
+        (WebCore::MakeOffset):
+        (WebCore::MakeArea):
+        (WebCore::validateVideoArea):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::beginStreaming):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::endStreaming):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::checkEndOfStream):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isMediaTypeSupported):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::createOptimalVideoType):
+        (WebCore::correctAspectRatio):
+        (WebCore::GetVideoDisplayArea):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::calculateOutputRectangle):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processOutputLoop):
+        (WebCore::setDesiredSampleTime):
+        (WebCore::clearDesiredSampleTime):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processOutput):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::deliverSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::trackSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::releaseResources):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::onSampleFree):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::notifyEvent):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::getSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::returnSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::areSamplesPending):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::initialize):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::clear):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::setFrameRate):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::startScheduler):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::stopScheduler):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::flush):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::scheduleSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::processSamplesInQueue):
+        (WebCore::MFTimeToMilliseconds):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::processSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::schedulerThreadProc):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::schedulerThreadProcPrivate):
+        (WebCore::findAdapter):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::Direct3DPresenter):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::~Direct3DPresenter):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getService):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::checkFormat):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::setVideoWindow):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::setDestinationRect):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createVideoSamples):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::releaseResources):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::checkDeviceState):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::presentSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::paintCurrentFrame):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::initializeD3D):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createD3DDevice):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createD3DSample):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::presentSwapChain):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getSwapChainPresentParameters):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::updateDestRect):
+        * platform/graphics/win/MediaPlayerPrivateMediaFoundation.h:
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::VideoSamplePool):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoSamplePool::~VideoSamplePool):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::VideoScheduler):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::~VideoScheduler):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::setPresenter):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::setClockRate):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::lastSampleTime):
+        (WebCore::MediaPlayerPrivateMediaFoundation::VideoScheduler::frameDuration):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getVideoWindow):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getDestinationRect):
+        (WebCore::MediaPlayerPrivateMediaFoundation::Direct3DPresenter::refreshRate):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetItem):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetItemType):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CompareItem):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Compare):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetUINT32):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetUINT64):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetDouble):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetGUID):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetStringLength):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetString):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetAllocatedString):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetBlobSize):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetBlob):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetAllocatedBlob):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetUnknown):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetItem):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DeleteItem):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DeleteAllItems):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetUINT32):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetUINT64):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetDouble):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetGUID):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetString):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetBlob):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetUnknown):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::LockStore):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::UnlockStore):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCount):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetItemByIndex):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CopyAllItems):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetNativeVideoSize):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetIdealVideoSize):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetAspectRatioMode):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetAspectRatioMode):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCurrentImage):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetBorderColor):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetBorderColor):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetRenderingPrefs):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetRenderingPrefs):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetFullscreen):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetFullscreen):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetParameters):
+        (WebCore::MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isScrubbing):
+
 2015-11-09  Alex Christensen  <achristensen@webkit.org>
 
         XHR timeouts should not fire if there is an immediate network error.
index 249bb17..831520d 100644 (file)
 #include "CachedResourceLoader.h"
 #include "FrameView.h"
 #include "GraphicsContext.h"
+#include "HWndDC.h"
 #include "HostWindow.h"
 #include "NotImplemented.h"
+#if USE(CAIRO)
+#include "PlatformContextCairo.h"
+#endif
 #include "SoftLinking.h"
 
 #if USE(MEDIA_FOUNDATION)
@@ -46,10 +50,45 @@ SOFT_LINK_OPTIONAL(Mf, MFCreateTopologyNode, HRESULT, STDAPICALLTYPE, (MF_TOPOLO
 SOFT_LINK_OPTIONAL(Mf, MFGetService, HRESULT, STDAPICALLTYPE, (IUnknown*, REFGUID, REFIID, LPVOID*));
 SOFT_LINK_OPTIONAL(Mf, MFCreateAudioRendererActivate, HRESULT, STDAPICALLTYPE, (IMFActivate**));
 SOFT_LINK_OPTIONAL(Mf, MFCreateVideoRendererActivate, HRESULT, STDAPICALLTYPE, (HWND, IMFActivate**));
+SOFT_LINK_OPTIONAL(Mf, MFCreateSampleGrabberSinkActivate, HRESULT, STDAPICALLTYPE, (IMFMediaType*, IMFSampleGrabberSinkCallback*, IMFActivate**));
 
 SOFT_LINK_LIBRARY(Mfplat);
 SOFT_LINK_OPTIONAL(Mfplat, MFStartup, HRESULT, STDAPICALLTYPE, (ULONG, DWORD));
 SOFT_LINK_OPTIONAL(Mfplat, MFShutdown, HRESULT, STDAPICALLTYPE, ());
+SOFT_LINK_OPTIONAL(Mfplat, MFCreateMemoryBuffer, HRESULT, STDAPICALLTYPE, (DWORD, IMFMediaBuffer**));
+SOFT_LINK_OPTIONAL(Mfplat, MFCreateSample, HRESULT, STDAPICALLTYPE, (IMFSample**));
+SOFT_LINK_OPTIONAL(Mfplat, MFCreateMediaType, HRESULT, STDAPICALLTYPE, (IMFMediaType**));
+SOFT_LINK_OPTIONAL(Mfplat, MFFrameRateToAverageTimePerFrame, HRESULT, STDAPICALLTYPE, (UINT32, UINT32, UINT64*));
+
+STDAPI MFCreateMediaType(_Outptr_ IMFMediaType**  ppMFType)
+{
+    return MFCreateMediaTypePtr()(ppMFType);
+}
+
+SOFT_LINK_LIBRARY(evr);
+SOFT_LINK_OPTIONAL(evr, MFCreateVideoSampleFromSurface, HRESULT, STDAPICALLTYPE, (IUnknown*, IMFSample**));
+
+SOFT_LINK_LIBRARY(Dxva2);
+SOFT_LINK_OPTIONAL(Dxva2, DXVA2CreateDirect3DDeviceManager9, HRESULT, STDAPICALLTYPE, (UINT*, IDirect3DDeviceManager9**));
+
+SOFT_LINK_LIBRARY(D3d9);
+SOFT_LINK_OPTIONAL(D3d9, Direct3DCreate9Ex, HRESULT, STDAPICALLTYPE, (UINT, IDirect3D9Ex**));
+
+// MFSamplePresenterSampleCounter
+// Data type: UINT32
+//
+// Version number for the video samples. When the presenter increments the version
+// number, all samples with the previous version number are stale and should be
+// discarded.
+static const GUID MFSamplePresenterSampleCounter =
+{ 0x869f1f7c, 0x3496, 0x48a9, { 0x88, 0xe3, 0x69, 0x85, 0x79, 0xd0, 0x8c, 0xb6 } };
+
+// MFSamplePresenterSampleSwapChain
+// Data type: IUNKNOWN
+// 
+// Pointer to a Direct3D swap chain.
+static const GUID MFSamplePresenterSampleSwapChain =
+{ 0x24a2e076, 0x3673, 0x433d, { 0x87, 0x4, 0x55, 0x2b, 0x1f, 0x5c, 0x16, 0x8c } };
 
 namespace WebCore {
 
@@ -77,9 +116,10 @@ MediaPlayerPrivateMediaFoundation::~MediaPlayerPrivateMediaFoundation()
 
 void MediaPlayerPrivateMediaFoundation::registerMediaEngine(MediaEngineRegistrar registrar)
 {
-    if (isAvailable())
+    if (isAvailable()) {
         registrar([](MediaPlayer* player) { return std::make_unique<MediaPlayerPrivateMediaFoundation>(player); },
             getSupportedTypes, supportsType, 0, 0, 0, 0);
+    }
 }
 
 bool MediaPlayerPrivateMediaFoundation::isAvailable() 
@@ -252,7 +292,7 @@ void MediaPlayerPrivateMediaFoundation::setSize(const IntSize& size)
     int w = m_size.width() * deviceScaleFactor;
     int h = m_size.height() * deviceScaleFactor;
 
-    if (m_hwndVideo && !m_lastPaintRect.isEmpty())
+    if (m_hwndVideo)
         ::MoveWindow(m_hwndVideo, x, y, w, h, FALSE);
 
     RECT rc = { 0, 0, w, h };
@@ -261,16 +301,13 @@ void MediaPlayerPrivateMediaFoundation::setSize(const IntSize& size)
 
 void MediaPlayerPrivateMediaFoundation::paint(GraphicsContext& context, const FloatRect& rect)
 {
-    if (context.paintingDisabled()
-        || !m_player->visible())
+    if (context.paintingDisabled() || !m_player->visible())
         return;
 
     m_lastPaintRect = rect;
 
-    // We currently let Media Foundation handle the drawing, by providing a handle to the window to draw in.
-    // We should instead read individual frames from the stream, and paint them into the graphics context here.
-
-    notImplemented();
+    if (m_presenter)
+        m_presenter->paintCurrentFrame(context, rect);
 }
 
 bool MediaPlayerPrivateMediaFoundation::createSession()
@@ -384,6 +421,9 @@ bool MediaPlayerPrivateMediaFoundation::endGetEvent(IMFAsyncResult* asyncResult)
 
     case MESessionClosed:
         break;
+
+    case MEMediaSample:
+        break;
     }
 
     if (mediaEventType != MESessionClosed) {
@@ -510,7 +550,7 @@ void MediaPlayerPrivateMediaFoundation::createVideoWindow()
         return;
     hWndParent = view->hostWindow()->platformPageClient();
 
-    m_hwndVideo = CreateWindowEx(WS_EX_NOACTIVATE | WS_EX_TRANSPARENT, registerVideoWindowClass(), 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+    m_hwndVideo = CreateWindowEx(WS_EX_NOACTIVATE | WS_EX_TRANSPARENT, registerVideoWindowClass(), 0, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
         0, 0, 0, 0, hWndParent, 0, 0, 0);
 }
 
@@ -522,6 +562,18 @@ void MediaPlayerPrivateMediaFoundation::destroyVideoWindow()
     }
 }
 
+void MediaPlayerPrivateMediaFoundation::invalidateFrameView()
+{
+    FrameView* view = nullptr;
+    if (!m_player || !m_player->cachedResourceLoader() || !m_player->cachedResourceLoader()->document())
+        return;
+    view = m_player->cachedResourceLoader()->document()->view();
+    if (!view)
+        return;
+
+    view->invalidate();
+}
+
 void MediaPlayerPrivateMediaFoundation::addListener(MediaPlayerListener* listener)
 {
     LockHolder locker(m_mutexListeners);
@@ -579,7 +631,12 @@ bool MediaPlayerPrivateMediaFoundation::createOutputNode(COMPtr<IMFStreamDescrip
         m_hasAudio = true;
     } else if (MFMediaType_Video == guidMajorType) {
         // Create the video renderer.
-        if (FAILED(MFCreateVideoRendererActivatePtr()(m_hwndVideo, &rendererActivate)))
+        if (FAILED(MFCreateVideoRendererActivatePtr()(nullptr, &rendererActivate)))
+            return false;
+
+        m_presenter = new CustomVideoPresenter(this);
+        m_presenter->SetVideoWindow(m_hwndVideo);
+        if (FAILED(rendererActivate->SetUnknown(MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE, static_cast<IMFActivate*>(m_presenter.get()))))
             return false;
         m_hasVideo = true;
     } else
@@ -638,13 +695,11 @@ void MediaPlayerPrivateMediaFoundation::onTopologySet()
     if (!MFGetServicePtr())
         return;
 
-    if (FAILED(MFGetServicePtr()(m_mediaSession.get(), MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_videoDisplay))))
-        return;
-
-    ASSERT(m_videoDisplay);
-
-    RECT rc = { 0, 0, m_size.width(), m_size.height() };
-    m_videoDisplay->SetVideoPosition(nullptr, &rc);
+    if (SUCCEEDED(MFGetServicePtr()(m_mediaSession.get(), MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_videoDisplay)))) {
+        ASSERT(m_videoDisplay);
+        RECT rc = { 0, 0, m_size.width(), m_size.height() };
+        m_videoDisplay->SetVideoPosition(nullptr, &rc);
+    }
 
     m_readyState = MediaPlayer::HaveFutureData;
 
@@ -724,6 +779,2088 @@ void MediaPlayerPrivateMediaFoundation::AsyncCallback::onMediaPlayerDeleted()
     m_mediaPlayer = nullptr;
 }
 
+MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::CustomVideoPresenter(MediaPlayerPrivateMediaFoundation* mediaPlayer)
+    : m_mediaPlayer(mediaPlayer)
+{
+    if (m_mediaPlayer)
+        m_mediaPlayer->addListener(this);
+
+    m_sourceRect.top = 0;
+    m_sourceRect.left = 0;
+    m_sourceRect.bottom = 1;
+    m_sourceRect.right = 1;
+
+    m_presenterEngine = std::make_unique<Direct3DPresenter>();
+    if (!m_presenterEngine)
+        return;
+
+    m_scheduler.setPresenter(m_presenterEngine.get());
+}
+
+MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::~CustomVideoPresenter()
+{
+    if (m_mediaPlayer)
+        m_mediaPlayer->removeListener(this);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject)
+{
+    *ppvObject = nullptr;
+    if (IsEqualGUID(riid, IID_IMFGetService))
+        *ppvObject = static_cast<IMFGetService*>(this);
+    else if (IsEqualGUID(riid, IID_IMFActivate))
+        *ppvObject = static_cast<IMFActivate*>(this);
+    else if (IsEqualGUID(riid, IID_IMFVideoDisplayControl))
+        *ppvObject = static_cast<IMFVideoDisplayControl*>(this);
+    else if (IsEqualGUID(riid, IID_IMFVideoPresenter))
+        *ppvObject = static_cast<IMFVideoPresenter*>(this);
+    else if (IsEqualGUID(riid, IID_IMFClockStateSink))
+        *ppvObject = static_cast<IMFClockStateSink*>(this);
+    else if (IsEqualGUID(riid, IID_IMFVideoDeviceID))
+        *ppvObject = static_cast<IMFVideoDeviceID*>(this);
+    else if (IsEqualGUID(riid, IID_IMFTopologyServiceLookupClient))
+        *ppvObject = static_cast<IMFTopologyServiceLookupClient*>(this);
+    else if (IsEqualGUID(riid, IID_IUnknown))
+        *ppvObject = static_cast<IMFVideoPresenter*>(this);
+    else if (IsEqualGUID(riid, IID_IMFAsyncCallback))
+        *ppvObject = static_cast<IMFAsyncCallback*>(this);
+    else
+        return E_NOINTERFACE;
+
+    AddRef();
+    return S_OK;
+}
+
+ULONG MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::AddRef()
+{
+    m_refCount++;
+    return m_refCount;
+}
+
+ULONG MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Release()
+{
+    m_refCount--;
+    ULONG refCount = m_refCount;
+    if (!refCount)
+        delete this;
+    return refCount;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset)
+{
+    LockHolder locker(m_lock);
+
+    // After shutdown, we cannot start.
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    m_renderState = RenderStateStarted;
+
+    if (isActive()) {
+        if (llClockStartOffset != PRESENTATION_CURRENT_POSITION) {
+            // This is a seek request, flush pending samples.
+            flush();
+        }
+    }
+
+    processOutputLoop();
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockStop(MFTIME hnsSystemTime)
+{
+    LockHolder locker(m_lock);
+
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    if (m_renderState != RenderStateStopped) {
+        m_renderState = RenderStateStopped;
+        flush();
+    }
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockPause(MFTIME hnsSystemTime)
+{
+    LockHolder locker(m_lock);
+
+    // After shutdown, we cannot pause.
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    m_renderState = RenderStatePaused;
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockRestart(MFTIME hnsSystemTime)
+{
+    LockHolder locker(m_lock);
+
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    ASSERT(m_renderState == RenderStatePaused);
+
+    m_renderState = RenderStateStarted;
+
+    processOutputLoop();
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::OnClockSetRate(MFTIME hnsSystemTime, float rate)
+{
+    LockHolder locker(m_lock);
+
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    m_rate = rate;
+
+    m_scheduler.setClockRate(rate);
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ProcessMessage(MFVP_MESSAGE_TYPE eMessage, ULONG_PTR ulParam)
+{
+    LockHolder locker(m_lock);
+
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    switch (eMessage) {
+    case MFVP_MESSAGE_FLUSH:
+        hr = flush();
+        break;
+
+    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
+        hr = renegotiateMediaType();
+        break;
+
+    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
+        // A new input sample is available. 
+        hr = processInputNotify();
+        break;
+
+    case MFVP_MESSAGE_BEGINSTREAMING:
+        hr = beginStreaming();
+        break;
+
+    case MFVP_MESSAGE_ENDSTREAMING:
+        hr = endStreaming();
+        break;
+
+    case MFVP_MESSAGE_ENDOFSTREAM:
+        m_endStreaming = true;
+        hr = checkEndOfStream();
+        break;
+
+    default:
+        hr = E_INVALIDARG;
+        break;
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetCurrentMediaType(_Outptr_  IMFVideoMediaType **ppMediaType)
+{
+    LockHolder locker(m_lock);
+
+    if (!ppMediaType)
+        return E_POINTER;
+
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    if (!m_mediaType)
+        return MF_E_NOT_INITIALIZED;
+
+    return m_mediaType->QueryInterface(__uuidof(IMFVideoMediaType), (void**)&ppMediaType);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetDeviceID(IID* pDeviceID)
+{
+    if (!pDeviceID)
+        return E_POINTER;
+
+    *pDeviceID = __uuidof(IDirect3DDevice9);
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::InitServicePointers(IMFTopologyServiceLookup *pLookup)
+{
+    if (!pLookup)
+        return E_POINTER;
+
+    HRESULT hr = S_OK;
+
+    LockHolder locker(m_lock);
+
+    if (isActive())
+        return MF_E_INVALIDREQUEST;
+
+    m_clock = nullptr;
+    m_mixer = nullptr;
+    m_mediaEventSink = nullptr;
+
+    // Lookup the services.
+
+    DWORD objectCount = 1;
+    hr = pLookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0, MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_clock), &objectCount);
+    // The clock service is optional.
+
+    objectCount = 1;
+    hr = pLookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0, MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_mixer), &objectCount);
+    if (FAILED(hr))
+        return hr;
+
+    hr = configureMixer(m_mixer.get());
+    if (FAILED(hr))
+        return hr;
+
+    objectCount = 1;
+    hr = pLookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0, MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_mediaEventSink), &objectCount);
+    if (FAILED(hr))
+        return hr;
+
+    m_renderState = RenderStateStopped;
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ReleaseServicePointers()
+{
+    LockHolder locker(m_lock);
+
+    m_renderState = RenderStateShutdown;
+
+    flush();
+
+    setMediaType(nullptr);
+
+    m_clock = nullptr;
+    m_mixer = nullptr;
+    m_mediaEventSink = nullptr;
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetService(REFGUID guidService, REFIID riid, LPVOID* ppvObject)
+{
+    if (!ppvObject)
+        return E_POINTER;
+
+    // We only support MR_VIDEO_RENDER_SERVICE.
+    if (guidService != MR_VIDEO_RENDER_SERVICE)
+        return MF_E_UNSUPPORTED_SERVICE;
+
+    HRESULT hr = m_presenterEngine->getService(guidService, riid, ppvObject);
+
+    if (FAILED(hr))
+        hr = QueryInterface(riid, ppvObject);
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ActivateObject(REFIID riid, void **ppv)
+{
+    if (!ppv)
+        return E_POINTER;
+
+    if (riid == IID_IMFVideoPresenter) {
+        *ppv = static_cast<IMFVideoPresenter*>(this);
+        return S_OK;
+    }
+    return E_FAIL;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::DetachObject()
+{
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::ShutdownObject()
+{
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetVideoWindow(HWND hwndVideo)
+{
+    LockHolder locker(m_lock);
+
+    if (!IsWindow(hwndVideo))
+        return E_INVALIDARG;
+
+    HRESULT hr = S_OK;
+    HWND oldHwnd = m_presenterEngine->getVideoWindow();
+
+    if (oldHwnd != hwndVideo) {
+        // This will create a new Direct3D device.
+        hr = m_presenterEngine->setVideoWindow(hwndVideo);
+
+        notifyEvent(EC_DISPLAY_CHANGED, 0, 0);
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetVideoWindow(HWND* phwndVideo)
+{
+    LockHolder locker(m_lock);
+
+    if (!phwndVideo)
+        return E_POINTER;
+
+    *phwndVideo = m_presenterEngine->getVideoWindow();
+
+    return S_OK;
+}
+
+static HRESULT setMixerSourceRect(IMFTransform* mixer, const MFVideoNormalizedRect& sourceRect)
+{
+    if (!mixer)
+        return E_POINTER;
+
+    COMPtr<IMFAttributes> attributes;
+
+    HRESULT hr = mixer->GetAttributes(&attributes);
+    if (FAILED(hr))
+        return hr;
+
+    return attributes->SetBlob(VIDEO_ZOOM_RECT, (const UINT8*)&sourceRect, sizeof(sourceRect));
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::SetVideoPosition(const MFVideoNormalizedRect* pnrcSource, const LPRECT prcDest)
+{
+    LockHolder locker(m_lock);
+
+    // First, check that the parameters are valid.
+
+    if (!pnrcSource && !prcDest)
+        return E_POINTER;
+
+    if (pnrcSource) {
+        if ((pnrcSource->left > pnrcSource->right) || (pnrcSource->top > pnrcSource->bottom))
+            return E_INVALIDARG;
+
+        // The source rectangle must be normalized.
+        if ((pnrcSource->left < 0) || (pnrcSource->right > 1) || (pnrcSource->top < 0) || (pnrcSource->bottom > 1))
+            return E_INVALIDARG;
+    }
+
+    if (prcDest) {
+        if ((prcDest->left > prcDest->right) || (prcDest->top > prcDest->bottom))
+            return E_INVALIDARG;
+    }
+
+    HRESULT hr = S_OK;
+
+    // Set the source rectangle.
+    if (pnrcSource) {
+        m_sourceRect = *pnrcSource;
+
+        if (m_mixer) {
+            hr = setMixerSourceRect(m_mixer.get(), m_sourceRect);
+            if (FAILED(hr))
+                return hr;
+        }
+    }
+
+    // Set the destination rectangle.
+    if (prcDest) {
+        RECT rcOldDest = m_presenterEngine->getDestinationRect();
+
+        // If the destination rectangle hasn't changed, we are done.
+        if (!EqualRect(&rcOldDest, prcDest)) {
+            hr = m_presenterEngine->setDestinationRect(*prcDest);
+            if (FAILED(hr))
+                return hr;
+
+            // We need to change the media type when the destination rectangle has changed.
+            if (m_mixer) {
+                hr = renegotiateMediaType();
+                if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) {
+                    // This is not a critical failure; the EVR will let us know when
+                    // we have to set the mixer media type.
+                    hr = S_OK;
+                } else {
+                    if (FAILED(hr))
+                        return hr;
+
+                    // We have successfully changed the media type,
+                    // ask for a repaint of the current frame.
+                    m_repaint = true;
+                    processOutput();
+                }
+            }
+        }
+    }
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::GetVideoPosition(MFVideoNormalizedRect* pnrcSource, LPRECT prcDest)
+{
+    LockHolder locker(m_lock);
+
+    if (!pnrcSource || !prcDest)
+        return E_POINTER;
+
+    *pnrcSource = m_sourceRect;
+    *prcDest = m_presenterEngine->getDestinationRect();
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::RepaintVideo()
+{
+    LockHolder locker(m_lock);
+
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr))
+        return hr;
+
+    // Check that at least one sample has been presented.
+    if (m_prerolled) {
+        m_repaint = true;
+        processOutput();
+    }
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::Invoke(IMFAsyncResult* pAsyncResult)
+{
+    return onSampleFree(pAsyncResult);
 }
 
+void MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::onMediaPlayerDeleted()
+{
+    m_mediaPlayer = nullptr;
+}
+
+void MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::paintCurrentFrame(GraphicsContext& context, const FloatRect& r)
+{
+    if (m_presenterEngine)
+        m_presenterEngine->paintCurrentFrame(context, r);
+}
+
+bool MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isActive() const
+{
+    return ((m_renderState == RenderStateStarted) || (m_renderState == RenderStatePaused));
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::configureMixer(IMFTransform* mixer)
+{
+    COMPtr<IMFVideoDeviceID> videoDeviceID;
+    HRESULT hr = mixer->QueryInterface(__uuidof(IMFVideoDeviceID), (void**)&videoDeviceID);
+    if (FAILED(hr))
+        return hr;
+
+    IID deviceID = GUID_NULL;
+    hr = videoDeviceID->GetDeviceID(&deviceID);
+    if (FAILED(hr))
+        return hr;
+
+    // The mixer must have this device ID.
+    if (!IsEqualGUID(deviceID, __uuidof(IDirect3DDevice9)))
+        return MF_E_INVALIDREQUEST;
+
+    setMixerSourceRect(mixer, m_sourceRect);
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::flush()
+{
+    m_prerolled = false;
+
+    // Flush the sceduler.
+    // This call will block until the scheduler thread has finished flushing.
+    m_scheduler.flush();
+
+    if (m_renderState == RenderStateStopped)
+        m_presenterEngine->presentSample(nullptr, 0);
+
+    return S_OK;
+}
+
+static bool areMediaTypesEqual(IMFMediaType* type1, IMFMediaType* type2)
+{
+    if (!type1 && !type2)
+        return true;
+    if (!type1 || !type2)
+        return false;
+
+    DWORD flags = 0;
+    return S_OK == type1->IsEqual(type2, &flags);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::setMediaType(IMFMediaType* mediaType)
+{
+    if (!mediaType) {
+        m_mediaType = nullptr;
+        releaseResources();
+        return S_OK;
+    }
+
+    // If we have shut down, we cannot set the media type.
+    HRESULT hr = checkShutdown();
+    if (FAILED(hr)) {
+        releaseResources();
+        return hr;
+    }
+
+    if (areMediaTypesEqual(m_mediaType.get(), mediaType))
+        return S_OK;
+
+    m_mediaType = nullptr;
+    releaseResources();
+
+    // Get allocated samples from the presenter.
+    VideoSampleList sampleQueue;
+    hr = m_presenterEngine->createVideoSamples(mediaType, sampleQueue);
+    if (FAILED(hr)) {
+        releaseResources();
+        return hr;
+    }
+
+    // Set the token counter on each sample.
+    // This will help us to determine when they are invalid, and can be released.
+    for (auto sample : sampleQueue) {
+        hr = sample->SetUINT32(MFSamplePresenterSampleCounter, m_tokenCounter);
+        if (FAILED(hr)) {
+            releaseResources();
+            return hr;
+        }
+    }
+
+    // Add the samples to the sample pool.
+    hr = m_samplePool.initialize(sampleQueue);
+    if (FAILED(hr)) {
+        releaseResources();
+        return hr;
+    }
+
+    // Set the frame rate. 
+    MFRatio fps = { 0, 0 };
+    hr = MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, (UINT32*)&fps.Numerator, (UINT32*)&fps.Denominator);
+    if (SUCCEEDED(hr) && fps.Numerator && fps.Denominator)
+        m_scheduler.setFrameRate(fps);
+    else {
+        // We could not get the frame ret, use default.
+        const MFRatio defaultFrameRate = { 30, 1 };
+        m_scheduler.setFrameRate(defaultFrameRate);
+    }
+
+    ASSERT(mediaType);
+    m_mediaType = mediaType;
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::checkShutdown() const
+{
+    if (m_renderState == RenderStateShutdown)
+        return MF_E_SHUTDOWN;
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::renegotiateMediaType()
+{
+    HRESULT hr = S_OK;
+
+    if (!m_mixer)
+        return MF_E_INVALIDREQUEST;
+
+    // Iterate over the available output types of the mixer.
+
+    DWORD typeIndex = 0;
+    bool foundMediaType = false;
+    while (!foundMediaType && (hr != MF_E_NO_MORE_TYPES)) {
+        // Get the next available media type.
+        COMPtr<IMFMediaType> mixerType;
+        hr = m_mixer->GetOutputAvailableType(0, typeIndex++, &mixerType);
+        if (FAILED(hr))
+            break;
+
+        // Do we support this media type?
+        hr = isMediaTypeSupported(mixerType.get());
+        if (FAILED(hr))
+            break;
+
+        // Make adjustments to proposed media type.
+        COMPtr<IMFMediaType> optimalType;
+        hr = createOptimalVideoType(mixerType.get(), &optimalType);
+        if (FAILED(hr))
+            break;
+
+        // Test whether the mixer can accept the modified media type
+        hr = m_mixer->SetOutputType(0, optimalType.get(), MFT_SET_TYPE_TEST_ONLY);
+        if (FAILED(hr))
+            break;
+
+        // Try to set the new media type
+
+        hr = setMediaType(optimalType.get());
+        if (FAILED(hr))
+            break;
+
+        hr = m_mixer->SetOutputType(0, optimalType.get(), 0);
+
+        ASSERT(SUCCEEDED(hr));
+
+        if (FAILED(hr))
+            setMediaType(nullptr);
+        else
+            foundMediaType = true;
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processInputNotify()
+{
+    // We have a new sample.
+    m_sampleNotify = true;
+
+    if (!m_mediaType) {
+        // The media type is not valid.
+        return MF_E_TRANSFORM_TYPE_NOT_SET;
+    } 
+    
+    // Invalidate the video area
+    if (m_mediaPlayer) {
+        auto weakPtr = m_mediaPlayer->m_weakPtrFactory.createWeakPtr();
+        callOnMainThread([weakPtr] {
+            if (weakPtr)
+                weakPtr->invalidateFrameView();
+        });
+    }
+
+    // Process sample
+    processOutputLoop();
+
+    return S_OK;
+}
+
+static float MFOffsetToFloat(const MFOffset& offset)
+{
+    const int denominator = std::numeric_limits<WORD>::max() + 1;
+    return offset.value + (float(offset.fract) / denominator);
+}
+
+static MFOffset MakeOffset(float v)
+{
+    // v = offset.value + (offset.fract / denominator), where denominator = 65536.0f.
+    const int denominator = std::numeric_limits<WORD>::max() + 1;
+    MFOffset offset;
+    offset.value = short(v);
+    offset.fract = WORD(denominator * (v - offset.value));
+    return offset;
+}
+
+static MFVideoArea MakeArea(float x, float y, DWORD width, DWORD height)
+{
+    MFVideoArea area;
+    area.OffsetX = MakeOffset(x);
+    area.OffsetY = MakeOffset(y);
+    area.Area.cx = width;
+    area.Area.cy = height;
+    return area;
+}
+
+static HRESULT validateVideoArea(const MFVideoArea& area, UINT32 width, UINT32 height)
+{
+    float fOffsetX = MFOffsetToFloat(area.OffsetX);
+    float fOffsetY = MFOffsetToFloat(area.OffsetY);
+
+    if (((LONG)fOffsetX + area.Area.cx > width) || ((LONG)fOffsetY + area.Area.cy > height))
+        return MF_E_INVALIDMEDIATYPE;
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::beginStreaming()
+{
+    return m_scheduler.startScheduler(m_clock.get());
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::endStreaming()
+{
+    return m_scheduler.stopScheduler();
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::checkEndOfStream()
+{
+    if (!m_endStreaming) {
+        // We have not received the end-of-stream message from the EVR.
+        return S_OK;
+    }
+
+    if (m_sampleNotify) {
+        // There is still input samples available for the mixer. 
+        return S_OK;
+    }
+
+    if (m_samplePool.areSamplesPending()) {
+        // There are samples scheduled for rendering.
+        return S_OK;
+    }
+
+    // We are done, notify the EVR.
+    notifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
+    m_endStreaming = false;
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::isMediaTypeSupported(IMFMediaType* mediaType)
+{
+    COMPtr<IMFMediaType> proposedVideoType = mediaType;
+
+    // We don't support compressed media types.
+    BOOL compressed = FALSE;
+    HRESULT hr = proposedVideoType->IsCompressedFormat(&compressed);
+    if (FAILED(hr))
+        return hr;
+    if (compressed)
+        return MF_E_INVALIDMEDIATYPE;
+
+    // Validate the format.
+    GUID guidSubType = GUID_NULL;
+    hr = proposedVideoType->GetGUID(MF_MT_SUBTYPE, &guidSubType);
+    if (FAILED(hr))
+        return hr;
+    D3DFORMAT d3dFormat = (D3DFORMAT)guidSubType.Data1;
+
+    // Check if the format can be used as backbuffer format.
+    hr = m_presenterEngine->checkFormat(d3dFormat);
+    if (FAILED(hr))
+        return hr;
+
+    // Check interlaced formats.
+    MFVideoInterlaceMode interlaceMode = MFVideoInterlace_Unknown;
+    hr = proposedVideoType->GetUINT32(MF_MT_INTERLACE_MODE, (UINT32*)&interlaceMode);
+    if (FAILED(hr))
+        return hr;
+
+    if (interlaceMode != MFVideoInterlace_Progressive)
+        return MF_E_INVALIDMEDIATYPE;
+
+    UINT32 width = 0, height = 0;
+    hr = MFGetAttributeSize(proposedVideoType.get(), MF_MT_FRAME_SIZE, &width, &height);
+    if (FAILED(hr))
+        return hr;
+
+    // Validate apertures.
+    MFVideoArea videoCropArea;
+    if (SUCCEEDED(proposedVideoType->GetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)&videoCropArea, sizeof(MFVideoArea), nullptr)))
+        validateVideoArea(videoCropArea, width, height);
+    if (SUCCEEDED(proposedVideoType->GetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&videoCropArea, sizeof(MFVideoArea), nullptr)))
+        validateVideoArea(videoCropArea, width, height);
+    if (SUCCEEDED(proposedVideoType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)&videoCropArea, sizeof(MFVideoArea), nullptr)))
+        validateVideoArea(videoCropArea, width, height);
+    
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::createOptimalVideoType(IMFMediaType* proposedType, IMFMediaType** optimalType)
+{
+    COMPtr<IMFMediaType> optimalVideoType;
+    HRESULT hr = MFCreateMediaType(&optimalVideoType);
+    if (FAILED(hr))
+        return hr;
+    hr = optimalVideoType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+    if (FAILED(hr))
+        return hr;
+
+    hr = proposedType->CopyAllItems(optimalVideoType.get());
+    if (FAILED(hr))
+        return hr;
+
+    // We now modify the new media type.
+
+    // We assume that the monitor's pixel aspect ratio is 1:1,
+    // and that the pixel aspect ratio is preserved by the presenter.
+    hr = MFSetAttributeRatio(optimalVideoType.get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
+    if (FAILED(hr))
+        return hr;
+
+    // Get the output rectangle.
+    RECT rcOutput = m_presenterEngine->getDestinationRect();
+    if (IsRectEmpty(&rcOutput)) {
+        hr = calculateOutputRectangle(proposedType, rcOutput);
+        if (FAILED(hr))
+            return hr;
+    }
+
+    hr = optimalVideoType->SetUINT32(MF_MT_YUV_MATRIX, MFVideoTransferMatrix_BT709);
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetUINT32(MF_MT_TRANSFER_FUNCTION, MFVideoTransFunc_709);
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetUINT32(MF_MT_VIDEO_PRIMARIES, MFVideoPrimaries_BT709);
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_16_235);
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetUINT32(MF_MT_VIDEO_LIGHTING, MFVideoLighting_dim);
+    if (FAILED(hr))
+        return hr;
+
+    hr = MFSetAttributeSize(optimalVideoType.get(), MF_MT_FRAME_SIZE, rcOutput.right, rcOutput.bottom);
+    if (FAILED(hr))
+        return hr;
+
+    MFVideoArea displayArea = MakeArea(0, 0, rcOutput.right, rcOutput.bottom);
+
+    hr = optimalVideoType->SetUINT32(MF_MT_PAN_SCAN_ENABLED, FALSE);
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&displayArea, sizeof(MFVideoArea));
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)&displayArea, sizeof(MFVideoArea));
+    if (FAILED(hr))
+        return hr;
+
+    hr = optimalVideoType->SetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)&displayArea, sizeof(MFVideoArea));
+    if (FAILED(hr))
+        return hr;
+
+    *optimalType = optimalVideoType.leakRef();
+
+    return S_OK;
+}
+
+static RECT correctAspectRatio(const RECT& src, const MFRatio& srcPAR, const MFRatio& destPAR)
+{
+    RECT rc = { 0, 0, src.right - src.left, src.bottom - src.top };
+
+    if ((srcPAR.Numerator * destPAR.Denominator) != (srcPAR.Denominator * destPAR.Numerator)) {
+        // The source and destination aspect ratios are different
+
+        // Transform the source aspect ratio to 1:1
+        if (srcPAR.Numerator > srcPAR.Denominator)
+            rc.right = MulDiv(rc.right, srcPAR.Numerator, srcPAR.Denominator);
+        else if (srcPAR.Numerator < srcPAR.Denominator)
+            rc.bottom = MulDiv(rc.bottom, srcPAR.Denominator, srcPAR.Numerator);
+
+
+        // Transform to destination aspect ratio.
+        if (destPAR.Numerator > destPAR.Denominator)
+            rc.bottom = MulDiv(rc.bottom, destPAR.Numerator, destPAR.Denominator);
+        else if (destPAR.Numerator < destPAR.Denominator)
+            rc.right = MulDiv(rc.right, destPAR.Denominator, destPAR.Numerator);
+
+    }
+
+    return rc;
+}
+
+static HRESULT GetVideoDisplayArea(IMFMediaType* type, MFVideoArea* area)
+{
+    if (!type || !area)
+        return E_POINTER;
+
+    HRESULT hr = S_OK;
+    UINT32 width = 0, height = 0;
+
+    BOOL bPanScan = MFGetAttributeUINT32(type, MF_MT_PAN_SCAN_ENABLED, FALSE);
+
+    if (bPanScan)
+        hr = type->GetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)area, sizeof(MFVideoArea), nullptr);
+
+    if (!bPanScan || hr == MF_E_ATTRIBUTENOTFOUND) {
+        hr = type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)area, sizeof(MFVideoArea), nullptr);
+
+        if (hr == MF_E_ATTRIBUTENOTFOUND)
+            hr = type->GetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)area, sizeof(MFVideoArea), nullptr);
+
+        if (hr == MF_E_ATTRIBUTENOTFOUND) {
+            hr = MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width, &height);
+            if (SUCCEEDED(hr))
+                *area = MakeArea(0.0, 0.0, width, height);
+        }
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::calculateOutputRectangle(IMFMediaType* proposedType, RECT& outputRect)
+{
+    COMPtr<IMFMediaType> proposedVideoType = proposedType;
+
+    UINT32 srcWidth = 0, srcHeight = 0;
+    HRESULT hr = MFGetAttributeSize(proposedVideoType.get(), MF_MT_FRAME_SIZE, &srcWidth, &srcHeight);
+    if (FAILED(hr))
+        return hr;
+
+    MFVideoArea displayArea;
+    ZeroMemory(&displayArea, sizeof(displayArea));
+
+    hr = GetVideoDisplayArea(proposedVideoType.get(), &displayArea);
+    if (FAILED(hr))
+        return hr;
+
+    LONG offsetX = (LONG)MFOffsetToFloat(displayArea.OffsetX);
+    LONG offsetY = (LONG)MFOffsetToFloat(displayArea.OffsetY);
+
+    // Check if the display area is valid.
+    // If it is valid, we use it. If not, we use the frame dimensions.
+
+    RECT rcOutput;
+
+    if (displayArea.Area.cx != 0
+        && displayArea.Area.cy != 0
+        && offsetX + displayArea.Area.cx <= srcWidth
+        && offsetY + displayArea.Area.cy <= srcHeight) {
+        rcOutput.left = offsetX;
+        rcOutput.right = offsetX + displayArea.Area.cx;
+        rcOutput.top = offsetY;
+        rcOutput.bottom = offsetY + displayArea.Area.cy;
+    } else {
+        rcOutput.left = 0;
+        rcOutput.top = 0;
+        rcOutput.right = srcWidth;
+        rcOutput.bottom = srcHeight;
+    }
+
+    // Correct aspect ratio.
+
+    MFRatio inputPAR = { 1, 1 };
+    MFRatio outputPAR = { 1, 1 }; // We assume the monitor's pixels are square.
+    MFGetAttributeRatio(proposedVideoType.get(), MF_MT_PIXEL_ASPECT_RATIO, (UINT32*)&inputPAR.Numerator, (UINT32*)&inputPAR.Denominator);
+    outputRect = correctAspectRatio(rcOutput, inputPAR, outputPAR);
+
+    return S_OK;
+}
+
+void MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processOutputLoop()
+{
+    // Get video frames from the mixer and schedule them for presentation.
+    HRESULT hr = S_OK;
+
+    while (hr == S_OK) {
+        if (!m_sampleNotify) {
+            // Currently no more input samples.
+            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
+            break;
+        }
+
+        // We break from the loop if we fail to process a sample.
+        hr = processOutput();
+    }
+
+    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
+        checkEndOfStream();
+}
+
+static HRESULT setDesiredSampleTime(IMFSample* sample, const LONGLONG& sampleTime, const LONGLONG& duration)
+{
+    // To tell the mixer to give us an earlier frame for repainting, we can set the desired sample time.
+    // We have to clear the desired sample time before reusing the sample.
+
+    if (!sample)
+        return E_POINTER;
+
+    COMPtr<IMFDesiredSample> desired;
+
+    HRESULT hr = sample->QueryInterface(__uuidof(IMFDesiredSample), (void**)&desired);
+
+    if (SUCCEEDED(hr))
+        desired->SetDesiredSampleTimeAndDuration(sampleTime, duration);
+
+    return hr;
+}
+
+static HRESULT clearDesiredSampleTime(IMFSample* sample)
+{
+    if (!sample)
+        return E_POINTER;
+
+    // We need to retrieve some attributes we have set on the sample before we call 
+    // IMFDesiredSample::Clear(), and set them once more, since they are cleared by
+    // the Clear() call.
+
+    UINT32 counter = MFGetAttributeUINT32(sample, MFSamplePresenterSampleCounter, (UINT32)-1);
+
+    COMPtr<IUnknown> swapChain;
+    sample->GetUnknown(MFSamplePresenterSampleSwapChain, IID_IUnknown, (void**)&swapChain);
+
+    COMPtr<IMFDesiredSample> desired;
+    HRESULT hr = sample->QueryInterface(__uuidof(IMFDesiredSample), (void**)&desired);
+    if (SUCCEEDED(hr)) {
+        desired->Clear();
+
+        hr = sample->SetUINT32(MFSamplePresenterSampleCounter, counter);
+        if (FAILED(hr))
+            return hr;
+
+        if (swapChain) {
+            hr = sample->SetUnknown(MFSamplePresenterSampleSwapChain, swapChain.get());
+            if (FAILED(hr))
+                return hr;
+        }
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::processOutput()
+{
+    // This method will try to get a new sample from the mixer.
+    // It is called when the mixer has a new sample, or when repainting the last frame.
+
+    ASSERT(m_sampleNotify || m_repaint);
+
+    LONGLONG mixerStartTime = 0, mixerEndTime = 0;
+    MFTIME systemTime = 0;
+    bool repaint = m_repaint;  
+
+    // If the clock has not started, we only present the first sample. 
+
+    if ((m_renderState != RenderStateStarted) && !m_repaint && m_prerolled)
+        return S_FALSE;
+
+    if (!m_mixer)
+        return MF_E_INVALIDREQUEST;
+
+    // Get a free sample from the pool.
+    COMPtr<IMFSample> sample;
+    HRESULT hr = m_samplePool.getSample(sample);
+    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
+        return S_FALSE; // We will try again later when there are free samples
+
+    if (FAILED(hr))
+        return hr;
+
+    ASSERT(sample);
+
+    ASSERT(MFGetAttributeUINT32(sample.get(), MFSamplePresenterSampleCounter, (UINT32)-1) == m_tokenCounter);
+
+    if (m_repaint) {
+        // Get the most recent sample from the mixer.
+        setDesiredSampleTime(sample.get(), m_scheduler.lastSampleTime(), m_scheduler.frameDuration());
+        m_repaint = false;
+    } else {
+        // Clear the desired sample time to get the next sample in the stream.
+        clearDesiredSampleTime(sample.get());
+
+        if (m_clock) {
+            // Get the starting time of the ProcessOutput call.
+            m_clock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
+        }
+    }
+
+    // Get a sample from the mixer. 
+    MFT_OUTPUT_DATA_BUFFER dataBuffer;
+    ZeroMemory(&dataBuffer, sizeof(dataBuffer));
+
+    dataBuffer.dwStreamID = 0;
+    dataBuffer.pSample = sample.get();
+    dataBuffer.dwStatus = 0;
+
+    DWORD status = 0;
+    hr = m_mixer->ProcessOutput(0, 1, &dataBuffer, &status);
+
+    // Release events. There are usually no events returned,
+    // but in case there are, we should release them.
+    if (dataBuffer.pEvents)
+        dataBuffer.pEvents->Release();
+
+    if (FAILED(hr)) {
+        HRESULT hr2 = m_samplePool.returnSample(sample.get());
+        if (FAILED(hr2))
+            return hr2;
+
+        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) {
+            // The media type has not been set, renegotiate.
+            hr = renegotiateMediaType();
+        } else if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+            // The media type changed, reset it.
+            setMediaType(nullptr);
+        } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+            // The mixer needs more input.
+            m_sampleNotify = false;
+        }
+    } else {
+        // We have got a sample from the mixer.
+
+        if (m_clock && !repaint) {
+            // Notify the EVR about latency.
+            m_clock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);
+
+            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
+            notifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
+        }
+
+        // Make sure we are notified when the sample is released
+        hr = trackSample(sample.get());
+        if (FAILED(hr))
+            return hr;
+
+        // Deliver the sample for scheduling
+        hr = deliverSample(sample.get(), repaint);
+        if (FAILED(hr))
+            return hr;
+
+        // At least one sample has been presented now.
+        m_prerolled = true;
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::deliverSample(IMFSample* sample, bool repaint)
+{
+    if (!sample)
+        return E_POINTER;
+
+    Direct3DPresenter::DeviceState state = Direct3DPresenter::DeviceOK;
+
+    // Determine if the sample should be presented immediately.
+    bool presentNow = ((m_renderState != RenderStateStarted) || isScrubbing() || repaint);
+
+    HRESULT hr = m_presenterEngine->checkDeviceState(state);
+
+    if (SUCCEEDED(hr))
+        hr = m_scheduler.scheduleSample(sample, presentNow);
+
+    if (FAILED(hr)) {
+        // Streaming has failed, notify the EVR.
+        notifyEvent(EC_ERRORABORT, hr, 0);
+    } else if (state == Direct3DPresenter::DeviceReset)
+        notifyEvent(EC_DISPLAY_CHANGED, S_OK, 0);
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::trackSample(IMFSample* sample)
+{
+    if (!sample)
+        return E_POINTER;
+
+    COMPtr<IMFTrackedSample> tracked;
+
+    HRESULT hr = sample->QueryInterface(__uuidof(IMFTrackedSample), (void**)&tracked);
+    if (FAILED(hr))
+        return hr;
+
+    if (!tracked)
+        return E_POINTER;
+
+    // Set callback object on which the onSampleFree method is invoked when the sample is no longer used.
+    return tracked->SetAllocator(this, nullptr);
+}
+
+void MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::releaseResources()
+{
+    // The token counter is incremented to indicate that existing samples are
+    // invalid and can be disposed in the method onSampleFree.
+    m_tokenCounter++;
+
+    flush();
+
+    m_samplePool.clear();
+
+    if (m_presenterEngine)
+        m_presenterEngine->releaseResources();
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::onSampleFree(IMFAsyncResult* result)
+{
+    if (!result)
+        return E_POINTER;
+
+    COMPtr<IUnknown> object;
+    HRESULT hr = result->GetObject(&object);
+    if (FAILED(hr)) {
+        notifyEvent(EC_ERRORABORT, hr, 0);
+        return hr;
+    }
+
+    COMPtr<IMFSample> sample;
+    hr = object->QueryInterface(__uuidof(IMFSample), (void**)&sample);
+    if (FAILED(hr)) {
+        notifyEvent(EC_ERRORABORT, hr, 0);
+        return hr;
+    }
+
+    m_lock.lock();
+
+    if (MFGetAttributeUINT32(sample.get(), MFSamplePresenterSampleCounter, (UINT32)-1) == m_tokenCounter) {
+        hr = m_samplePool.returnSample(sample.get());
+
+        // Do more processing, since a free sample is available
+        if (SUCCEEDED(hr))
+            processOutputLoop();
+    }
+
+    m_lock.unlock();
+
+    if (FAILED(hr))
+        notifyEvent(EC_ERRORABORT, hr, 0);
+
+    return hr;
+}
+
+void MediaPlayerPrivateMediaFoundation::CustomVideoPresenter::notifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
+{
+    if (m_mediaEventSink)
+        m_mediaEventSink->Notify(EventCode, Param1, Param2);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoSamplePool::getSample(COMPtr<IMFSample>& sample)
+{
+    LockHolder locker(m_lock);
+
+    if (!m_initialized)
+        return MF_E_NOT_INITIALIZED;
+
+    if (m_videoSampleQueue.isEmpty())
+        return MF_E_SAMPLEALLOCATOR_EMPTY;
+
+    sample = m_videoSampleQueue.takeFirst();
+
+    m_pending++;
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoSamplePool::returnSample(IMFSample* sample)
+{
+    if (!sample)
+        return E_POINTER;
+
+    LockHolder locker(m_lock);
+
+    if (!m_initialized)
+        return MF_E_NOT_INITIALIZED;
+
+    m_videoSampleQueue.append(sample);
+    m_pending--;
+    return S_OK;
+}
+
+bool MediaPlayerPrivateMediaFoundation::VideoSamplePool::areSamplesPending()
+{
+    LockHolder locker(m_lock);
+
+    if (!m_initialized)
+        return FALSE;
+
+    return (m_pending > 0);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoSamplePool::initialize(VideoSampleList& samples)
+{
+    LockHolder locker(m_lock);
+
+    if (m_initialized)
+        return MF_E_INVALIDREQUEST;
+
+    // Copy the samples
+    for (auto sample : samples)
+        m_videoSampleQueue.append(sample);
+
+    m_initialized = true;
+    samples.clear();
+
+    return S_OK;
+}
+
+void MediaPlayerPrivateMediaFoundation::VideoSamplePool::clear()
+{
+    LockHolder locker(m_lock);
+
+    m_videoSampleQueue.clear();
+    m_initialized = false;
+    m_pending = 0;
+}
+
+
+// Scheduler thread messages.
+
+enum ScheduleEvent {
+    EventTerminate = WM_USER,
+    EventSchedule,
+    EventFlush
+};
+
+void MediaPlayerPrivateMediaFoundation::VideoScheduler::setFrameRate(const MFRatio& fps)
+{
+    UINT64 avgTimePerFrame = 0;
+    MFFrameRateToAverageTimePerFramePtr()(fps.Numerator, fps.Denominator, &avgTimePerFrame);
+
+    m_frameDuration = (MFTIME)avgTimePerFrame;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoScheduler::startScheduler(IMFClock* clock)
+{
+    if (m_schedulerThread.isValid())
+        return E_UNEXPECTED;
+
+    HRESULT hr = S_OK;
+
+    m_clock = clock;
+
+    // Use high timer resolution.
+    timeBeginPeriod(1);
+
+    // Create an event to signal that the scheduler thread has started.
+    m_threadReadyEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
+    if (!m_threadReadyEvent.isValid())
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    // Create an event to signal that the flush has completed.
+    m_flushEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
+    if (!m_flushEvent.isValid())
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    // Start scheduler thread.
+    DWORD threadID = 0;
+    m_schedulerThread = ::CreateThread(nullptr, 0, schedulerThreadProc, (LPVOID)this, 0, &threadID);
+    if (!m_schedulerThread.isValid())
+        return HRESULT_FROM_WIN32(GetLastError());
+
+    HANDLE hObjects[] = { m_threadReadyEvent.get(), m_schedulerThread.get() };
+
+    // Wait for the thread to start
+    DWORD result = ::WaitForMultipleObjects(2, hObjects, FALSE, INFINITE);
+    if (WAIT_OBJECT_0 != result) {
+        // The thread has terminated.
+        m_schedulerThread.clear();
+        return E_UNEXPECTED;
+    }
+
+    m_threadID = threadID;
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoScheduler::stopScheduler()
+{
+    if (!m_schedulerThread.isValid())
+        return S_OK;
+
+    // Terminate the scheduler thread
+    ::PostThreadMessage(m_threadID, EventTerminate, 0, 0);
+
+    // Wait for the scheduler thread to finish.
+    ::WaitForSingleObject(m_schedulerThread.get(), INFINITE);
+
+    LockHolder locker(m_lock);
+
+    m_schedulerThread.clear();
+    m_flushEvent.clear();
+
+    // Throw away samples
+    m_scheduledSamples.clear();
+
+    // Clear previously set timer resolution.
+    timeEndPeriod(1);
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoScheduler::flush()
+{
+    // This method will wait for the flush to finish on the worker thread.
+
+    if (m_schedulerThread.isValid()) {
+        ::PostThreadMessage(m_threadID, EventFlush, 0, 0);
+
+        HANDLE objects[] = { m_flushEvent.get(), m_schedulerThread.get() };
+
+        const int schedulerTimeout = 5000;
+
+        // Wait for the flush to finish or the thread to terminate.
+        ::WaitForMultipleObjects(2, objects, FALSE, schedulerTimeout);
+    }
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoScheduler::scheduleSample(IMFSample* sample, bool presentNow)
+{
+    if (!sample)
+        return E_POINTER;
+
+    if (!m_presenter)
+        return MF_E_NOT_INITIALIZED;
+
+    if (!m_schedulerThread.isValid())
+        return MF_E_NOT_INITIALIZED;
+
+    DWORD exitCode = 0;
+    ::GetExitCodeThread(m_schedulerThread.get(), &exitCode);
+
+    if (exitCode != STILL_ACTIVE)
+        return E_FAIL;
+
+    if (presentNow || !m_clock)
+        m_presenter->presentSample(sample, 0);
+    else {
+        // Submit the sample for scheduling.
+        LockHolder locker(m_lock);
+        m_scheduledSamples.append(sample);
+
+        ::PostThreadMessage(m_threadID, EventSchedule, 0, 0);
+    }
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoScheduler::processSamplesInQueue(LONG& nextSleep)
+{
+    HRESULT hr = S_OK;
+    LONG wait = 0;
+
+    // Process samples as long as there are samples in the queue, and they have not arrived too early.
+
+    while (true) {
+        COMPtr<IMFSample> sample;
+
+        if (true) {
+            LockHolder locker(m_lock);
+            if (m_scheduledSamples.isEmpty())
+                break;
+            sample = m_scheduledSamples.takeFirst();
+        }
+
+        // Process the sample.
+        // If the sample has arrived too early, wait will be > 0,
+        // and the scheduler should go to sleep.
+        hr = processSample(sample.get(), wait);
+
+        if (FAILED(hr))
+            break;
+
+        if (wait > 0)
+            break;
+    }
+
+    if (!wait) {
+        // The queue is empty. Sleep until the next message arrives.
+        wait = INFINITE;
+    }
+
+    nextSleep = wait;
+    return hr;
+}
+
+// MFTimeToMilliseconds: Convert 100-nanosecond time to milliseconds.
+static LONG MFTimeToMilliseconds(const LONGLONG& time)
+{
+    return (time / 10000);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::VideoScheduler::processSample(IMFSample* sample, LONG& nextSleep)
+{
+    if (!sample)
+        return E_POINTER;
+
+    HRESULT hr = S_OK;
+
+    LONGLONG presentationTime = 0;
+    LONGLONG timeNow = 0;
+    MFTIME systemTime = 0;
+
+    bool presentNow = true;
+    LONG nextSleepTime = 0;
+
+    if (m_clock) {
+        // Get the time stamp of the sample.
+        // A sample can possibly have no time stamp.
+        hr = sample->GetSampleTime(&presentationTime);
+
+        // Get the clock time.
+        // If the sample does not have a time stamp, the clock time is not needed.
+        if (SUCCEEDED(hr))
+            hr = m_clock->GetCorrelatedTime(0, &timeNow, &systemTime);
+
+        // Determine the time until the sample should be presented.
+        // Samples arriving late, will have negative values.
+        LONGLONG timeDelta = presentationTime - timeNow;
+        if (m_playbackRate < 0) {
+            // Reverse delta for reverse playback.
+            timeDelta = -timeDelta;
+        }
+
+        LONGLONG frameDurationOneFourth = m_frameDuration / 4;
+
+        if (timeDelta < -frameDurationOneFourth) {
+            // The sample has arrived late. 
+            presentNow = true;
+        } else if (timeDelta > (3 * frameDurationOneFourth)) {
+            // We can sleep, the sample has arrived too early.
+            nextSleepTime = MFTimeToMilliseconds(timeDelta - (3 * frameDurationOneFourth));
+
+            // Since sleeping is using the system clock, we need to convert the sleep time
+            // from presentation time to system time.
+            nextSleepTime = (LONG)(nextSleepTime / fabsf(m_playbackRate));
+
+            presentNow = false;
+        }
+    }
+
+    if (presentNow)
+        hr = m_presenter->presentSample(sample, presentationTime);
+    else {
+        // Return the sample to the queue, since it is not ready.
+        LockHolder locker(m_lock);
+        m_scheduledSamples.prepend(sample);
+    }
+
+    nextSleep = nextSleepTime;
+
+    return hr;
+}
+
+DWORD WINAPI MediaPlayerPrivateMediaFoundation::VideoScheduler::schedulerThreadProc(LPVOID lpParameter)
+{
+    VideoScheduler* scheduler = reinterpret_cast<VideoScheduler*>(lpParameter);
+    if (!scheduler)
+        return static_cast<DWORD>(-1);
+    return scheduler->schedulerThreadProcPrivate();
+}
+
+DWORD MediaPlayerPrivateMediaFoundation::VideoScheduler::schedulerThreadProcPrivate()
+{
+    HRESULT hr = S_OK;
+
+    // This will force a message queue to be created for the thread.
+    MSG msg;
+    PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
+
+    // The thread is ready.
+    SetEvent(m_threadReadyEvent.get());
+
+    LONG wait = INFINITE;
+    bool exitThread = false;
+    while (!exitThread) {
+        // Wait for messages
+        DWORD result = MsgWaitForMultipleObjects(0, nullptr, FALSE, wait, QS_POSTMESSAGE);
+
+        if (result == WAIT_TIMEOUT) {
+            hr = processSamplesInQueue(wait);
+            if (FAILED(hr))
+                exitThread = true;
+        }
+
+        while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
+            bool processSamples = true;
+
+            switch (msg.message) {
+            case EventTerminate:
+                exitThread = true;
+                break;
+
+            case EventFlush:
+                {
+                    LockHolder lock(m_lock);
+                    m_scheduledSamples.clear();
+                }
+                wait = INFINITE;
+                SetEvent(m_flushEvent.get());
+                break;
+
+            case EventSchedule:
+                if (processSamples) {
+                    hr = processSamplesInQueue(wait);
+                    if (FAILED(hr))
+                        exitThread = true;
+                    processSamples = (wait != INFINITE);
+                }
+                break;
+            }
+        }
+    }
+    return (SUCCEEDED(hr) ? 0 : 1);
+}
+
+static HRESULT findAdapter(IDirect3D9* direct3D9, HMONITOR monitor, UINT& adapterID)
+{
+    HRESULT hr = E_FAIL;
+
+    UINT adapterCount = direct3D9->GetAdapterCount();
+    for (UINT i = 0; i < adapterCount; i++) {
+        HMONITOR monitorTmp = direct3D9->GetAdapterMonitor(i);
+
+        if (!monitorTmp)
+            break;
+
+        if (monitorTmp == monitor) {
+            adapterID = i;
+            hr = S_OK;
+            break;
+        }
+    }
+
+    return hr;
+}
+
+MediaPlayerPrivateMediaFoundation::Direct3DPresenter::Direct3DPresenter()
+{
+    SetRectEmpty(&m_destRect);
+
+    ZeroMemory(&m_displayMode, sizeof(m_displayMode));
+
+    HRESULT hr = initializeD3D();
+
+    if (FAILED(hr))
+        return;
+
+    createD3DDevice();
+}
+
+MediaPlayerPrivateMediaFoundation::Direct3DPresenter::~Direct3DPresenter()
+{
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getService(REFGUID guidService, REFIID riid, void** ppv)
+{
+    ASSERT(ppv);
+
+    HRESULT hr = S_OK;
+
+    if (riid == __uuidof(IDirect3DDeviceManager9)) {
+        if (!m_deviceManager)
+            hr = MF_E_UNSUPPORTED_SERVICE;
+        else {
+            *ppv = m_deviceManager.get();
+            m_deviceManager->AddRef();
+        }
+    } else
+        hr = MF_E_UNSUPPORTED_SERVICE;
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::checkFormat(D3DFORMAT format)
+{
+    HRESULT hr = S_OK;
+
+    UINT adapter = D3DADAPTER_DEFAULT;
+    D3DDEVTYPE type = D3DDEVTYPE_HAL;
+
+    if (m_device) {
+        D3DDEVICE_CREATION_PARAMETERS params;
+        hr = m_device->GetCreationParameters(&params);
+        if (FAILED(hr))
+            return hr;
+
+        adapter = params.AdapterOrdinal;
+        type = params.DeviceType;
+    }
+
+    D3DDISPLAYMODE mode;
+    hr = m_direct3D9->GetAdapterDisplayMode(adapter, &mode);
+    if (FAILED(hr))
+        return hr;
+
+    return m_direct3D9->CheckDeviceType(adapter, type, mode.Format, format, TRUE);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::setVideoWindow(HWND hwnd)
+{
+    ASSERT(IsWindow(hwnd));
+    ASSERT(hwnd != m_hwnd);
+
+    {
+        LockHolder locker(m_lock);
+        m_hwnd = hwnd;
+        updateDestRect();
+    }
+
+    return createD3DDevice();
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::setDestinationRect(const RECT& rcDest)
+{
+    if (EqualRect(&rcDest, &m_destRect))
+        return S_OK;
+
+    LockHolder locker(m_lock);
+
+    m_destRect = rcDest;
+    updateDestRect();
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createVideoSamples(IMFMediaType* format, VideoSampleList& videoSampleQueue)
+{
+    // Create video samples matching the supplied format.
+    // A swap chain with a single back buffer will be created for each video sample.
+    // The mixer will render to the back buffer through a surface kept by the sample.
+    // The surface can be rendered to a window by presenting the swap chain.
+    // In our case the surface is transferred to system memory, and rendered to a graphics context.
+
+    if (!m_hwnd)
+        return MF_E_INVALIDREQUEST;
+
+    if (!format)
+        return MF_E_UNEXPECTED;
+
+
+    LockHolder locker(m_lock);
+
+    releaseResources();
+
+    D3DPRESENT_PARAMETERS presentParameters;
+    HRESULT hr = getSwapChainPresentParameters(format, &presentParameters);
+    if (FAILED(hr)) {
+        releaseResources();
+        return hr;
+    }
+
+    updateDestRect();
+
+    static const int presenterBufferCount = 3;
+
+    for (int i = 0; i < presenterBufferCount; i++) {
+        COMPtr<IDirect3DSwapChain9> swapChain;
+        hr = m_device->CreateAdditionalSwapChain(&presentParameters, &swapChain);
+        if (FAILED(hr)) {
+            releaseResources();
+            return hr;
+        }
+
+        COMPtr<IMFSample> videoSample;
+        hr = createD3DSample(swapChain.get(), videoSample);
+        if (FAILED(hr)) {
+            releaseResources();
+            return hr;
+        }
+
+        videoSampleQueue.append(videoSample);
+
+        // Keep the swap chain alive by setting it as a custom attribute on the sample.
+        hr = videoSample->SetUnknown(MFSamplePresenterSampleSwapChain, swapChain.get());
+        if (FAILED(hr)) {
+            releaseResources();
+            return hr;
+        }
+    }
+
+    return hr;
+}
+
+void MediaPlayerPrivateMediaFoundation::Direct3DPresenter::releaseResources()
+{
+    m_surfaceRepaint = nullptr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::checkDeviceState(DeviceState& state)
+{
+    LockHolder locker(m_lock);
+
+    HRESULT hr = m_device->CheckDeviceState(m_hwnd);
+
+    state = DeviceOK;
+
+    // Not all failure codes are critical.
+
+    switch (hr) {
+    case S_OK:
+    case S_PRESENT_OCCLUDED:
+    case S_PRESENT_MODE_CHANGED:
+        hr = S_OK;
+        break;
+
+    case D3DERR_DEVICELOST:
+    case D3DERR_DEVICEHUNG:
+        hr = createD3DDevice();
+        if (FAILED(hr))
+            return hr;
+        state = DeviceReset;
+        hr = S_OK;
+        break;
+
+    case D3DERR_DEVICEREMOVED:
+        state = DeviceRemoved;
+        break;
+
+    case E_INVALIDARG:
+        // This might happen if the window has been destroyed, or is not valid.
+        // A new device will be created if a new window is set.
+        hr = S_OK;
+    }
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::presentSample(IMFSample* sample, LONGLONG targetPresentationTime)
+{
+    HRESULT hr = S_OK;
+
+    LockHolder locker(m_lock);
+
+    COMPtr<IDirect3DSurface9> surface;
+
+    if (sample) {
+        COMPtr<IMFMediaBuffer> buffer;
+        hr = sample->GetBufferByIndex(0, &buffer);
+        hr = MFGetServicePtr()(buffer.get(), MR_BUFFER_SERVICE, __uuidof(IDirect3DSurface9), (void**)&surface);
+    } else if (m_surfaceRepaint) {
+        // Use the last surface.
+        surface = m_surfaceRepaint;
+    }
+
+    if (surface) {
+        UINT width = m_destRect.right - m_destRect.left;
+        UINT height = m_destRect.bottom - m_destRect.top;
+
+        if (width > 0 && height > 0) {
+            if (!m_memSurface || m_width != width || m_height != height) {
+                hr = m_device->CreateOffscreenPlainSurface(width, height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &m_memSurface, nullptr);
+                m_width = width;
+                m_height = height;
+            }
+            // Copy data from video memory to system memory
+            hr = m_device->GetRenderTargetData(surface.get(), m_memSurface.get());
+            if (FAILED(hr)) {
+                m_memSurface = nullptr;
+                hr = S_OK;
+            }
+        }
+
+        // Since we want to draw to the GraphicsContext provided in the paint method,
+        // and not draw directly to the window, we skip presenting the swap chain:
+
+        // COMPtr<IDirect3DSwapChain9> swapChain;
+        // hr = surface->GetContainer(__uuidof(IDirect3DSwapChain9), (LPVOID*)&swapChain));
+        // hr = presentSwapChain(swapChain, surface));
+
+        // Keep the last surface for repaints.
+        m_surfaceRepaint = surface;
+    }
+
+    if (FAILED(hr)) {
+        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG) {
+            // Ignore this error. We have to reset or recreate the device.
+            // The presenter will handle this when checking the device state the next time.
+            hr = S_OK;
+        }
+    }
+    return hr;
+}
+
+void MediaPlayerPrivateMediaFoundation::Direct3DPresenter::paintCurrentFrame(WebCore::GraphicsContext& context, const WebCore::FloatRect& r)
+{
+    UINT width = m_destRect.right - m_destRect.left;
+    UINT height = m_destRect.bottom - m_destRect.top;
+
+    if (!width || !height)
+        return;
+
+    LockHolder locker(m_lock);
+
+    if (!m_memSurface)
+        return;
+
+    D3DLOCKED_RECT lockedRect;
+    if (SUCCEEDED(m_memSurface->LockRect(&lockedRect, nullptr, D3DLOCK_READONLY))) {
+        void* data = lockedRect.pBits;
+        int pitch = lockedRect.Pitch;
+#if USE(CAIRO)
+        WebCore::PlatformContextCairo* ctxt = context.platformContext();
+        cairo_surface_t* image = cairo_image_surface_create_for_data(static_cast<unsigned char*>(data), CAIRO_FORMAT_ARGB32, width, height, pitch);
+        WebCore::FloatRect clip(0, 0, width, height);
+        WebCore::FloatRect rect = r;
+        rect.intersect(clip);
+        ctxt->drawSurfaceToContext(image, rect, rect, context);
+        cairo_surface_destroy(image);
+#else
+#error "Platform needs to implement drawing of Direct3D surface to graphics context!"
+#endif
+        m_memSurface->UnlockRect();
+    }
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::initializeD3D()
+{
+    ASSERT(!m_direct3D9);
+    ASSERT(!m_deviceManager);
+
+    HRESULT hr = Direct3DCreate9ExPtr()(D3D_SDK_VERSION, &m_direct3D9);
+    if (FAILED(hr))
+        return hr;
+
+    return DXVA2CreateDirect3DDeviceManager9Ptr()(&m_deviceResetToken, &m_deviceManager);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createD3DDevice()
+{
+    HRESULT hr = S_OK;
+    UINT adapterID = D3DADAPTER_DEFAULT;
+
+    LockHolder locker(m_lock);
+
+    if (!m_direct3D9 || !m_deviceManager)
+        return MF_E_NOT_INITIALIZED;
+
+    HWND hwnd = GetDesktopWindow();
+
+    // We create additional swap chains to present the video frames,
+    // and do not use the implicit swap chain of the device.
+    // The size of the back buffer is 1 x 1.
+
+    D3DPRESENT_PARAMETERS pp;
+    ZeroMemory(&pp, sizeof(pp));
+
+    pp.BackBufferWidth = 1;
+    pp.BackBufferHeight = 1;
+    pp.Windowed = TRUE;
+    pp.SwapEffect = D3DSWAPEFFECT_COPY;
+    pp.BackBufferFormat = D3DFMT_UNKNOWN;
+    pp.hDeviceWindow = hwnd;
+    pp.Flags = D3DPRESENTFLAG_VIDEO;
+    pp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
+
+    if (m_hwnd) {
+        HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
+
+        hr = findAdapter(m_direct3D9.get(), monitor, adapterID);
+        if (FAILED(hr))
+            return hr;
+    }
+
+    D3DCAPS9 ddCaps;
+    ZeroMemory(&ddCaps, sizeof(ddCaps));
+
+    hr = m_direct3D9->GetDeviceCaps(adapterID, D3DDEVTYPE_HAL, &ddCaps);
+    if (FAILED(hr))
+        return hr;
+
+    DWORD flags = D3DCREATE_NOWINDOWCHANGES | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE;
+
+    if (ddCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
+        flags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
+    else
+        flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
+
+    COMPtr<IDirect3DDevice9Ex> device;
+    hr = m_direct3D9->CreateDeviceEx(adapterID, D3DDEVTYPE_HAL, pp.hDeviceWindow, flags, &pp, nullptr, &device);
+    if (FAILED(hr))
+        return hr;
+
+    hr = m_direct3D9->GetAdapterDisplayMode(adapterID, &m_displayMode);
+    if (FAILED(hr))
+        return hr;
+
+    hr = m_deviceManager->ResetDevice(device.get(), m_deviceResetToken);
+    if (FAILED(hr))
+        return hr;
+
+    m_device = device;
+
+    return hr;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::createD3DSample(IDirect3DSwapChain9* swapChain, COMPtr<IMFSample>& videoSample)
+{
+    COMPtr<IDirect3DSurface9> surface;
+    HRESULT hr = swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &surface);
+    if (FAILED(hr))
+        return hr;
+
+    D3DCOLOR colorBlack = D3DCOLOR_ARGB(0xFF, 0x00, 0x00, 0x00);
+    hr = m_device->ColorFill(surface.get(), nullptr, colorBlack);
+    if (FAILED(hr))
+        return hr;
+
+    return MFCreateVideoSampleFromSurfacePtr()(surface.get(), &videoSample);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::presentSwapChain(IDirect3DSwapChain9* swapChain, IDirect3DSurface9* surface)
+{
+    if (!m_hwnd)
+        return MF_E_INVALIDREQUEST;
+
+    return swapChain->Present(nullptr, &m_destRect, m_hwnd, nullptr, 0);
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::getSwapChainPresentParameters(IMFMediaType* type, D3DPRESENT_PARAMETERS* presentParams)
+{
+    if (!m_hwnd)
+        return MF_E_INVALIDREQUEST;
+
+    COMPtr<IMFMediaType> videoType = type;
+
+    UINT32 width = 0, height = 0;
+    HRESULT hr = MFGetAttributeSize(videoType.get(), MF_MT_FRAME_SIZE, &width, &height);
+    if (FAILED(hr))
+        return hr;
+
+    GUID guidSubType = GUID_NULL;
+    hr = videoType->GetGUID(MF_MT_SUBTYPE, &guidSubType);
+    if (FAILED(hr))
+        return hr;
+
+    DWORD d3dFormat = guidSubType.Data1;
+
+    ZeroMemory(presentParams, sizeof(D3DPRESENT_PARAMETERS));
+    presentParams->BackBufferWidth = width;
+    presentParams->BackBufferHeight = height;
+    presentParams->Windowed = TRUE;
+    presentParams->SwapEffect = D3DSWAPEFFECT_COPY;
+    presentParams->BackBufferFormat = (D3DFORMAT)d3dFormat;
+    presentParams->hDeviceWindow = m_hwnd;
+    presentParams->Flags = D3DPRESENTFLAG_VIDEO;
+    presentParams->PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
+
+    D3DDEVICE_CREATION_PARAMETERS params;
+    hr = m_device->GetCreationParameters(&params);
+    if (FAILED(hr))
+        return hr;
+
+    if (params.DeviceType != D3DDEVTYPE_HAL)
+        presentParams->Flags |= D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
+
+    return S_OK;
+}
+
+HRESULT MediaPlayerPrivateMediaFoundation::Direct3DPresenter::updateDestRect()
+{
+    if (!m_hwnd)
+        return S_FALSE;
+
+    RECT rcView;
+    GetClientRect(m_hwnd, &rcView);
+
+    // Clip to the client area of the window.
+    if (m_destRect.right > rcView.right)
+        m_destRect.right = rcView.right;
+
+    if (m_destRect.bottom > rcView.bottom)
+        m_destRect.bottom = rcView.bottom;
+
+    return S_OK;
+}
+
+} // namespace WebCore
+
 #endif
index b043c79..4a6b532 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifndef MediaPlayerPrivateMediaFoundation_h
+#define MediaPlayerPrivateMediaFoundation_h
+
 #include "COMPtr.h"
 #include "MediaPlayerPrivate.h"
+#include "Win32Handle.h"
+
+#include <D3D9.h>
+#include <Dxva2api.h>
 
 #include <Mfapi.h>
+#include <Mferror.h>
 #include <Mfidl.h>
+#include <evcode.h>
 #include <evr.h>
 
+#include <wtf/Deque.h>
 #include <wtf/Lock.h>
 #include <wtf/ThreadingPrimitives.h>
 #include <wtf/WeakPtr.h>
@@ -118,6 +128,8 @@ private:
     void createVideoWindow();
     void destroyVideoWindow();
 
+    void invalidateFrameView();
+
     void addListener(MediaPlayerListener*);
     void removeListener(MediaPlayerListener*);
     void notifyDeleted();
@@ -153,6 +165,288 @@ private:
         Lock m_mutex;
     };
 
+    typedef Deque<COMPtr<IMFSample>> VideoSampleList;
+
+    class VideoSamplePool {
+    public:
+        VideoSamplePool() { }
+        virtual ~VideoSamplePool() { }
+
+        HRESULT initialize(VideoSampleList& samples);
+        void clear();
+
+        HRESULT getSample(COMPtr<IMFSample>&);
+        HRESULT returnSample(IMFSample*);
+        bool areSamplesPending();
+
+    private:
+        Lock m_lock;
+        VideoSampleList m_videoSampleQueue;
+        bool m_initialized { false };
+        unsigned m_pending { 0 };
+    };
+
+    class Direct3DPresenter;
+
+    class VideoScheduler {
+    public:
+        VideoScheduler() { }
+        virtual ~VideoScheduler() { }
+
+        void setPresenter(Direct3DPresenter* presenter) { m_presenter = presenter; }
+
+        void setFrameRate(const MFRatio& fps);
+        void setClockRate(float rate) { m_playbackRate = rate; }
+
+        const LONGLONG& lastSampleTime() const { return m_lastSampleTime; }
+        const LONGLONG& frameDuration() const { return m_frameDuration; }
+
+        HRESULT startScheduler(IMFClock*);
+        HRESULT stopScheduler();
+
+        HRESULT scheduleSample(IMFSample*, bool presentNow);
+        HRESULT processSamplesInQueue(LONG& nextSleep);
+        HRESULT processSample(IMFSample*, LONG& nextSleep);
+        HRESULT flush();
+
+    private:
+        static DWORD WINAPI schedulerThreadProc(LPVOID lpParameter);
+        DWORD schedulerThreadProcPrivate();
+
+        Deque<COMPtr<IMFSample>> m_scheduledSamples;
+        Lock m_lock;
+
+        COMPtr<IMFClock> m_clock;
+        Direct3DPresenter* m_presenter { nullptr };
+
+        DWORD m_threadID { 0 };
+        Win32Handle m_schedulerThread;
+        Win32Handle m_threadReadyEvent;
+        Win32Handle m_flushEvent;
+
+        float m_playbackRate { 1.0f };
+        MFTIME m_frameDuration { 0 };
+        MFTIME m_lastSampleTime { 0 };
+    };
+
+    class Direct3DPresenter {
+    public:
+        Direct3DPresenter();
+        ~Direct3DPresenter();
+
+        enum DeviceState {
+            DeviceOK,
+            DeviceReset,
+            DeviceRemoved,
+        };
+
+        // Returns the IDirect3DDeviceManager9 interface.
+        HRESULT getService(REFGUID guidService, REFIID riid, void** ppv);
+
+        HRESULT checkFormat(D3DFORMAT);
+
+        HRESULT setVideoWindow(HWND);
+        HWND getVideoWindow() const { return m_hwnd; }
+        HRESULT setDestinationRect(const RECT& destRect);
+        RECT getDestinationRect() const { return m_destRect; };
+
+        HRESULT createVideoSamples(IMFMediaType* format, VideoSampleList& videoSampleQueue);
+        void releaseResources();
+
+        HRESULT checkDeviceState(DeviceState&);
+        HRESULT presentSample(IMFSample*, LONGLONG target);
+
+        UINT refreshRate() const { return m_displayMode.RefreshRate; }
+
+        void paintCurrentFrame(GraphicsContext&, const FloatRect&);
+
+    private:
+        HRESULT initializeD3D();
+        HRESULT getSwapChainPresentParameters(IMFMediaType*, D3DPRESENT_PARAMETERS* presentParams);
+        HRESULT createD3DDevice();
+        HRESULT createD3DSample(IDirect3DSwapChain9*, COMPtr<IMFSample>& videoSample);
+        HRESULT updateDestRect();
+
+        HRESULT presentSwapChain(IDirect3DSwapChain9*, IDirect3DSurface9*);
+
+        UINT m_deviceResetToken { 0 };
+        HWND m_hwnd { nullptr };
+        RECT m_destRect;
+        D3DDISPLAYMODE m_displayMode;
+
+        Lock m_lock;
+        
+        COMPtr<IDirect3D9Ex> m_direct3D9;
+        COMPtr<IDirect3DDevice9Ex> m_device;
+        COMPtr<IDirect3DDeviceManager9> m_deviceManager;
+        COMPtr<IDirect3DSurface9> m_surfaceRepaint;
+
+        COMPtr<IDirect3DSurface9> m_memSurface;
+        int m_width { 0 };
+        int m_height { 0 };
+    };
+
+    class CustomVideoPresenter
+        : public IMFVideoPresenter
+        , public IMFVideoDeviceID
+        , public IMFTopologyServiceLookupClient
+        , public IMFGetService
+        , public IMFActivate
+        , public IMFVideoDisplayControl
+        , public IMFAsyncCallback
+        , public MediaPlayerListener {
+    public:
+        CustomVideoPresenter(MediaPlayerPrivateMediaFoundation*);
+        ~CustomVideoPresenter();
+
+        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject) override;
+        virtual ULONG STDMETHODCALLTYPE AddRef() override;
+        virtual ULONG STDMETHODCALLTYPE Release() override;
+
+        // IMFClockStateSink
+        virtual HRESULT STDMETHODCALLTYPE OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) override;
+        virtual HRESULT STDMETHODCALLTYPE OnClockStop(MFTIME hnsSystemTime) override;
+        virtual HRESULT STDMETHODCALLTYPE OnClockPause(MFTIME hnsSystemTime) override;
+        virtual HRESULT STDMETHODCALLTYPE OnClockRestart(MFTIME hnsSystemTime) override;
+        virtual HRESULT STDMETHODCALLTYPE OnClockSetRate(MFTIME hnsSystemTime, float flRate) override;
+
+        // IMFVideoPresenter
+        virtual HRESULT STDMETHODCALLTYPE ProcessMessage(MFVP_MESSAGE_TYPE eMessage, ULONG_PTR ulParam) override;
+        virtual HRESULT STDMETHODCALLTYPE GetCurrentMediaType(_Outptr_  IMFVideoMediaType **ppMediaType) override;
+
+        // IMFVideoDeviceID
+        virtual HRESULT STDMETHODCALLTYPE GetDeviceID(IID* pDeviceID) override;
+
+        // IMFTopologyServiceLookupClient
+        virtual HRESULT STDMETHODCALLTYPE InitServicePointers(_In_  IMFTopologyServiceLookup *pLookup) override;
+        virtual HRESULT STDMETHODCALLTYPE ReleaseServicePointers(void) override;
+
+        // IMFGetService
+        virtual HRESULT STDMETHODCALLTYPE GetService(REFGUID guidService, REFIID riid, LPVOID *ppvObject);
+
+        // IMFActivate
+        virtual HRESULT STDMETHODCALLTYPE ActivateObject(REFIID riid, void **ppv);
+        virtual HRESULT STDMETHODCALLTYPE DetachObject();
+        virtual HRESULT STDMETHODCALLTYPE ShutdownObject();
+
+        // IMFAttributes
+        virtual HRESULT STDMETHODCALLTYPE GetItem(__RPC__in REFGUID guidKey, __RPC__inout_opt PROPVARIANT *pValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetItemType(__RPC__in REFGUID guidKey, __RPC__out MF_ATTRIBUTE_TYPE *pType) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE CompareItem(__RPC__in REFGUID guidKey, __RPC__in REFPROPVARIANT Value, __RPC__out BOOL *pbResult) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE Compare(__RPC__in_opt IMFAttributes *pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, __RPC__out BOOL *pbResult) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetUINT32(__RPC__in REFGUID guidKey, __RPC__out UINT32 *punValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetUINT64(__RPC__in REFGUID guidKey, __RPC__out UINT64 *punValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetDouble(__RPC__in REFGUID guidKey, __RPC__out double *pfValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetGUID(__RPC__in REFGUID guidKey, __RPC__out GUID *pguidValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetStringLength(__RPC__in REFGUID guidKey, __RPC__out UINT32 *pcchLength) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetString(__RPC__in REFGUID guidKey, __RPC__out_ecount_full(cchBufSize) LPWSTR pwszValue, UINT32 cchBufSize, __RPC__inout_opt UINT32 *pcchLength) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetAllocatedString(__RPC__in REFGUID guidKey, __RPC__deref_out_ecount_full_opt((*pcchLength + 1)) LPWSTR *ppwszValue, __RPC__out UINT32 *pcchLength) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetBlobSize(__RPC__in REFGUID guidKey, __RPC__out UINT32 *pcbBlobSize) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetBlob(__RPC__in REFGUID guidKey, __RPC__out_ecount_full(cbBufSize) UINT8 *pBuf, UINT32 cbBufSize, __RPC__inout_opt UINT32 *pcbBlobSize) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetAllocatedBlob(__RPC__in REFGUID guidKey, __RPC__deref_out_ecount_full_opt(*pcbSize) UINT8 **ppBuf, __RPC__out UINT32 *pcbSize) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetUnknown(__RPC__in REFGUID guidKey, __RPC__in REFIID riid, __RPC__deref_out_opt LPVOID *ppv) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetItem(__RPC__in REFGUID guidKey, __RPC__in REFPROPVARIANT Value) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE DeleteItem(__RPC__in REFGUID guidKey) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE DeleteAllItems(void) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetUINT32(__RPC__in REFGUID guidKey, UINT32 unValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetUINT64(__RPC__in REFGUID guidKey, UINT64 unValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetDouble(__RPC__in REFGUID guidKey, double fValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetGUID(__RPC__in REFGUID guidKey, __RPC__in REFGUID guidValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetString(__RPC__in REFGUID guidKey, __RPC__in_string LPCWSTR wszValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetBlob(__RPC__in REFGUID guidKey, __RPC__in_ecount_full(cbBufSize) const UINT8 *pBuf, UINT32 cbBufSize) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetUnknown(__RPC__in REFGUID guidKey, __RPC__in_opt IUnknown *pUnknown) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE LockStore(void) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE UnlockStore(void) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetCount(__RPC__out UINT32 *pcItems) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetItemByIndex(UINT32 unIndex, __RPC__out GUID *pguidKey, __RPC__inout_opt PROPVARIANT *pValue) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE CopyAllItems(__RPC__in_opt IMFAttributes *pDest) { return E_NOTIMPL; }
+
+        // IMFVideoDisplayControl
+        virtual HRESULT STDMETHODCALLTYPE GetNativeVideoSize(SIZE* pszVideo, SIZE* pszARVideo) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetIdealVideoSize(SIZE* pszMin, SIZE* pszMax) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetVideoPosition(const MFVideoNormalizedRect* pnrcSource, const LPRECT prcDest);
+        virtual HRESULT STDMETHODCALLTYPE GetVideoPosition(MFVideoNormalizedRect* pnrcSource, LPRECT prcDest);
+        virtual HRESULT STDMETHODCALLTYPE SetAspectRatioMode(DWORD dwAspectRatioMode) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetAspectRatioMode(DWORD* pdwAspectRatioMode) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetVideoWindow(HWND hwndVideo);
+        virtual HRESULT STDMETHODCALLTYPE GetVideoWindow(HWND* phwndVideo);
+        virtual HRESULT STDMETHODCALLTYPE RepaintVideo();
+        virtual HRESULT STDMETHODCALLTYPE GetCurrentImage(BITMAPINFOHEADER* pBih, BYTE** pDib, DWORD* pcbDib, LONGLONG* pTimeStamp) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetBorderColor(COLORREF Clr) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetBorderColor(COLORREF* pClr) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetRenderingPrefs(DWORD dwRenderFlags) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetRenderingPrefs(DWORD* pdwRenderFlags) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE SetFullscreen(BOOL bFullscreen) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE GetFullscreen(BOOL* pbFullscreen) { return E_NOTIMPL; }
+
+        // IMFAsyncCallback methods
+        virtual HRESULT STDMETHODCALLTYPE GetParameters(DWORD*, DWORD*) { return E_NOTIMPL; }
+        virtual HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult* pAsyncResult);
+
+        // MediaPlayerListener
+        virtual void onMediaPlayerDeleted() override;
+
+        void paintCurrentFrame(GraphicsContext&, const FloatRect&);
+
+    private:
+        ULONG m_refCount { 0 };
+        Lock m_lock;
+        MediaPlayerPrivateMediaFoundation* m_mediaPlayer;
+
+        enum RenderState {
+            RenderStateStarted = 1,
+            RenderStateStopped,
+            RenderStatePaused,
+            RenderStateShutdown,
+        };
+
+        RenderState m_renderState { RenderStateShutdown };
+        COMPtr<IMFClock> m_clock;
+        COMPtr<IMediaEventSink> m_mediaEventSink;
+        COMPtr<IMFTransform> m_mixer;
+        COMPtr<IMFMediaType> m_mediaType;
+        std::unique_ptr<Direct3DPresenter> m_presenterEngine;
+        MFVideoNormalizedRect m_sourceRect;
+        bool m_sampleNotify { false };
+        bool m_prerolled { false };
+        bool m_repaint { false };
+        bool m_endStreaming { false };
+        VideoScheduler m_scheduler;
+        VideoSamplePool m_samplePool;
+        unsigned m_tokenCounter { 0 };
+        float m_rate { 1.0f };
+
+        bool isActive() const;
+
+        bool isScrubbing() const { return m_rate == 0.0f; }
+
+        HRESULT configureMixer(IMFTransform* mixer);
+        HRESULT flush();
+        HRESULT setMediaType(IMFMediaType*);
+        HRESULT checkShutdown() const;
+        HRESULT renegotiateMediaType();
+        HRESULT processInputNotify();
+        HRESULT beginStreaming();
+        HRESULT endStreaming();
+        HRESULT checkEndOfStream();
+        HRESULT isMediaTypeSupported(IMFMediaType*);
+        HRESULT createOptimalVideoType(IMFMediaType* proposedType, IMFMediaType** optimalType);
+        HRESULT calculateOutputRectangle(IMFMediaType* proposedType, RECT& outputRect);
+
+        void processOutputLoop();
+        HRESULT processOutput();
+        HRESULT deliverSample(IMFSample*, bool repaint);
+        HRESULT trackSample(IMFSample*);
+        void releaseResources();
+
+        HRESULT onSampleFree(IMFAsyncResult*);
+
+        void notifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2);
+    };
+
+    COMPtr<CustomVideoPresenter> m_presenter;
 };
 
 }
+
+#endif