git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
959
deps/juce/modules/juce_video/native/juce_win32_Video.h
vendored
Normal file
959
deps/juce/modules/juce_video/native/juce_win32_Video.h
vendored
Normal file
@ -0,0 +1,959 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace VideoRenderers
|
||||
{
|
||||
//==============================================================================
|
||||
struct Base
|
||||
{
|
||||
virtual ~Base() {}
|
||||
|
||||
virtual HRESULT create (ComSmartPtr<IGraphBuilder>&, ComSmartPtr<IBaseFilter>&, HWND) = 0;
|
||||
virtual void setVideoWindow (HWND) = 0;
|
||||
virtual void setVideoPosition (HWND) = 0;
|
||||
virtual void repaintVideo (HWND, HDC) = 0;
|
||||
virtual void displayModeChanged() = 0;
|
||||
virtual HRESULT getVideoSize (long& videoWidth, long& videoHeight) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct VMR7 : public Base
|
||||
{
|
||||
VMR7() {}
|
||||
|
||||
HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
|
||||
{
|
||||
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 (filterConfig);
|
||||
if (SUCCEEDED (hr)) hr = filterConfig->SetRenderingMode (VMRMode_Windowless);
|
||||
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (windowlessControl);
|
||||
if (SUCCEEDED (hr)) hr = windowlessControl->SetVideoClippingWindow (hwnd);
|
||||
if (SUCCEEDED (hr)) hr = windowlessControl->SetAspectRatioMode (VMR_ARMODE_LETTER_BOX);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void setVideoWindow (HWND hwnd) override
|
||||
{
|
||||
windowlessControl->SetVideoClippingWindow (hwnd);
|
||||
}
|
||||
|
||||
void setVideoPosition (HWND hwnd) override
|
||||
{
|
||||
long videoWidth = 0, videoHeight = 0;
|
||||
windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
|
||||
|
||||
RECT src, dest;
|
||||
SetRect (&src, 0, 0, videoWidth, videoHeight);
|
||||
GetClientRect (hwnd, &dest);
|
||||
|
||||
windowlessControl->SetVideoPosition (&src, &dest);
|
||||
}
|
||||
|
||||
void repaintVideo (HWND hwnd, HDC hdc) override
|
||||
{
|
||||
windowlessControl->RepaintVideo (hwnd, hdc);
|
||||
}
|
||||
|
||||
void displayModeChanged() override
|
||||
{
|
||||
windowlessControl->DisplayModeChanged();
|
||||
}
|
||||
|
||||
HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
|
||||
{
|
||||
return windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
|
||||
}
|
||||
|
||||
ComSmartPtr<IVMRWindowlessControl> windowlessControl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
struct EVR : public Base
|
||||
{
|
||||
EVR() {}
|
||||
|
||||
HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
|
||||
ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
|
||||
{
|
||||
ComSmartPtr<IMFGetService> getService;
|
||||
|
||||
HRESULT hr = baseFilter.CoCreateInstance (CLSID_EnhancedVideoRenderer);
|
||||
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"EVR");
|
||||
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (getService);
|
||||
if (SUCCEEDED (hr)) hr = getService->GetService (MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl,
|
||||
(void**) videoDisplayControl.resetAndGetPointerAddress());
|
||||
if (SUCCEEDED (hr)) hr = videoDisplayControl->SetVideoWindow (hwnd);
|
||||
if (SUCCEEDED (hr)) hr = videoDisplayControl->SetAspectRatioMode (MFVideoARMode_PreservePicture);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void setVideoWindow (HWND hwnd) override
|
||||
{
|
||||
videoDisplayControl->SetVideoWindow (hwnd);
|
||||
}
|
||||
|
||||
void setVideoPosition (HWND hwnd) override
|
||||
{
|
||||
const MFVideoNormalizedRect src = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
|
||||
RECT dest;
|
||||
GetClientRect (hwnd, &dest);
|
||||
|
||||
videoDisplayControl->SetVideoPosition (&src, &dest);
|
||||
}
|
||||
|
||||
void repaintVideo (HWND, HDC) override
|
||||
{
|
||||
videoDisplayControl->RepaintVideo();
|
||||
}
|
||||
|
||||
void displayModeChanged() override {}
|
||||
|
||||
HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
|
||||
{
|
||||
SIZE sz = { 0, 0 };
|
||||
HRESULT hr = videoDisplayControl->GetNativeVideoSize (&sz, nullptr);
|
||||
videoWidth = sz.cx;
|
||||
videoHeight = sz.cy;
|
||||
return hr;
|
||||
}
|
||||
|
||||
ComSmartPtr<IMFVideoDisplayControl> videoDisplayControl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct VideoComponent::Pimpl : public Component,
|
||||
private ComponentPeer::ScaleFactorListener
|
||||
{
|
||||
Pimpl (VideoComponent& ownerToUse, bool)
|
||||
: owner (ownerToUse)
|
||||
{
|
||||
setOpaque (true);
|
||||
context.reset (new DirectShowContext (*this));
|
||||
componentWatcher.reset (new ComponentWatcher (*this));
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
close();
|
||||
context = nullptr;
|
||||
componentWatcher = nullptr;
|
||||
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->removeScaleFactorListener (this);
|
||||
}
|
||||
|
||||
Result loadFromString (const String& fileOrURLPath)
|
||||
{
|
||||
close();
|
||||
auto r = context->loadFile (fileOrURLPath);
|
||||
|
||||
if (r.wasOk())
|
||||
{
|
||||
videoLoaded = true;
|
||||
context->updateVideoPosition();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Result load (const File& file)
|
||||
{
|
||||
auto r = loadFromString (file.getFullPathName());
|
||||
|
||||
if (r.wasOk())
|
||||
currentFile = file;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Result load (const URL& url)
|
||||
{
|
||||
auto r = loadFromString (URL::removeEscapeChars (url.toString (true)));
|
||||
|
||||
if (r.wasOk())
|
||||
currentURL = url;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
stop();
|
||||
context->release();
|
||||
|
||||
videoLoaded = false;
|
||||
currentFile = File();
|
||||
currentURL = {};
|
||||
}
|
||||
|
||||
bool isOpen() const
|
||||
{
|
||||
return videoLoaded;
|
||||
}
|
||||
|
||||
bool isPlaying() const
|
||||
{
|
||||
return context->state == DirectShowContext::runningState;
|
||||
}
|
||||
|
||||
void play()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->play();
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->pause();
|
||||
}
|
||||
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setPosition (newPosition);
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
return videoLoaded ? context->getPosition() : 0.0;
|
||||
}
|
||||
|
||||
void setSpeed (double newSpeed)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setSpeed (newSpeed);
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
return videoLoaded ? context->getSpeed() : 0.0;
|
||||
}
|
||||
|
||||
Rectangle<int> getNativeSize() const
|
||||
{
|
||||
return videoLoaded ? context->getVideoSize()
|
||||
: Rectangle<int>();
|
||||
}
|
||||
|
||||
double getDuration() const
|
||||
{
|
||||
return videoLoaded ? context->getDuration() : 0.0;
|
||||
}
|
||||
|
||||
void setVolume (float newVolume)
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->setVolume (newVolume);
|
||||
}
|
||||
|
||||
float getVolume() const
|
||||
{
|
||||
return videoLoaded ? context->getVolume() : 0.0f;
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (videoLoaded)
|
||||
context->handleUpdateNowIfNeeded();
|
||||
else
|
||||
g.fillAll (Colours::grey);
|
||||
}
|
||||
|
||||
void updateContextPosition()
|
||||
{
|
||||
context->updateContextPosition();
|
||||
|
||||
if (getWidth() > 0 && getHeight() > 0)
|
||||
if (auto* peer = getTopLevelComponent()->getPeer())
|
||||
context->updateWindowPosition ((peer->getAreaCoveredBy (*this).toDouble()
|
||||
* peer->getPlatformScaleFactor()).toNearestInt());
|
||||
}
|
||||
|
||||
void updateContextVisibility()
|
||||
{
|
||||
context->showWindow (isShowing());
|
||||
}
|
||||
|
||||
void recreateNativeWindowAsync()
|
||||
{
|
||||
context->recreateNativeWindowAsync();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void playbackStarted()
|
||||
{
|
||||
if (owner.onPlaybackStarted != nullptr)
|
||||
owner.onPlaybackStarted();
|
||||
}
|
||||
|
||||
void playbackStopped()
|
||||
{
|
||||
if (owner.onPlaybackStopped != nullptr)
|
||||
owner.onPlaybackStopped();
|
||||
}
|
||||
|
||||
void errorOccurred (const String& errorMessage)
|
||||
{
|
||||
if (owner.onErrorOccurred != nullptr)
|
||||
owner.onErrorOccurred (errorMessage);
|
||||
}
|
||||
|
||||
File currentFile;
|
||||
URL currentURL;
|
||||
|
||||
private:
|
||||
VideoComponent& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
bool videoLoaded = false;
|
||||
|
||||
//==============================================================================
|
||||
void nativeScaleFactorChanged (double /*newScaleFactor*/) override
|
||||
{
|
||||
if (videoLoaded)
|
||||
updateContextPosition();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ComponentWatcher : public ComponentMovementWatcher
|
||||
{
|
||||
ComponentWatcher (Pimpl& c) : ComponentMovementWatcher (&c), owner (c)
|
||||
{
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool, bool) override
|
||||
{
|
||||
if (owner.videoLoaded)
|
||||
owner.updateContextPosition();
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
if (owner.currentPeer != nullptr)
|
||||
owner.currentPeer->removeScaleFactorListener (&owner);
|
||||
|
||||
if (owner.videoLoaded)
|
||||
owner.recreateNativeWindowAsync();
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
if (owner.videoLoaded)
|
||||
owner.updateContextVisibility();
|
||||
}
|
||||
|
||||
Pimpl& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentWatcher)
|
||||
};
|
||||
|
||||
std::unique_ptr<ComponentWatcher> componentWatcher;
|
||||
|
||||
//==============================================================================
|
||||
struct DirectShowContext : public AsyncUpdater
|
||||
{
|
||||
DirectShowContext (Pimpl& c) : component (c)
|
||||
{
|
||||
ignoreUnused (CoInitialize (nullptr));
|
||||
}
|
||||
|
||||
~DirectShowContext() override
|
||||
{
|
||||
release();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void updateWindowPosition (const Rectangle<int>& newBounds)
|
||||
{
|
||||
nativeWindow->setWindowPosition (newBounds);
|
||||
}
|
||||
|
||||
void showWindow (bool shouldBeVisible)
|
||||
{
|
||||
nativeWindow->showWindow (shouldBeVisible);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void repaint()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->repaintVideo (nativeWindow->hwnd, nativeWindow->hdc);
|
||||
}
|
||||
|
||||
void updateVideoPosition()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->setVideoPosition (nativeWindow->hwnd);
|
||||
}
|
||||
|
||||
void displayResolutionChanged()
|
||||
{
|
||||
if (hasVideo)
|
||||
videoRenderer->displayModeChanged();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void peerChanged()
|
||||
{
|
||||
deleteNativeWindow();
|
||||
|
||||
mediaEvent->SetNotifyWindow (0, 0, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (nullptr);
|
||||
|
||||
createNativeWindow();
|
||||
|
||||
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
|
||||
mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (hwnd);
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
if (needToRecreateNativeWindow)
|
||||
{
|
||||
peerChanged();
|
||||
needToRecreateNativeWindow = false;
|
||||
}
|
||||
|
||||
if (needToUpdateViewport)
|
||||
{
|
||||
updateVideoPosition();
|
||||
needToUpdateViewport = false;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
else
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void recreateNativeWindowAsync()
|
||||
{
|
||||
needToRecreateNativeWindow = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void updateContextPosition()
|
||||
{
|
||||
needToUpdateViewport = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Result loadFile (const String& fileOrURLPath)
|
||||
{
|
||||
jassert (state == uninitializedState);
|
||||
|
||||
if (! createNativeWindow())
|
||||
return Result::fail ("Can't create window");
|
||||
|
||||
HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
|
||||
|
||||
// basic playback interfaces
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaControl);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaPosition);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaEvent);
|
||||
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (basicAudio);
|
||||
|
||||
// video renderer interface
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
|
||||
{
|
||||
videoRenderer.reset (new VideoRenderers::EVR());
|
||||
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
|
||||
|
||||
if (FAILED (hr))
|
||||
videoRenderer = nullptr;
|
||||
}
|
||||
|
||||
if (videoRenderer == nullptr)
|
||||
{
|
||||
videoRenderer.reset (new VideoRenderers::VMR7());
|
||||
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// build filter graph
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
|
||||
|
||||
if (FAILED (hr))
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
// Annoyingly, if we don't run the msg loop between failing and deleting the window, the
|
||||
// whole OS message-dispatch system gets itself into a state, and refuses to deliver any
|
||||
// more messages for the whole app. (That's what happens in Win7, anyway)
|
||||
MessageManager::getInstance()->runDispatchLoopUntil (200);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// remove video renderer if not connected (no video)
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
if (isRendererConnected())
|
||||
{
|
||||
hasVideo = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasVideo = false;
|
||||
graphBuilder->RemoveFilter (baseFilter);
|
||||
videoRenderer = nullptr;
|
||||
baseFilter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// set window to receive events
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
|
||||
hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
|
||||
}
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
{
|
||||
state = stoppedState;
|
||||
pause();
|
||||
return Result::ok();
|
||||
}
|
||||
|
||||
// Note that if you're trying to open a file and this method fails, you may
|
||||
// just need to install a suitable codec. It seems that by default DirectShow
|
||||
// doesn't support a very good range of formats.
|
||||
release();
|
||||
return getErrorMessageFromResult (hr);
|
||||
}
|
||||
|
||||
static Result getErrorMessageFromResult (HRESULT hr)
|
||||
{
|
||||
switch (hr)
|
||||
{
|
||||
case VFW_E_INVALID_FILE_FORMAT: return Result::fail ("Invalid file format");
|
||||
case VFW_E_NOT_FOUND: return Result::fail ("File not found");
|
||||
case VFW_E_UNKNOWN_FILE_TYPE: return Result::fail ("Unknown file type");
|
||||
case VFW_E_UNSUPPORTED_STREAM: return Result::fail ("Unsupported stream");
|
||||
case VFW_E_CANNOT_CONNECT: return Result::fail ("Cannot connect");
|
||||
case VFW_E_CANNOT_LOAD_SOURCE_FILTER: return Result::fail ("Cannot load source filter");
|
||||
}
|
||||
|
||||
TCHAR messageBuffer[512] = { 0 };
|
||||
|
||||
FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
|
||||
|
||||
return Result::fail (String (messageBuffer));
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (mediaControl != nullptr)
|
||||
mediaControl->Stop();
|
||||
|
||||
if (mediaEvent != nullptr)
|
||||
mediaEvent->SetNotifyWindow (0, 0, 0);
|
||||
|
||||
if (videoRenderer != nullptr)
|
||||
videoRenderer->setVideoWindow (nullptr);
|
||||
|
||||
hasVideo = false;
|
||||
videoRenderer = nullptr;
|
||||
baseFilter = nullptr;
|
||||
basicAudio = nullptr;
|
||||
mediaEvent = nullptr;
|
||||
mediaPosition = nullptr;
|
||||
mediaControl = nullptr;
|
||||
graphBuilder = nullptr;
|
||||
|
||||
state = uninitializedState;
|
||||
|
||||
if (nativeWindow != nullptr)
|
||||
deleteNativeWindow();
|
||||
}
|
||||
|
||||
void graphEventProc()
|
||||
{
|
||||
LONG ec = 0;
|
||||
LONG_PTR p1 = {}, p2 = {};
|
||||
|
||||
jassert (mediaEvent != nullptr);
|
||||
|
||||
while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
|
||||
{
|
||||
mediaEvent->FreeEventParams (ec, p1, p2);
|
||||
|
||||
switch (ec)
|
||||
{
|
||||
case EC_REPAINT:
|
||||
component.repaint();
|
||||
break;
|
||||
|
||||
case EC_COMPLETE:
|
||||
component.stop();
|
||||
component.setPosition (0.0);
|
||||
break;
|
||||
|
||||
case EC_ERRORABORT:
|
||||
case EC_ERRORABORTEX:
|
||||
component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
|
||||
// intentional fallthrough
|
||||
case EC_USERABORT:
|
||||
component.close();
|
||||
break;
|
||||
|
||||
case EC_STATE_CHANGE:
|
||||
switch (p1)
|
||||
{
|
||||
case State_Paused: component.playbackStopped(); break;
|
||||
case State_Running: component.playbackStarted(); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void play()
|
||||
{
|
||||
mediaControl->Run();
|
||||
state = runningState;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
mediaControl->Stop();
|
||||
state = stoppedState;
|
||||
}
|
||||
|
||||
void pause()
|
||||
{
|
||||
mediaControl->Pause();
|
||||
state = pausedState;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Rectangle<int> getVideoSize() const noexcept
|
||||
{
|
||||
long width = 0, height = 0;
|
||||
|
||||
if (hasVideo)
|
||||
videoRenderer->getVideoSize (width, height);
|
||||
|
||||
return { (int) width, (int) height };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
double getDuration() const
|
||||
{
|
||||
REFTIME duration;
|
||||
mediaPosition->get_Duration (&duration);
|
||||
return duration;
|
||||
}
|
||||
|
||||
double getSpeed() const
|
||||
{
|
||||
double speed;
|
||||
mediaPosition->get_Rate (&speed);
|
||||
return speed;
|
||||
}
|
||||
|
||||
double getPosition() const
|
||||
{
|
||||
REFTIME seconds;
|
||||
mediaPosition->get_CurrentPosition (&seconds);
|
||||
return seconds;
|
||||
}
|
||||
|
||||
void setSpeed (double newSpeed) { mediaPosition->put_Rate (newSpeed); }
|
||||
void setPosition (double seconds) { mediaPosition->put_CurrentPosition (seconds); }
|
||||
void setVolume (float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
|
||||
|
||||
// in DirectShow, full volume is 0, silence is -10000
|
||||
static long convertToDShowVolume (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 (float) (volume + 10000) / 10000.0f;
|
||||
}
|
||||
|
||||
enum State { uninitializedState, runningState, pausedState, stoppedState };
|
||||
State state = uninitializedState;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
enum { graphEventID = WM_APP + 0x43f0 };
|
||||
|
||||
Pimpl& component;
|
||||
HWND hwnd = {};
|
||||
HDC hdc = {};
|
||||
|
||||
ComSmartPtr<IGraphBuilder> graphBuilder;
|
||||
ComSmartPtr<IMediaControl> mediaControl;
|
||||
ComSmartPtr<IMediaPosition> mediaPosition;
|
||||
ComSmartPtr<IMediaEventEx> mediaEvent;
|
||||
ComSmartPtr<IBasicAudio> basicAudio;
|
||||
ComSmartPtr<IBaseFilter> baseFilter;
|
||||
|
||||
std::unique_ptr<VideoRenderers::Base> videoRenderer;
|
||||
|
||||
bool hasVideo = false, needToUpdateViewport = true, needToRecreateNativeWindow = false;
|
||||
|
||||
//==============================================================================
|
||||
bool createNativeWindow()
|
||||
{
|
||||
jassert (nativeWindow == nullptr);
|
||||
|
||||
if (auto* topLevelPeer = component.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
nativeWindow.reset (new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this));
|
||||
|
||||
hwnd = nativeWindow->hwnd;
|
||||
component.currentPeer = topLevelPeer;
|
||||
component.currentPeer->addScaleFactorListener (&component);
|
||||
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
hdc = GetDC (hwnd);
|
||||
component.updateContextPosition();
|
||||
component.updateContextVisibility();
|
||||
return true;
|
||||
}
|
||||
|
||||
nativeWindow = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void deleteNativeWindow()
|
||||
{
|
||||
jassert (nativeWindow != nullptr);
|
||||
ReleaseDC (hwnd, hdc);
|
||||
hwnd = {};
|
||||
hdc = {};
|
||||
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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct NativeWindowClass : private DeletedAtShutdown
|
||||
{
|
||||
bool isRegistered() const noexcept { return atom != 0; }
|
||||
LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) (pointer_sized_uint) MAKELONG (atom, 0); }
|
||||
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (NativeWindowClass)
|
||||
|
||||
private:
|
||||
NativeWindowClass()
|
||||
{
|
||||
String windowClassName ("JUCE_DIRECTSHOW_");
|
||||
windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);
|
||||
|
||||
HINSTANCE moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
|
||||
|
||||
TCHAR moduleFile [1024] = {};
|
||||
GetModuleFileName (moduleHandle, moduleFile, 1024);
|
||||
|
||||
WNDCLASSEX wcex = {};
|
||||
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) Process::getCurrentModuleInstanceHandle());
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (auto* c = (DirectShowContext*) GetWindowLongPtr (hwnd, GWLP_USERDATA))
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case WM_NCHITTEST: return HTTRANSPARENT;
|
||||
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);
|
||||
}
|
||||
|
||||
ATOM atom = {};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NativeWindowClass)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct NativeWindow
|
||||
{
|
||||
NativeWindow (HWND parentToAddTo, void* userData)
|
||||
{
|
||||
auto* 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, nullptr,
|
||||
(HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr);
|
||||
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
hdc = GetDC (hwnd);
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) userData);
|
||||
}
|
||||
}
|
||||
|
||||
jassert (hwnd != nullptr);
|
||||
}
|
||||
|
||||
~NativeWindow()
|
||||
{
|
||||
if (hwnd != nullptr)
|
||||
{
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) 0);
|
||||
DestroyWindow (hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
void setWindowPosition (Rectangle<int> newBounds)
|
||||
{
|
||||
SetWindowPos (hwnd, nullptr, newBounds.getX(), newBounds.getY(),
|
||||
newBounds.getWidth(), newBounds.getHeight(),
|
||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||||
}
|
||||
|
||||
void showWindow (bool shouldBeVisible)
|
||||
{
|
||||
ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
||||
}
|
||||
|
||||
HWND hwnd = {};
|
||||
HDC hdc = {};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow)
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeWindow> nativeWindow;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext)
|
||||
};
|
||||
|
||||
std::unique_ptr<DirectShowContext> context;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (VideoComponent::Pimpl::DirectShowContext::NativeWindowClass)
|
Reference in New Issue
Block a user