mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-02-04 03:40:07 +00:00
New class: DirectShowComponent, for native video playback in Windows. Added a demo page for this to the juce demo app.
This commit is contained in:
parent
a6f3466852
commit
b94782d388
50 changed files with 2643 additions and 47 deletions
941
src/native/windows/juce_win32_DirectShowComponent.cpp
Normal file
941
src/native/windows/juce_win32_DirectShowComponent.cpp
Normal file
|
|
@ -0,0 +1,941 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
||||
Copyright 2004-11 by Raw Material Software Ltd.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
JUCE can be redistributed and/or modified under the terms of the GNU General
|
||||
Public License (Version 2), as published by the Free Software Foundation.
|
||||
A copy of the license is included in the JUCE distribution, or can be found
|
||||
online at www.gnu.org/licenses.
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.rawmaterialsoftware.com/juce for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_INCLUDED_FILE
|
||||
|
||||
|
||||
//======================================================================
|
||||
namespace DirectShowHelpers
|
||||
{
|
||||
bool checkDShowAvailability()
|
||||
{
|
||||
ComSmartPtr <IGraphBuilder> graph;
|
||||
return SUCCEEDED (graph.CoCreateInstance (CLSID_FilterGraph));
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
class VideoRenderer
|
||||
{
|
||||
public:
|
||||
VideoRenderer() {}
|
||||
virtual ~VideoRenderer() {}
|
||||
|
||||
virtual HRESULT create (ComSmartPtr <IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr <IBaseFilter>& baseFilter, HWND hwnd) = 0;
|
||||
|
||||
virtual void setVideoWindow (HWND hwnd) = 0;
|
||||
virtual void setVideoPosition (HWND hwnd, long videoWidth, long videoHeight) = 0;
|
||||
virtual void repaintVideo (HWND hwnd, HDC hdc) = 0;
|
||||
virtual void displayModeChanged() = 0;
|
||||
virtual HRESULT getVideoSize (long& videoWidth, long& videoHeight) = 0;
|
||||
};
|
||||
|
||||
//======================================================================
|
||||
class VMR7 : public VideoRenderer
|
||||
{
|
||||
public:
|
||||
VMR7() {}
|
||||
|
||||
HRESULT create (ComSmartPtr <IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr <IBaseFilter>& baseFilter, HWND hwnd)
|
||||
{
|
||||
ComSmartPtr <IVMRFilterConfig> filterConfig;
|
||||
|
||||
HRESULT hr = baseFilter.CoCreateInstance (CLSID_VideoMixingRenderer);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = graphBuilder->AddFilter (baseFilter, L"VMR-7");
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = baseFilter.QueryInterface (IID_IVMRFilterConfig, filterConfig);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = filterConfig->SetRenderingMode (VMRMode_Windowless);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = baseFilter.QueryInterface (IID_IVMRWindowlessControl, windowlessControl);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = windowlessControl->SetVideoClippingWindow (hwnd);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = windowlessControl->SetAspectRatioMode (VMR_ARMODE_LETTER_BOX);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void setVideoWindow (HWND hwnd)
|
||||
{
|
||||
windowlessControl->SetVideoClippingWindow (hwnd);
|
||||
}
|
||||
|
||||
void setVideoPosition (HWND hwnd, long videoWidth, long videoHeight)
|
||||
{
|
||||
RECT src, dest;
|
||||
|
||||
SetRect (&src, 0, 0, videoWidth, videoHeight);
|
||||
GetClientRect (hwnd, &dest);
|
||||
|
||||
windowlessControl->SetVideoPosition (&src, &dest);
|
||||
}
|
||||
|
||||
void repaintVideo (HWND hwnd, HDC hdc)
|
||||
{
|
||||
windowlessControl->RepaintVideo (hwnd, hdc);
|
||||
}
|
||||
|
||||
void displayModeChanged()
|
||||
{
|
||||
windowlessControl->DisplayModeChanged();
|
||||
}
|
||||
|
||||
HRESULT getVideoSize (long& videoWidth, long& videoHeight)
|
||||
{
|
||||
return windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
ComSmartPtr <IVMRWindowlessControl> windowlessControl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7);
|
||||
};
|
||||
|
||||
|
||||
//======================================================================
|
||||
#if JUCE_MEDIAFOUNDATION
|
||||
class EVR : public VideoRenderer
|
||||
{
|
||||
public:
|
||||
EVR() {}
|
||||
|
||||
HRESULT create (ComSmartPtr <IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr <IBaseFilter>& baseFilter, HWND hwnd)
|
||||
{
|
||||
ComSmartPtr <IMFGetService> getService;
|
||||
|
||||
HRESULT hr = baseFilter.CoCreateInstance (CLSID_EnhancedVideoRenderer);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = graphBuilder->AddFilter (baseFilter, L"EVR");
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = baseFilter.QueryInterface (IID_IMFGetService, getService);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = getService->GetService (MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl,
|
||||
(LPVOID*) videoDisplayControl.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = videoDisplayControl->SetVideoWindow (hwnd);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = videoDisplayControl->SetAspectRatioMode (MFVideoARMode_PreservePicture);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void setVideoWindow (HWND hwnd)
|
||||
{
|
||||
videoDisplayControl->SetVideoWindow (hwnd);
|
||||
}
|
||||
|
||||
void setVideoPosition (HWND hwnd, long /*videoWidth*/, long /*videoHeight*/)
|
||||
{
|
||||
const MFVideoNormalizedRect src = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
|
||||
RECT dest;
|
||||
GetClientRect (hwnd, &dest);
|
||||
|
||||
videoDisplayControl->SetVideoPosition (&src, &dest);
|
||||
}
|
||||
|
||||
void repaintVideo (HWND /*hwnd*/, HDC /*hdc*/)
|
||||
{
|
||||
videoDisplayControl->RepaintVideo();
|
||||
}
|
||||
|
||||
void displayModeChanged() {}
|
||||
|
||||
HRESULT getVideoSize (long& videoWidth, long& videoHeight)
|
||||
{
|
||||
SIZE sz;
|
||||
HRESULT hr = videoDisplayControl->GetNativeVideoSize (&sz, nullptr);
|
||||
videoWidth = sz.cx;
|
||||
videoHeight = sz.cy;
|
||||
return hr;
|
||||
}
|
||||
|
||||
private:
|
||||
ComSmartPtr <IMFVideoDisplayControl> videoDisplayControl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR);
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//======================================================================
|
||||
class DirectShowComponent::DirectShowContext
|
||||
{
|
||||
public:
|
||||
DirectShowContext (DirectShowComponent& component_, VideoRendererType type_)
|
||||
: component (component_),
|
||||
hwnd (0),
|
||||
hdc (0),
|
||||
state (uninitializedState),
|
||||
hasVideo (false),
|
||||
videoWidth (0),
|
||||
videoHeight (0),
|
||||
type (type_)
|
||||
{
|
||||
CoInitialize (0);
|
||||
|
||||
if (type == dshowDefault)
|
||||
{
|
||||
type = dshowVMR7;
|
||||
|
||||
#if JUCE_MEDIAFOUNDATION
|
||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
|
||||
type = dshowEVR;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
~DirectShowContext()
|
||||
{
|
||||
release();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
HWND getNativeWindowHandle() const
|
||||
{
|
||||
return nativeWindow != nullptr ? nativeWindow->getHandle() : 0;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void updateWindowPosition (const Rectangle<int>& newBounds)
|
||||
{
|
||||
nativeWindow->setWindowPosition (newBounds);
|
||||
}
|
||||
|
||||
void showWindow (bool shouldBeVisible)
|
||||
{
|
||||
nativeWindow->showWindow (shouldBeVisible);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void repaint()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->repaintVideo (nativeWindow->getHandle(), nativeWindow->getContext());
|
||||
}
|
||||
|
||||
void updateVideoPosition()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->setVideoPosition (nativeWindow->getHandle(), videoWidth, videoHeight);
|
||||
}
|
||||
|
||||
void displayResolutionChanged()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->displayModeChanged();
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void peerChanged()
|
||||
{
|
||||
deleteNativeWindow();
|
||||
|
||||
mediaEvent->SetNotifyWindow (0, 0, 0);
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (nullptr);
|
||||
|
||||
createNativeWindow();
|
||||
|
||||
mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (hwnd);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
bool loadFile (const String& fileOrURLPath)
|
||||
{
|
||||
jassert (state == uninitializedState);
|
||||
|
||||
if (! createNativeWindow())
|
||||
return false;
|
||||
|
||||
HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
|
||||
|
||||
// basic playback interfaces
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (IID_IMediaControl, mediaControl);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (IID_IMediaPosition, mediaPosition);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (IID_IMediaEventEx, mediaEvent);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (IID_IBasicAudio, basicAudio);
|
||||
|
||||
// video renderer interface
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
#if JUCE_MEDIAFOUNDATION
|
||||
if (type == dshowEVR)
|
||||
videoRenderer = new DirectShowHelpers::EVR();
|
||||
else
|
||||
#endif
|
||||
videoRenderer = new DirectShowHelpers::VMR7();
|
||||
|
||||
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
|
||||
}
|
||||
|
||||
// build filter graph
|
||||
if (SUCCEEDED (hr))
|
||||
hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
|
||||
|
||||
// remove video renderer if not connected (no video)
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (isRendererConnected())
|
||||
{
|
||||
hasVideo = true;
|
||||
hr = videoRenderer->getVideoSize (videoWidth, videoHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasVideo = false;
|
||||
graphBuilder->RemoveFilter (baseFilter);
|
||||
videoRenderer = nullptr;
|
||||
baseFilter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// set window to receive events
|
||||
if (SUCCEEDED (hr))
|
||||
hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
state = stoppedState;
|
||||
return true;
|
||||
}
|
||||
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (mediaControl != nullptr)
|
||||
mediaControl->Stop();
|
||||
|
||||
if (mediaEvent != nullptr)
|
||||
mediaEvent->SetNotifyWindow (0, 0, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (0);
|
||||
|
||||
hasVideo = false;
|
||||
videoRenderer = nullptr;
|
||||
|
||||
baseFilter = nullptr;
|
||||
basicAudio = nullptr;
|
||||
mediaEvent = nullptr;
|
||||
mediaPosition = nullptr;
|
||||
mediaControl = nullptr;
|
||||
graphBuilder = nullptr;
|
||||
|
||||
state = uninitializedState;
|
||||
|
||||
videoWidth = 0;
|
||||
videoHeight = 0;
|
||||
|
||||
if (nativeWindow != nullptr)
|
||||
deleteNativeWindow();
|
||||
}
|
||||
|
||||
void graphEventProc()
|
||||
{
|
||||
LONG ec;
|
||||
LONG_PTR p1, p2;
|
||||
|
||||
jassert (mediaEvent != nullptr);
|
||||
|
||||
while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
|
||||
{
|
||||
switch (ec)
|
||||
{
|
||||
case EC_REPAINT:
|
||||
component.repaint();
|
||||
break;
|
||||
|
||||
case EC_COMPLETE:
|
||||
if (component.isLooping())
|
||||
component.goToStart();
|
||||
else
|
||||
component.stop();
|
||||
break;
|
||||
|
||||
case EC_USERABORT:
|
||||
case EC_ERRORABORT:
|
||||
case EC_ERRORABORTEX:
|
||||
component.closeMovie();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mediaEvent->FreeEventParams (ec, p1, p2);
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void run()
|
||||
{
|
||||
mediaControl->Run();
|
||||
state = runningState;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
mediaControl->Stop();
|
||||
state = stoppedState;
|
||||
}
|
||||
|
||||
void pause()
|
||||
{
|
||||
mediaControl->Pause();
|
||||
state = pausedState;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
bool isInitialised() const noexcept { return state != uninitializedState; }
|
||||
bool isRunning() const noexcept { return state == runningState; }
|
||||
bool isPaused() const noexcept { return state == pausedState; }
|
||||
bool isStopped() const noexcept { return state == stoppedState; }
|
||||
bool containsVideo() const noexcept { return hasVideo; }
|
||||
int getVideoWidth() const noexcept { return (int) videoWidth; }
|
||||
int getVideoHeight() const noexcept { return (int) videoHeight; }
|
||||
|
||||
//======================================================================
|
||||
double getDuration() const
|
||||
{
|
||||
REFTIME duration;
|
||||
mediaPosition->get_Duration (&duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
REFTIME seconds;
|
||||
mediaPosition->get_CurrentPosition (&seconds);
|
||||
return seconds;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void setSpeed (const float newSpeed) { mediaPosition->put_Rate (newSpeed); }
|
||||
void setPosition (const double seconds) { mediaPosition->put_CurrentPosition (seconds); }
|
||||
void setVolume (const float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
|
||||
|
||||
// in DirectShow, full volume is 0, silence is -10000
|
||||
static long convertToDShowVolume (const float vol) noexcept
|
||||
{
|
||||
if (vol >= 1.0f) return 0;
|
||||
if (vol <= 0.0f) return -10000;
|
||||
|
||||
return roundToInt ((vol * 10000.0f) - 10000.0f);
|
||||
}
|
||||
|
||||
float getVolume() const
|
||||
{
|
||||
long volume;
|
||||
basicAudio->get_Volume (&volume);
|
||||
return (volume + 10000) / 10000.0f;
|
||||
}
|
||||
|
||||
private:
|
||||
//======================================================================
|
||||
enum { graphEventID = WM_APP + 0x43f0 };
|
||||
|
||||
DirectShowComponent& component;
|
||||
HWND hwnd;
|
||||
HDC hdc;
|
||||
|
||||
enum State { uninitializedState, runningState, pausedState, stoppedState };
|
||||
State state;
|
||||
|
||||
bool hasVideo;
|
||||
long videoWidth, videoHeight;
|
||||
|
||||
VideoRendererType type;
|
||||
|
||||
ComSmartPtr <IGraphBuilder> graphBuilder;
|
||||
ComSmartPtr <IMediaControl> mediaControl;
|
||||
ComSmartPtr <IMediaPosition> mediaPosition;
|
||||
ComSmartPtr <IMediaEventEx> mediaEvent;
|
||||
ComSmartPtr <IBasicAudio> basicAudio;
|
||||
ComSmartPtr <IBaseFilter> baseFilter;
|
||||
|
||||
ScopedPointer <DirectShowHelpers::VideoRenderer> videoRenderer;
|
||||
|
||||
//======================================================================
|
||||
class NativeWindowClass : public DeletedAtShutdown
|
||||
{
|
||||
private:
|
||||
NativeWindowClass()
|
||||
: atom (0)
|
||||
{
|
||||
String windowClassName ("JUCE_DIRECTSHOW_");
|
||||
windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);
|
||||
|
||||
HINSTANCE moduleHandle = (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle();
|
||||
|
||||
TCHAR moduleFile [1024] = { 0 };
|
||||
GetModuleFileName (moduleHandle, moduleFile, 1024);
|
||||
|
||||
WNDCLASSEX wcex = { 0 };
|
||||
wcex.cbSize = sizeof (wcex);
|
||||
wcex.style = CS_OWNDC;
|
||||
wcex.lpfnWndProc = (WNDPROC) wndProc;
|
||||
wcex.lpszClassName = windowClassName.toWideCharPointer();
|
||||
wcex.hInstance = moduleHandle;
|
||||
|
||||
atom = RegisterClassEx (&wcex);
|
||||
jassert (atom != 0);
|
||||
}
|
||||
|
||||
~NativeWindowClass()
|
||||
{
|
||||
if (atom != 0)
|
||||
UnregisterClass (getWindowClassName(), (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle());
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
DirectShowContext* c = (DirectShowContext*) GetWindowLongPtr (hwnd, GWLP_USERDATA);
|
||||
|
||||
if (c != nullptr)
|
||||
{
|
||||
jassert (c->getNativeWindowHandle() == hwnd);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case WM_ERASEBKGND: return 1;
|
||||
case WM_DISPLAYCHANGE: c->displayResolutionChanged(); break;
|
||||
case graphEventID: c->graphEventProc(); return 0;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc (hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
public:
|
||||
bool isRegistered() const noexcept { return atom != 0; }
|
||||
LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) MAKELONG (atom, 0); }
|
||||
|
||||
juce_DeclareSingleton_SingleThreaded_Minimal (NativeWindowClass);
|
||||
|
||||
private:
|
||||
ATOM atom;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NativeWindowClass);
|
||||
};
|
||||
|
||||
//======================================================================
|
||||
class NativeWindow
|
||||
{
|
||||
public:
|
||||
NativeWindow (HWND parentToAddTo, void* const userData)
|
||||
: hwnd (0), hdc (0)
|
||||
{
|
||||
NativeWindowClass* const wc = NativeWindowClass::getInstance();
|
||||
|
||||
if (wc->isRegistered())
|
||||
{
|
||||
DWORD exstyle = 0;
|
||||
DWORD type = WS_CHILD;
|
||||
|
||||
hwnd = CreateWindowEx (exstyle, wc->getWindowClassName(),
|
||||
L"", type, 0, 0, 0, 0, parentToAddTo, 0,
|
||||
(HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle(), 0);
|
||||
|
||||
if (hwnd != 0)
|
||||
{
|
||||
hdc = GetDC (hwnd);
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) userData);
|
||||
}
|
||||
}
|
||||
|
||||
jassert (hwnd != 0);
|
||||
}
|
||||
|
||||
~NativeWindow()
|
||||
{
|
||||
if (hwnd != 0)
|
||||
{
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) 0);
|
||||
DestroyWindow (hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
HWND getHandle() const noexcept { return hwnd; }
|
||||
HDC getContext() const noexcept { return hdc; }
|
||||
|
||||
void setWindowPosition (const Rectangle<int>& newBounds)
|
||||
{
|
||||
SetWindowPos (hwnd, 0, newBounds.getX(), newBounds.getY(),
|
||||
newBounds.getWidth(), newBounds.getHeight(),
|
||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||||
}
|
||||
|
||||
void showWindow (const bool shouldBeVisible)
|
||||
{
|
||||
ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
||||
}
|
||||
|
||||
private:
|
||||
HWND hwnd;
|
||||
HDC hdc;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow);
|
||||
};
|
||||
|
||||
ScopedPointer<NativeWindow> nativeWindow;
|
||||
|
||||
//======================================================================
|
||||
bool createNativeWindow()
|
||||
{
|
||||
jassert (nativeWindow == nullptr);
|
||||
|
||||
ComponentPeer* topLevelPeer = component.getTopLevelComponent()->getPeer();
|
||||
|
||||
jassert (topLevelPeer != nullptr);
|
||||
|
||||
if (topLevelPeer != nullptr)
|
||||
{
|
||||
nativeWindow = new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this);
|
||||
|
||||
hwnd = nativeWindow->getHandle();
|
||||
|
||||
if (hwnd != 0)
|
||||
{
|
||||
hdc = GetDC (hwnd);
|
||||
component.updateContextPosition();
|
||||
component.showContext (component.isShowing());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nativeWindow = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void deleteNativeWindow()
|
||||
{
|
||||
jassert (nativeWindow != nullptr);
|
||||
ReleaseDC (hwnd, hdc);
|
||||
hwnd = 0;
|
||||
hdc = 0;
|
||||
nativeWindow = nullptr;
|
||||
}
|
||||
|
||||
bool isRendererConnected()
|
||||
{
|
||||
ComSmartPtr <IEnumPins> enumPins;
|
||||
|
||||
HRESULT hr = baseFilter->EnumPins (enumPins.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
hr = enumPins->Reset();
|
||||
|
||||
ComSmartPtr<IPin> pin;
|
||||
|
||||
while (SUCCEEDED (hr)
|
||||
&& enumPins->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
|
||||
{
|
||||
ComSmartPtr<IPin> otherPin;
|
||||
|
||||
hr = pin->ConnectedTo (otherPin.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
PIN_DIRECTION direction;
|
||||
hr = pin->QueryDirection (&direction);
|
||||
|
||||
if (SUCCEEDED (hr) && direction == PINDIR_INPUT)
|
||||
return true;
|
||||
}
|
||||
else if (hr == VFW_E_NOT_CONNECTED)
|
||||
{
|
||||
hr = S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext);
|
||||
};
|
||||
|
||||
juce_ImplementSingleton_SingleThreaded (DirectShowComponent::DirectShowContext::NativeWindowClass);
|
||||
|
||||
|
||||
//======================================================================
|
||||
class DirectShowComponent::DirectShowComponentWatcher : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
DirectShowComponentWatcher (DirectShowComponent* const owner_)
|
||||
: ComponentMovementWatcher (owner_),
|
||||
owner (owner_)
|
||||
{
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/)
|
||||
{
|
||||
if (owner->videoLoaded)
|
||||
owner->updateContextPosition();
|
||||
}
|
||||
|
||||
void componentPeerChanged()
|
||||
{
|
||||
if (owner->videoLoaded)
|
||||
owner->recreateNativeWindowAsync();
|
||||
}
|
||||
|
||||
void componentVisibilityChanged()
|
||||
{
|
||||
if (owner->videoLoaded)
|
||||
owner->showContext (owner->isShowing());
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
private:
|
||||
DirectShowComponent* const owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowComponentWatcher);
|
||||
};
|
||||
|
||||
|
||||
//======================================================================
|
||||
DirectShowComponent::DirectShowComponent (VideoRendererType type)
|
||||
: videoLoaded (false),
|
||||
looping (false),
|
||||
needToUpdateViewport (true),
|
||||
needToRecreateNativeWindow (false)
|
||||
{
|
||||
setOpaque (true);
|
||||
context = new DirectShowContext (*this, type);
|
||||
componentWatcher = new DirectShowComponentWatcher (this);
|
||||
}
|
||||
|
||||
DirectShowComponent::~DirectShowComponent()
|
||||
{
|
||||
componentWatcher = nullptr;
|
||||
}
|
||||
|
||||
bool DirectShowComponent::isDirectShowAvailable()
|
||||
{
|
||||
static bool isDSAvailable = DirectShowHelpers::checkDShowAvailability();
|
||||
return isDSAvailable;
|
||||
}
|
||||
|
||||
void DirectShowComponent::recreateNativeWindowAsync()
|
||||
{
|
||||
needToRecreateNativeWindow = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DirectShowComponent::updateContextPosition()
|
||||
{
|
||||
needToUpdateViewport = true;
|
||||
|
||||
if (getWidth() > 0 && getHeight() > 0)
|
||||
{
|
||||
Component* const topComp = getTopLevelComponent();
|
||||
|
||||
if (topComp->getPeer() != nullptr)
|
||||
context->updateWindowPosition (topComp->getLocalArea (this, getLocalBounds()));
|
||||
}
|
||||
}
|
||||
|
||||
void DirectShowComponent::showContext (const bool shouldBeVisible)
|
||||
{
|
||||
context->showWindow (shouldBeVisible);
|
||||
}
|
||||
|
||||
void DirectShowComponent::paint (Graphics& g)
|
||||
{
|
||||
if (videoLoaded)
|
||||
{
|
||||
if (needToRecreateNativeWindow)
|
||||
{
|
||||
context->peerChanged();
|
||||
needToRecreateNativeWindow = false;
|
||||
}
|
||||
|
||||
if (needToUpdateViewport)
|
||||
{
|
||||
context->updateVideoPosition();
|
||||
needToUpdateViewport = false;
|
||||
}
|
||||
|
||||
context->repaint();
|
||||
|
||||
ComponentPeer* const peer = getPeer();
|
||||
|
||||
if (peer != nullptr)
|
||||
{
|
||||
const Point<int> topLeft (getScreenPosition() - peer->getScreenPosition());
|
||||
peer->addMaskedRegion (topLeft.getX(), topLeft.getY(), getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g.fillAll (Colours::grey);
|
||||
}
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
bool DirectShowComponent::loadMovie (const String& fileOrURLPath)
|
||||
{
|
||||
closeMovie();
|
||||
|
||||
videoLoaded = context->loadFile (fileOrURLPath);
|
||||
|
||||
if (videoLoaded)
|
||||
{
|
||||
videoPath = fileOrURLPath;
|
||||
context->updateVideoPosition();
|
||||
}
|
||||
|
||||
return videoLoaded;
|
||||
}
|
||||
|
||||
bool DirectShowComponent::loadMovie (const File& videoFile)
|
||||
{
|
||||
return loadMovie (videoFile.getFullPathName());
|
||||
}
|
||||
|
||||
bool DirectShowComponent::loadMovie (const URL& videoURL)
|
||||
{
|
||||
return loadMovie (videoURL.toString (false));
|
||||
}
|
||||
|
||||
void DirectShowComponent::closeMovie()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->release();
|
||||
|
||||
videoLoaded = false;
|
||||
videoPath = String::empty;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
const File DirectShowComponent::getCurrentMoviePath() const { return videoPath; }
|
||||
bool DirectShowComponent::isMovieOpen() const { return videoLoaded; }
|
||||
double DirectShowComponent::getMovieDuration() const { return videoLoaded ? context->getDuration() : 0.0; }
|
||||
void DirectShowComponent::setLooping (const bool shouldLoop) { looping = shouldLoop; }
|
||||
bool DirectShowComponent::isLooping() const { return looping; }
|
||||
|
||||
void DirectShowComponent::getMovieNormalSize (int &width, int &height) const
|
||||
{
|
||||
width = context->getVideoWidth();
|
||||
height = context->getVideoHeight();
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void DirectShowComponent::setBoundsWithCorrectAspectRatio (const Rectangle<int>& spaceToFitWithin,
|
||||
const RectanglePlacement& placement)
|
||||
{
|
||||
int normalWidth, normalHeight;
|
||||
getMovieNormalSize (normalWidth, normalHeight);
|
||||
|
||||
const Rectangle<int> normalSize (0, 0, normalWidth, normalHeight);
|
||||
|
||||
if (! (spaceToFitWithin.isEmpty() || normalSize.isEmpty()))
|
||||
setBounds (placement.appliedTo (normalSize, spaceToFitWithin));
|
||||
else
|
||||
setBounds (spaceToFitWithin);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
void DirectShowComponent::play()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->run();
|
||||
}
|
||||
|
||||
void DirectShowComponent::stop()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->stop();
|
||||
}
|
||||
|
||||
bool DirectShowComponent::isPlaying() const
|
||||
{
|
||||
return context->isRunning();
|
||||
}
|
||||
|
||||
void DirectShowComponent::goToStart()
|
||||
{
|
||||
setPosition (0.0);
|
||||
}
|
||||
|
||||
void DirectShowComponent::setPosition (const double seconds)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setPosition (seconds);
|
||||
}
|
||||
|
||||
double DirectShowComponent::getPosition() const
|
||||
{
|
||||
return videoLoaded ? context->getPosition() : 0.0;
|
||||
}
|
||||
|
||||
void DirectShowComponent::setSpeed (const float newSpeed)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setSpeed (newSpeed);
|
||||
}
|
||||
|
||||
void DirectShowComponent::setMovieVolume (const float newVolume)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setVolume (newVolume);
|
||||
}
|
||||
|
||||
float DirectShowComponent::getMovieVolume() const
|
||||
{
|
||||
return videoLoaded ? context->getVolume() : 0.0f;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue