2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #if JUCE_INCLUDED_FILE && JUCE_DIRECTSHOW
29 //======================================================================
30 namespace DirectShowHelpers
32 bool checkDShowAvailability()
34 ComSmartPtr
<IGraphBuilder
> graph
;
35 return SUCCEEDED (graph
.CoCreateInstance (CLSID_FilterGraph
));
38 //======================================================================
43 virtual ~VideoRenderer() {}
45 virtual HRESULT
create (ComSmartPtr
<IGraphBuilder
>& graphBuilder
,
46 ComSmartPtr
<IBaseFilter
>& baseFilter
, HWND hwnd
) = 0;
48 virtual void setVideoWindow (HWND hwnd
) = 0;
49 virtual void setVideoPosition (HWND hwnd
, long videoWidth
, long videoHeight
) = 0;
50 virtual void repaintVideo (HWND hwnd
, HDC hdc
) = 0;
51 virtual void displayModeChanged() = 0;
52 virtual HRESULT
getVideoSize (long& videoWidth
, long& videoHeight
) = 0;
55 //======================================================================
56 class VMR7
: public VideoRenderer
61 HRESULT
create (ComSmartPtr
<IGraphBuilder
>& graphBuilder
,
62 ComSmartPtr
<IBaseFilter
>& baseFilter
, HWND hwnd
)
64 ComSmartPtr
<IVMRFilterConfig
> filterConfig
;
66 HRESULT hr
= baseFilter
.CoCreateInstance (CLSID_VideoMixingRenderer
);
69 hr
= graphBuilder
->AddFilter (baseFilter
, L
"VMR-7");
72 hr
= baseFilter
.QueryInterface (filterConfig
);
75 hr
= filterConfig
->SetRenderingMode (VMRMode_Windowless
);
78 hr
= baseFilter
.QueryInterface (windowlessControl
);
81 hr
= windowlessControl
->SetVideoClippingWindow (hwnd
);
84 hr
= windowlessControl
->SetAspectRatioMode (VMR_ARMODE_LETTER_BOX
);
89 void setVideoWindow (HWND hwnd
)
91 windowlessControl
->SetVideoClippingWindow (hwnd
);
94 void setVideoPosition (HWND hwnd
, long videoWidth
, long videoHeight
)
98 SetRect (&src
, 0, 0, videoWidth
, videoHeight
);
99 GetClientRect (hwnd
, &dest
);
101 windowlessControl
->SetVideoPosition (&src
, &dest
);
104 void repaintVideo (HWND hwnd
, HDC hdc
)
106 windowlessControl
->RepaintVideo (hwnd
, hdc
);
109 void displayModeChanged()
111 windowlessControl
->DisplayModeChanged();
114 HRESULT
getVideoSize (long& videoWidth
, long& videoHeight
)
116 return windowlessControl
->GetNativeVideoSize (&videoWidth
, &videoHeight
, nullptr, nullptr);
120 ComSmartPtr
<IVMRWindowlessControl
> windowlessControl
;
122 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7
);
126 //======================================================================
127 #if JUCE_MEDIAFOUNDATION
128 class EVR
: public VideoRenderer
133 HRESULT
create (ComSmartPtr
<IGraphBuilder
>& graphBuilder
,
134 ComSmartPtr
<IBaseFilter
>& baseFilter
, HWND hwnd
)
136 ComSmartPtr
<IMFGetService
> getService
;
138 HRESULT hr
= baseFilter
.CoCreateInstance (CLSID_EnhancedVideoRenderer
);
141 hr
= graphBuilder
->AddFilter (baseFilter
, L
"EVR");
144 hr
= baseFilter
.QueryInterface (getService
);
147 hr
= getService
->GetService (MR_VIDEO_RENDER_SERVICE
, IID_IMFVideoDisplayControl
,
148 (LPVOID
*) videoDisplayControl
.resetAndGetPointerAddress());
151 hr
= videoDisplayControl
->SetVideoWindow (hwnd
);
154 hr
= videoDisplayControl
->SetAspectRatioMode (MFVideoARMode_PreservePicture
);
159 void setVideoWindow (HWND hwnd
)
161 videoDisplayControl
->SetVideoWindow (hwnd
);
164 void setVideoPosition (HWND hwnd
, long /*videoWidth*/, long /*videoHeight*/)
166 const MFVideoNormalizedRect src
= { 0.0f
, 0.0f
, 1.0f
, 1.0f
};
169 GetClientRect (hwnd
, &dest
);
171 videoDisplayControl
->SetVideoPosition (&src
, &dest
);
174 void repaintVideo (HWND
/*hwnd*/, HDC
/*hdc*/)
176 videoDisplayControl
->RepaintVideo();
179 void displayModeChanged() {}
181 HRESULT
getVideoSize (long& videoWidth
, long& videoHeight
)
184 HRESULT hr
= videoDisplayControl
->GetNativeVideoSize (&sz
, nullptr);
191 ComSmartPtr
<IMFVideoDisplayControl
> videoDisplayControl
;
193 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR
);
199 //======================================================================
200 class DirectShowComponent::DirectShowContext
203 DirectShowContext (DirectShowComponent
& component_
, VideoRendererType type_
)
204 : component (component_
),
207 state (uninitializedState
),
215 if (type
== dshowDefault
)
219 #if JUCE_MEDIAFOUNDATION
220 if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista
)
232 //======================================================================
233 HWND
getNativeWindowHandle() const
235 return nativeWindow
!= nullptr ? nativeWindow
->getHandle() : 0;
238 //======================================================================
239 void updateWindowPosition (const Rectangle
<int>& newBounds
)
241 nativeWindow
->setWindowPosition (newBounds
);
244 void showWindow (bool shouldBeVisible
)
246 nativeWindow
->showWindow (shouldBeVisible
);
249 //======================================================================
253 videoRenderer
->repaintVideo (nativeWindow
->getHandle(), nativeWindow
->getContext());
256 void updateVideoPosition()
259 videoRenderer
->setVideoPosition (nativeWindow
->getHandle(), videoWidth
, videoHeight
);
262 void displayResolutionChanged()
265 videoRenderer
->displayModeChanged();
268 //======================================================================
271 deleteNativeWindow();
273 mediaEvent
->SetNotifyWindow (0, 0, 0);
274 if (videoRenderer
!= nullptr)
275 videoRenderer
->setVideoWindow (nullptr);
277 createNativeWindow();
279 mediaEvent
->SetNotifyWindow ((OAHWND
) hwnd
, graphEventID
, 0);
280 if (videoRenderer
!= nullptr)
281 videoRenderer
->setVideoWindow (hwnd
);
284 //======================================================================
285 bool loadFile (const String
& fileOrURLPath
)
287 jassert (state
== uninitializedState
);
289 if (! createNativeWindow())
292 HRESULT hr
= graphBuilder
.CoCreateInstance (CLSID_FilterGraph
);
294 // basic playback interfaces
295 if (SUCCEEDED (hr
)) hr
= graphBuilder
.QueryInterface (mediaControl
);
296 if (SUCCEEDED (hr
)) hr
= graphBuilder
.QueryInterface (mediaPosition
);
297 if (SUCCEEDED (hr
)) hr
= graphBuilder
.QueryInterface (mediaEvent
);
298 if (SUCCEEDED (hr
)) hr
= graphBuilder
.QueryInterface (basicAudio
);
300 // video renderer interface
303 #if JUCE_MEDIAFOUNDATION
304 if (type
== dshowEVR
)
305 videoRenderer
= new DirectShowHelpers::EVR();
308 videoRenderer
= new DirectShowHelpers::VMR7();
310 hr
= videoRenderer
->create (graphBuilder
, baseFilter
, hwnd
);
313 // build filter graph
315 hr
= graphBuilder
->RenderFile (fileOrURLPath
.toWideCharPointer(), nullptr);
317 // remove video renderer if not connected (no video)
320 if (isRendererConnected())
323 hr
= videoRenderer
->getVideoSize (videoWidth
, videoHeight
);
328 graphBuilder
->RemoveFilter (baseFilter
);
329 videoRenderer
= nullptr;
330 baseFilter
= nullptr;
334 // set window to receive events
336 hr
= mediaEvent
->SetNotifyWindow ((OAHWND
) hwnd
, graphEventID
, 0);
340 state
= stoppedState
;
350 if (mediaControl
!= nullptr)
351 mediaControl
->Stop();
353 if (mediaEvent
!= nullptr)
354 mediaEvent
->SetNotifyWindow (0, 0, 0);
356 if (videoRenderer
!= nullptr)
357 videoRenderer
->setVideoWindow (0);
360 videoRenderer
= nullptr;
362 baseFilter
= nullptr;
363 basicAudio
= nullptr;
364 mediaEvent
= nullptr;
365 mediaPosition
= nullptr;
366 mediaControl
= nullptr;
367 graphBuilder
= nullptr;
369 state
= uninitializedState
;
374 if (nativeWindow
!= nullptr)
375 deleteNativeWindow();
378 void graphEventProc()
383 jassert (mediaEvent
!= nullptr);
385 while (SUCCEEDED (mediaEvent
->GetEvent (&ec
, &p1
, &p2
, 0)))
394 if (component
.isLooping())
395 component
.goToStart();
402 case EC_ERRORABORTEX
:
403 component
.closeMovie();
410 mediaEvent
->FreeEventParams (ec
, p1
, p2
);
414 //======================================================================
418 state
= runningState
;
423 mediaControl
->Stop();
424 state
= stoppedState
;
429 mediaControl
->Pause();
433 //======================================================================
434 bool isInitialised() const noexcept
{ return state
!= uninitializedState
; }
435 bool isRunning() const noexcept
{ return state
== runningState
; }
436 bool isPaused() const noexcept
{ return state
== pausedState
; }
437 bool isStopped() const noexcept
{ return state
== stoppedState
; }
438 bool containsVideo() const noexcept
{ return hasVideo
; }
439 int getVideoWidth() const noexcept
{ return (int) videoWidth
; }
440 int getVideoHeight() const noexcept
{ return (int) videoHeight
; }
442 //======================================================================
443 double getDuration() const
446 mediaPosition
->get_Duration (&duration
);
450 double getPosition() const
453 mediaPosition
->get_CurrentPosition (&seconds
);
457 //======================================================================
458 void setSpeed (const float newSpeed
) { mediaPosition
->put_Rate (newSpeed
); }
459 void setPosition (const double seconds
) { mediaPosition
->put_CurrentPosition (seconds
); }
460 void setVolume (const float newVolume
) { basicAudio
->put_Volume (convertToDShowVolume (newVolume
)); }
462 // in DirectShow, full volume is 0, silence is -10000
463 static long convertToDShowVolume (const float vol
) noexcept
465 if (vol
>= 1.0f
) return 0;
466 if (vol
<= 0.0f
) return -10000;
468 return roundToInt ((vol
* 10000.0f
) - 10000.0f
);
471 float getVolume() const
474 basicAudio
->get_Volume (&volume
);
475 return (volume
+ 10000) / 10000.0f
;
479 //======================================================================
480 enum { graphEventID
= WM_APP
+ 0x43f0 };
482 DirectShowComponent
& component
;
486 enum State
{ uninitializedState
, runningState
, pausedState
, stoppedState
};
490 long videoWidth
, videoHeight
;
492 VideoRendererType type
;
494 ComSmartPtr
<IGraphBuilder
> graphBuilder
;
495 ComSmartPtr
<IMediaControl
> mediaControl
;
496 ComSmartPtr
<IMediaPosition
> mediaPosition
;
497 ComSmartPtr
<IMediaEventEx
> mediaEvent
;
498 ComSmartPtr
<IBasicAudio
> basicAudio
;
499 ComSmartPtr
<IBaseFilter
> baseFilter
;
501 ScopedPointer
<DirectShowHelpers::VideoRenderer
> videoRenderer
;
503 //======================================================================
504 class NativeWindowClass
: public DeletedAtShutdown
510 String
windowClassName ("JUCE_DIRECTSHOW_");
511 windowClassName
<< (int) (Time::currentTimeMillis() & 0x7fffffff);
513 HINSTANCE moduleHandle
= (HINSTANCE
) PlatformUtilities::getCurrentModuleInstanceHandle();
515 TCHAR moduleFile
[1024] = { 0 };
516 GetModuleFileName (moduleHandle
, moduleFile
, 1024);
518 WNDCLASSEX wcex
= { 0 };
519 wcex
.cbSize
= sizeof (wcex
);
520 wcex
.style
= CS_OWNDC
;
521 wcex
.lpfnWndProc
= (WNDPROC
) wndProc
;
522 wcex
.lpszClassName
= windowClassName
.toWideCharPointer();
523 wcex
.hInstance
= moduleHandle
;
525 atom
= RegisterClassEx (&wcex
);
532 UnregisterClass (getWindowClassName(), (HINSTANCE
) PlatformUtilities::getCurrentModuleInstanceHandle());
534 clearSingletonInstance();
537 static LRESULT CALLBACK
wndProc (HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
539 DirectShowContext
* c
= (DirectShowContext
*) GetWindowLongPtr (hwnd
, GWLP_USERDATA
);
543 jassert (c
->getNativeWindowHandle() == hwnd
);
547 case WM_ERASEBKGND
: return 1;
548 case WM_DISPLAYCHANGE
: c
->displayResolutionChanged(); break;
549 case graphEventID
: c
->graphEventProc(); return 0;
554 return DefWindowProc (hwnd
, msg
, wParam
, lParam
);
558 bool isRegistered() const noexcept
{ return atom
!= 0; }
559 LPCTSTR
getWindowClassName() const noexcept
{ return (LPCTSTR
) MAKELONG (atom
, 0); }
561 juce_DeclareSingleton_SingleThreaded_Minimal (NativeWindowClass
);
566 JUCE_DECLARE_NON_COPYABLE (NativeWindowClass
);
569 //======================================================================
573 NativeWindow (HWND parentToAddTo
, void* const userData
)
576 NativeWindowClass
* const wc
= NativeWindowClass::getInstance();
578 if (wc
->isRegistered())
581 DWORD type
= WS_CHILD
;
583 hwnd
= CreateWindowEx (exstyle
, wc
->getWindowClassName(),
584 L
"", type
, 0, 0, 0, 0, parentToAddTo
, 0,
585 (HINSTANCE
) PlatformUtilities::getCurrentModuleInstanceHandle(), 0);
590 SetWindowLongPtr (hwnd
, GWLP_USERDATA
, (LONG_PTR
) userData
);
601 SetWindowLongPtr (hwnd
, GWLP_USERDATA
, (LONG_PTR
) 0);
602 DestroyWindow (hwnd
);
606 HWND
getHandle() const noexcept
{ return hwnd
; }
607 HDC
getContext() const noexcept
{ return hdc
; }
609 void setWindowPosition (const Rectangle
<int>& newBounds
)
611 SetWindowPos (hwnd
, 0, newBounds
.getX(), newBounds
.getY(),
612 newBounds
.getWidth(), newBounds
.getHeight(),
613 SWP_NOACTIVATE
| SWP_NOZORDER
| SWP_NOOWNERZORDER
);
616 void showWindow (const bool shouldBeVisible
)
618 ShowWindow (hwnd
, shouldBeVisible
? SW_SHOWNA
: SW_HIDE
);
625 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow
);
628 ScopedPointer
<NativeWindow
> nativeWindow
;
630 //======================================================================
631 bool createNativeWindow()
633 jassert (nativeWindow
== nullptr);
635 ComponentPeer
* topLevelPeer
= component
.getTopLevelComponent()->getPeer();
637 jassert (topLevelPeer
!= nullptr);
639 if (topLevelPeer
!= nullptr)
641 nativeWindow
= new NativeWindow ((HWND
) topLevelPeer
->getNativeHandle(), this);
643 hwnd
= nativeWindow
->getHandle();
648 component
.updateContextPosition();
649 component
.showContext (component
.isShowing());
654 nativeWindow
= nullptr;
661 void deleteNativeWindow()
663 jassert (nativeWindow
!= nullptr);
664 ReleaseDC (hwnd
, hdc
);
667 nativeWindow
= nullptr;
670 bool isRendererConnected()
672 ComSmartPtr
<IEnumPins
> enumPins
;
674 HRESULT hr
= baseFilter
->EnumPins (enumPins
.resetAndGetPointerAddress());
677 hr
= enumPins
->Reset();
679 ComSmartPtr
<IPin
> pin
;
681 while (SUCCEEDED (hr
)
682 && enumPins
->Next (1, pin
.resetAndGetPointerAddress(), nullptr) == S_OK
)
684 ComSmartPtr
<IPin
> otherPin
;
686 hr
= pin
->ConnectedTo (otherPin
.resetAndGetPointerAddress());
690 PIN_DIRECTION direction
;
691 hr
= pin
->QueryDirection (&direction
);
693 if (SUCCEEDED (hr
) && direction
== PINDIR_INPUT
)
696 else if (hr
== VFW_E_NOT_CONNECTED
)
705 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext
);
708 juce_ImplementSingleton_SingleThreaded (DirectShowComponent::DirectShowContext::NativeWindowClass
);
711 //======================================================================
712 class DirectShowComponent::DirectShowComponentWatcher
: public ComponentMovementWatcher
715 DirectShowComponentWatcher (DirectShowComponent
* const owner_
)
716 : ComponentMovementWatcher (owner_
),
721 void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/)
723 if (owner
->videoLoaded
)
724 owner
->updateContextPosition();
727 void componentPeerChanged()
729 if (owner
->videoLoaded
)
730 owner
->recreateNativeWindowAsync();
733 void componentVisibilityChanged()
735 if (owner
->videoLoaded
)
736 owner
->showContext (owner
->isShowing());
739 //======================================================================
741 DirectShowComponent
* const owner
;
743 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowComponentWatcher
);
747 //======================================================================
748 DirectShowComponent::DirectShowComponent (VideoRendererType type
)
749 : videoLoaded (false),
751 needToUpdateViewport (true),
752 needToRecreateNativeWindow (false)
755 context
= new DirectShowContext (*this, type
);
756 componentWatcher
= new DirectShowComponentWatcher (this);
759 DirectShowComponent::~DirectShowComponent()
761 componentWatcher
= nullptr;
764 bool DirectShowComponent::isDirectShowAvailable()
766 static bool isDSAvailable
= DirectShowHelpers::checkDShowAvailability();
767 return isDSAvailable
;
770 void DirectShowComponent::recreateNativeWindowAsync()
772 needToRecreateNativeWindow
= true;
776 void DirectShowComponent::updateContextPosition()
778 needToUpdateViewport
= true;
780 if (getWidth() > 0 && getHeight() > 0)
782 Component
* const topComp
= getTopLevelComponent();
784 if (topComp
->getPeer() != nullptr)
785 context
->updateWindowPosition (topComp
->getLocalArea (this, getLocalBounds()));
789 void DirectShowComponent::showContext (const bool shouldBeVisible
)
791 context
->showWindow (shouldBeVisible
);
794 void DirectShowComponent::paint (Graphics
& g
)
798 if (needToRecreateNativeWindow
)
800 context
->peerChanged();
801 needToRecreateNativeWindow
= false;
804 if (needToUpdateViewport
)
806 context
->updateVideoPosition();
807 needToUpdateViewport
= false;
812 ComponentPeer
* const peer
= getPeer();
816 const Point
<int> topLeft (getScreenPosition() - peer
->getScreenPosition());
817 peer
->addMaskedRegion (topLeft
.getX(), topLeft
.getY(), getWidth(), getHeight());
822 g
.fillAll (Colours::grey
);
826 //======================================================================
827 bool DirectShowComponent::loadMovie (const String
& fileOrURLPath
)
831 videoLoaded
= context
->loadFile (fileOrURLPath
);
835 videoPath
= fileOrURLPath
;
836 context
->updateVideoPosition();
842 bool DirectShowComponent::loadMovie (const File
& videoFile
)
844 return loadMovie (videoFile
.getFullPathName());
847 bool DirectShowComponent::loadMovie (const URL
& videoURL
)
849 return loadMovie (videoURL
.toString (false));
852 void DirectShowComponent::closeMovie()
858 videoPath
= String::empty
;
861 //======================================================================
862 File
DirectShowComponent::getCurrentMoviePath() const { return videoPath
; }
863 bool DirectShowComponent::isMovieOpen() const { return videoLoaded
; }
864 double DirectShowComponent::getMovieDuration() const { return videoLoaded
? context
->getDuration() : 0.0; }
865 void DirectShowComponent::setLooping (const bool shouldLoop
) { looping
= shouldLoop
; }
866 bool DirectShowComponent::isLooping() const { return looping
; }
868 void DirectShowComponent::getMovieNormalSize (int &width
, int &height
) const
870 width
= context
->getVideoWidth();
871 height
= context
->getVideoHeight();
874 //======================================================================
875 void DirectShowComponent::setBoundsWithCorrectAspectRatio (const Rectangle
<int>& spaceToFitWithin
,
876 const RectanglePlacement
& placement
)
878 int normalWidth
, normalHeight
;
879 getMovieNormalSize (normalWidth
, normalHeight
);
881 const Rectangle
<int> normalSize (0, 0, normalWidth
, normalHeight
);
883 if (! (spaceToFitWithin
.isEmpty() || normalSize
.isEmpty()))
884 setBounds (placement
.appliedTo (normalSize
, spaceToFitWithin
));
886 setBounds (spaceToFitWithin
);
889 //======================================================================
890 void DirectShowComponent::play()
896 void DirectShowComponent::stop()
902 bool DirectShowComponent::isPlaying() const
904 return context
->isRunning();
907 void DirectShowComponent::goToStart()
912 void DirectShowComponent::setPosition (const double seconds
)
915 context
->setPosition (seconds
);
918 double DirectShowComponent::getPosition() const
920 return videoLoaded
? context
->getPosition() : 0.0;
923 void DirectShowComponent::setSpeed (const float newSpeed
)
926 context
->setSpeed (newSpeed
);
929 void DirectShowComponent::setMovieVolume (const float newVolume
)
932 context
->setVolume (newVolume
);
935 float DirectShowComponent::getMovieVolume() const
937 return videoLoaded
? context
->getVolume() : 0.0f
;