2 * Copyright (C) 2014 Alex Christensen <achristensen@webkit.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "MediaPlayerPrivateMediaFoundation.h"
30 #include "CachedResourceLoader.h"
31 #include "FrameView.h"
32 #include "GraphicsContext.h"
33 #include "HostWindow.h"
34 #include "NotImplemented.h"
36 #if USE(MEDIA_FOUNDATION)
38 #include <wtf/MainThread.h>
42 MediaPlayerPrivateMediaFoundation::MediaPlayerPrivateMediaFoundation(MediaPlayer* player)
45 , m_loadingProgress(false)
49 , m_hwndVideo(nullptr)
50 , m_readyState(MediaPlayer::HaveNothing)
51 , m_mediaSession(nullptr)
52 , m_sourceResolver(nullptr)
53 , m_mediaSource(nullptr)
56 , m_videoDisplay(nullptr)
62 MediaPlayerPrivateMediaFoundation::~MediaPlayerPrivateMediaFoundation()
68 PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateMediaFoundation::create(MediaPlayer* player)
70 return adoptPtr(new MediaPlayerPrivateMediaFoundation(player));
73 void MediaPlayerPrivateMediaFoundation::registerMediaEngine(MediaEngineRegistrar registrar)
76 registrar(create, getSupportedTypes, supportsType, 0, 0, 0, 0);
79 bool MediaPlayerPrivateMediaFoundation::isAvailable()
85 void MediaPlayerPrivateMediaFoundation::getSupportedTypes(HashSet<String>& types)
87 types.add(String("video/mp4"));
90 MediaPlayer::SupportsType MediaPlayerPrivateMediaFoundation::supportsType(const MediaEngineSupportParameters& parameters)
92 if (parameters.type.isNull() || parameters.type.isEmpty())
93 return MediaPlayer::IsNotSupported;
95 if (parameters.type == "video/mp4")
96 return MediaPlayer::IsSupported;
98 return MediaPlayer::IsNotSupported;
101 void MediaPlayerPrivateMediaFoundation::load(const String& url)
103 startCreateMediaSource(url);
106 void MediaPlayerPrivateMediaFoundation::cancelLoad()
111 void MediaPlayerPrivateMediaFoundation::play()
116 PROPVARIANT varStart;
117 PropVariantInit(&varStart);
118 varStart.vt = VT_EMPTY;
120 HRESULT hr = m_mediaSession->Start(nullptr, &varStart);
121 ASSERT(SUCCEEDED(hr));
123 PropVariantClear(&varStart);
125 m_paused = !SUCCEEDED(hr);
128 void MediaPlayerPrivateMediaFoundation::pause()
133 m_paused = SUCCEEDED(m_mediaSession->Pause());
136 IntSize MediaPlayerPrivateMediaFoundation::naturalSize() const
141 bool MediaPlayerPrivateMediaFoundation::hasVideo() const
146 bool MediaPlayerPrivateMediaFoundation::hasAudio() const
151 void MediaPlayerPrivateMediaFoundation::setVisible(bool visible)
156 bool MediaPlayerPrivateMediaFoundation::seeking() const
162 bool MediaPlayerPrivateMediaFoundation::paused() const
167 MediaPlayer::NetworkState MediaPlayerPrivateMediaFoundation::networkState() const
170 return MediaPlayer::Empty;
173 MediaPlayer::ReadyState MediaPlayerPrivateMediaFoundation::readyState() const
178 std::unique_ptr<PlatformTimeRanges> MediaPlayerPrivateMediaFoundation::buffered() const
181 return PlatformTimeRanges::create();
184 bool MediaPlayerPrivateMediaFoundation::didLoadingProgress() const
186 return m_loadingProgress;
189 void MediaPlayerPrivateMediaFoundation::setSize(const IntSize& size)
196 LayoutSize scrollOffset;
198 FrameView* view = nullptr;
199 if (m_player && m_player->cachedResourceLoader() && m_player->cachedResourceLoader()->document())
200 view = m_player->cachedResourceLoader()->document()->view();
202 scrollOffset = view->scrollOffsetForFixedPosition();
204 int xPos = -scrollOffset.width().toInt() + m_lastPaintRect.x();
205 int yPos = -scrollOffset.height().toInt() + m_lastPaintRect.y();
207 if (m_hwndVideo && !m_lastPaintRect.isEmpty())
208 ::MoveWindow(m_hwndVideo, xPos, yPos, m_size.width(), m_size.height(), FALSE);
210 RECT rc = { 0, 0, m_size.width(), m_size.height() };
211 m_videoDisplay->SetVideoPosition(nullptr, &rc);
214 void MediaPlayerPrivateMediaFoundation::paint(GraphicsContext* context, const IntRect& rect)
216 if (context->paintingDisabled()
217 || !m_player->visible())
220 m_lastPaintRect = rect;
222 // We currently let Media Foundation handle the drawing, by providing a handle to the window to draw in.
223 // We should instead read individual frames from the stream, and paint them into the graphics context here.
228 bool MediaPlayerPrivateMediaFoundation::createSession()
230 if (FAILED(MFStartup(MF_VERSION, MFSTARTUP_FULL)))
233 if (FAILED(MFCreateMediaSession(nullptr, &m_mediaSession)))
237 AsyncCallback* callback = new AsyncCallback(this, true);
238 HRESULT hr = m_mediaSession->BeginGetEvent(callback, nullptr);
239 ASSERT(SUCCEEDED(hr));
244 bool MediaPlayerPrivateMediaFoundation::endSession()
246 if (m_mediaSession) {
247 m_mediaSession->Shutdown();
248 m_mediaSession->Release();
249 m_mediaSession = nullptr;
252 HRESULT hr = MFShutdown();
253 ASSERT(SUCCEEDED(hr));
258 bool MediaPlayerPrivateMediaFoundation::startCreateMediaSource(const String& url)
260 if (FAILED(MFCreateSourceResolver(&m_sourceResolver)))
263 COMPtr<IUnknown> cancelCookie;
264 Vector<UChar> urlSource = url.charactersWithNullTermination();
266 AsyncCallback* callback = new AsyncCallback(this, false);
268 if (FAILED(m_sourceResolver->BeginCreateObjectFromURL(urlSource.data(), MF_RESOLUTION_MEDIASOURCE, nullptr, &cancelCookie, callback, nullptr)))
274 bool MediaPlayerPrivateMediaFoundation::endCreatedMediaSource(IMFAsyncResult* asyncResult)
276 MF_OBJECT_TYPE objectType;
277 COMPtr<IUnknown> source;
279 HRESULT hr = m_sourceResolver->EndCreateObjectFromURL(asyncResult, &objectType, &source);
283 hr = source->QueryInterface(IID_PPV_ARGS(&m_mediaSource));
287 hr = asyncResult->GetStatus();
288 m_loadingProgress = SUCCEEDED(hr);
290 callOnMainThread([this] {
291 onCreatedMediaSource();
297 bool MediaPlayerPrivateMediaFoundation::endGetEvent(IMFAsyncResult* asyncResult)
299 COMPtr<IMFMediaEvent> event;
301 // Get the event from the event queue.
302 HRESULT hr = m_mediaSession->EndGetEvent(asyncResult, &event);
306 // Get the event type.
307 MediaEventType mediaEventType;
308 hr = event->GetType(&mediaEventType);
312 switch (mediaEventType) {
313 case MESessionTopologySet:
314 callOnMainThread([this] {
319 case MESessionClosed:
323 if (mediaEventType != MESessionClosed) {
324 // For all other events, ask the media session for the
325 // next event in the queue.
326 AsyncCallback* callback = new AsyncCallback(this, true);
328 hr = m_mediaSession->BeginGetEvent(callback, nullptr);
336 bool MediaPlayerPrivateMediaFoundation::createTopologyFromSource()
338 // Create a new topology.
339 if (FAILED(MFCreateTopology(&m_topology)))
342 // Create the presentation descriptor for the media source.
343 if (FAILED(m_mediaSource->CreatePresentationDescriptor(&m_sourcePD)))
346 // Get the number of streams in the media source.
347 DWORD sourceStreams = 0;
348 if (FAILED(m_sourcePD->GetStreamDescriptorCount(&sourceStreams)))
351 // For each stream, create the topology nodes and add them to the topology.
352 for (DWORD i = 0; i < sourceStreams; i++) {
353 if (!addBranchToPartialTopology(i))
360 bool MediaPlayerPrivateMediaFoundation::addBranchToPartialTopology(int stream)
362 // Get the stream descriptor for this stream.
363 COMPtr<IMFStreamDescriptor> sourceSD;
364 BOOL selected = FALSE;
365 if (FAILED(m_sourcePD->GetStreamDescriptorByIndex(stream, &selected, &sourceSD)))
368 // Create the topology branch only if the stream is selected.
369 // Otherwise, do nothing.
373 // Create a source node for this stream.
374 COMPtr<IMFTopologyNode> sourceNode;
375 if (!createSourceStreamNode(sourceSD, sourceNode))
378 COMPtr<IMFTopologyNode> outputNode;
379 if (!createOutputNode(sourceSD, outputNode))
382 // Add both nodes to the topology.
383 if (FAILED(m_topology->AddNode(sourceNode.get())))
386 if (FAILED(m_topology->AddNode(outputNode.get())))
389 // Connect the source node to the output node.
390 if (FAILED(sourceNode->ConnectOutput(0, outputNode.get(), 0)))
396 LRESULT CALLBACK MediaPlayerPrivateMediaFoundation::VideoViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
398 return DefWindowProc(hWnd, message, wParam, lParam);
401 LPCWSTR MediaPlayerPrivateMediaFoundation::registerVideoWindowClass()
403 const LPCWSTR kVideoWindowClassName = L"WebVideoWindowClass";
405 static bool haveRegisteredWindowClass = false;
406 if (haveRegisteredWindowClass)
407 return kVideoWindowClassName;
409 haveRegisteredWindowClass = true;
413 wcex.cbSize = sizeof(WNDCLASSEX);
415 wcex.style = CS_DBLCLKS;
416 wcex.lpfnWndProc = VideoViewWndProc;
419 wcex.hInstance = nullptr;
420 wcex.hIcon = nullptr;
421 wcex.hCursor = ::LoadCursor(0, IDC_ARROW);
422 wcex.hbrBackground = nullptr;
423 wcex.lpszMenuName = nullptr;
424 wcex.lpszClassName = kVideoWindowClassName;
425 wcex.hIconSm = nullptr;
427 if (RegisterClassEx(&wcex))
428 return kVideoWindowClassName;
433 void MediaPlayerPrivateMediaFoundation::createVideoWindow()
435 HWND hWndParent = nullptr;
436 FrameView* view = nullptr;
437 if (!m_player || !m_player->cachedResourceLoader() || !m_player->cachedResourceLoader()->document())
439 view = m_player->cachedResourceLoader()->document()->view();
440 if (!view || !view->hostWindow())
442 hWndParent = view->hostWindow()->platformPageClient();
444 m_hwndVideo = CreateWindowEx(WS_EX_NOACTIVATE | WS_EX_TRANSPARENT, registerVideoWindowClass(), 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
445 0, 0, 0, 0, hWndParent, 0, 0, 0);
448 void MediaPlayerPrivateMediaFoundation::destroyVideoWindow()
451 DestroyWindow(m_hwndVideo);
452 m_hwndVideo = nullptr;
456 bool MediaPlayerPrivateMediaFoundation::createOutputNode(COMPtr<IMFStreamDescriptor> sourceSD, COMPtr<IMFTopologyNode>& node)
462 // Get the stream ID.
464 sourceSD->GetStreamIdentifier(&streamID); // Just for debugging, ignore any failures.
467 COMPtr<IMFMediaTypeHandler> handler;
468 if (FAILED(sourceSD->GetMediaTypeHandler(&handler)))
471 GUID guidMajorType = GUID_NULL;
472 if (FAILED(handler->GetMajorType(&guidMajorType)))
475 // Create a downstream node.
476 if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node)))
479 // Create an IMFActivate object for the renderer, based on the media type.
480 COMPtr<IMFActivate> rendererActivate;
481 if (MFMediaType_Audio == guidMajorType) {
482 // Create the audio renderer.
483 if (FAILED(MFCreateAudioRendererActivate(&rendererActivate)))
486 } else if (MFMediaType_Video == guidMajorType) {
487 // Create the video renderer.
488 if (FAILED(MFCreateVideoRendererActivate(m_hwndVideo, &rendererActivate)))
494 // Set the IActivate object on the output node.
495 if (FAILED(node->SetObject(rendererActivate.get())))
501 bool MediaPlayerPrivateMediaFoundation::createSourceStreamNode(COMPtr<IMFStreamDescriptor> sourceSD, COMPtr<IMFTopologyNode>& node)
503 if (!m_mediaSource || !m_sourcePD || !sourceSD)
506 // Create the source-stream node.
507 HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node);
511 // Set attribute: Pointer to the media source.
512 hr = node->SetUnknown(MF_TOPONODE_SOURCE, m_mediaSource.get());
516 // Set attribute: Pointer to the presentation descriptor.
517 hr = node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, m_sourcePD.get());
521 // Set attribute: Pointer to the stream descriptor.
522 hr = node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, sourceSD.get());
529 void MediaPlayerPrivateMediaFoundation::onCreatedMediaSource()
531 if (!createTopologyFromSource())
534 // Set the topology on the media session.
535 HRESULT hr = m_mediaSession->SetTopology(0, m_topology.get());
536 ASSERT(SUCCEEDED(hr));
539 void MediaPlayerPrivateMediaFoundation::onTopologySet()
541 if (FAILED(MFGetService(m_mediaSession.get(), MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_videoDisplay))))
544 ASSERT(m_videoDisplay);
546 RECT rc = { 0, 0, m_size.width(), m_size.height() };
547 m_videoDisplay->SetVideoPosition(nullptr, &rc);
549 m_readyState = MediaPlayer::HaveFutureData;
552 m_player->readyStateChanged();
555 m_player->playbackStateChanged();
558 MediaPlayerPrivateMediaFoundation::AsyncCallback::AsyncCallback(MediaPlayerPrivateMediaFoundation* mediaPlayer, bool event)
560 , m_mediaPlayer(mediaPlayer)
565 MediaPlayerPrivateMediaFoundation::AsyncCallback::~AsyncCallback()
569 HRESULT MediaPlayerPrivateMediaFoundation::AsyncCallback::QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject)
571 if (!IsEqualGUID(riid, IID_IMFAsyncCallback)) {
572 *ppvObject = nullptr;
573 return E_NOINTERFACE;
580 ULONG STDMETHODCALLTYPE MediaPlayerPrivateMediaFoundation::AsyncCallback::AddRef()
586 ULONG STDMETHODCALLTYPE MediaPlayerPrivateMediaFoundation::AsyncCallback::Release()
589 ULONG refCount = m_refCount;
595 HRESULT STDMETHODCALLTYPE MediaPlayerPrivateMediaFoundation::AsyncCallback::GetParameters(__RPC__out DWORD *pdwFlags, __RPC__out DWORD *pdwQueue)
597 // Returning E_NOTIMPL gives default values.
601 HRESULT STDMETHODCALLTYPE MediaPlayerPrivateMediaFoundation::AsyncCallback::Invoke(__RPC__in_opt IMFAsyncResult *pAsyncResult)
604 m_mediaPlayer->endGetEvent(pAsyncResult);
606 m_mediaPlayer->endCreatedMediaSource(pAsyncResult);