The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

960 lines
30KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace VideoRenderers
  19. {
  20. //==============================================================================
  21. struct Base
  22. {
  23. virtual ~Base() {}
  24. virtual HRESULT create (ComSmartPtr<IGraphBuilder>&, ComSmartPtr<IBaseFilter>&, HWND) = 0;
  25. virtual void setVideoWindow (HWND) = 0;
  26. virtual void setVideoPosition (HWND) = 0;
  27. virtual void repaintVideo (HWND, HDC) = 0;
  28. virtual void displayModeChanged() = 0;
  29. virtual HRESULT getVideoSize (long& videoWidth, long& videoHeight) = 0;
  30. };
  31. //==============================================================================
  32. struct VMR7 : public Base
  33. {
  34. VMR7() {}
  35. HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
  36. ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
  37. {
  38. ComSmartPtr<IVMRFilterConfig> filterConfig;
  39. HRESULT hr = baseFilter.CoCreateInstance (CLSID_VideoMixingRenderer);
  40. if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"VMR-7");
  41. if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (filterConfig);
  42. if (SUCCEEDED (hr)) hr = filterConfig->SetRenderingMode (VMRMode_Windowless);
  43. if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (windowlessControl);
  44. if (SUCCEEDED (hr)) hr = windowlessControl->SetVideoClippingWindow (hwnd);
  45. if (SUCCEEDED (hr)) hr = windowlessControl->SetAspectRatioMode (VMR_ARMODE_LETTER_BOX);
  46. return hr;
  47. }
  48. void setVideoWindow (HWND hwnd) override
  49. {
  50. windowlessControl->SetVideoClippingWindow (hwnd);
  51. }
  52. void setVideoPosition (HWND hwnd) override
  53. {
  54. long videoWidth = 0, videoHeight = 0;
  55. windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
  56. RECT src, dest;
  57. SetRect (&src, 0, 0, videoWidth, videoHeight);
  58. GetClientRect (hwnd, &dest);
  59. windowlessControl->SetVideoPosition (&src, &dest);
  60. }
  61. void repaintVideo (HWND hwnd, HDC hdc) override
  62. {
  63. windowlessControl->RepaintVideo (hwnd, hdc);
  64. }
  65. void displayModeChanged() override
  66. {
  67. windowlessControl->DisplayModeChanged();
  68. }
  69. HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
  70. {
  71. return windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
  72. }
  73. ComSmartPtr<IVMRWindowlessControl> windowlessControl;
  74. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7)
  75. };
  76. //==============================================================================
  77. struct EVR : public Base
  78. {
  79. EVR() {}
  80. HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
  81. ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
  82. {
  83. ComSmartPtr<IMFGetService> getService;
  84. HRESULT hr = baseFilter.CoCreateInstance (CLSID_EnhancedVideoRenderer);
  85. if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"EVR");
  86. if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (getService);
  87. if (SUCCEEDED (hr)) hr = getService->GetService (MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl,
  88. (void**) videoDisplayControl.resetAndGetPointerAddress());
  89. if (SUCCEEDED (hr)) hr = videoDisplayControl->SetVideoWindow (hwnd);
  90. if (SUCCEEDED (hr)) hr = videoDisplayControl->SetAspectRatioMode (MFVideoARMode_PreservePicture);
  91. return hr;
  92. }
  93. void setVideoWindow (HWND hwnd) override
  94. {
  95. videoDisplayControl->SetVideoWindow (hwnd);
  96. }
  97. void setVideoPosition (HWND hwnd) override
  98. {
  99. const MFVideoNormalizedRect src = { 0.0f, 0.0f, 1.0f, 1.0f };
  100. RECT dest;
  101. GetClientRect (hwnd, &dest);
  102. videoDisplayControl->SetVideoPosition (&src, &dest);
  103. }
  104. void repaintVideo (HWND, HDC) override
  105. {
  106. videoDisplayControl->RepaintVideo();
  107. }
  108. void displayModeChanged() override {}
  109. HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
  110. {
  111. SIZE sz = { 0, 0 };
  112. HRESULT hr = videoDisplayControl->GetNativeVideoSize (&sz, nullptr);
  113. videoWidth = sz.cx;
  114. videoHeight = sz.cy;
  115. return hr;
  116. }
  117. ComSmartPtr<IMFVideoDisplayControl> videoDisplayControl;
  118. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR)
  119. };
  120. }
  121. //==============================================================================
  122. struct VideoComponent::Pimpl : public Component,
  123. private ComponentPeer::ScaleFactorListener
  124. {
  125. Pimpl (VideoComponent& ownerToUse, bool)
  126. : owner (ownerToUse)
  127. {
  128. setOpaque (true);
  129. context.reset (new DirectShowContext (*this));
  130. componentWatcher.reset (new ComponentWatcher (*this));
  131. }
  132. ~Pimpl() override
  133. {
  134. close();
  135. context = nullptr;
  136. componentWatcher = nullptr;
  137. if (currentPeer != nullptr)
  138. currentPeer->removeScaleFactorListener (this);
  139. }
  140. Result loadFromString (const String& fileOrURLPath)
  141. {
  142. close();
  143. auto r = context->loadFile (fileOrURLPath);
  144. if (r.wasOk())
  145. {
  146. videoLoaded = true;
  147. context->updateVideoPosition();
  148. }
  149. return r;
  150. }
  151. Result load (const File& file)
  152. {
  153. auto r = loadFromString (file.getFullPathName());
  154. if (r.wasOk())
  155. currentFile = file;
  156. return r;
  157. }
  158. Result load (const URL& url)
  159. {
  160. auto r = loadFromString (URL::removeEscapeChars (url.toString (true)));
  161. if (r.wasOk())
  162. currentURL = url;
  163. return r;
  164. }
  165. void close()
  166. {
  167. stop();
  168. context->release();
  169. videoLoaded = false;
  170. currentFile = File();
  171. currentURL = {};
  172. }
  173. bool isOpen() const
  174. {
  175. return videoLoaded;
  176. }
  177. bool isPlaying() const
  178. {
  179. return context->state == DirectShowContext::runningState;
  180. }
  181. void play()
  182. {
  183. if (videoLoaded)
  184. context->play();
  185. }
  186. void stop()
  187. {
  188. if (videoLoaded)
  189. context->pause();
  190. }
  191. void setPosition (double newPosition)
  192. {
  193. if (videoLoaded)
  194. context->setPosition (newPosition);
  195. }
  196. double getPosition() const
  197. {
  198. return videoLoaded ? context->getPosition() : 0.0;
  199. }
  200. void setSpeed (double newSpeed)
  201. {
  202. if (videoLoaded)
  203. context->setSpeed (newSpeed);
  204. }
  205. double getSpeed() const
  206. {
  207. return videoLoaded ? context->getSpeed() : 0.0;
  208. }
  209. Rectangle<int> getNativeSize() const
  210. {
  211. return videoLoaded ? context->getVideoSize()
  212. : Rectangle<int>();
  213. }
  214. double getDuration() const
  215. {
  216. return videoLoaded ? context->getDuration() : 0.0;
  217. }
  218. void setVolume (float newVolume)
  219. {
  220. if (videoLoaded)
  221. context->setVolume (newVolume);
  222. }
  223. float getVolume() const
  224. {
  225. return videoLoaded ? context->getVolume() : 0.0f;
  226. }
  227. void paint (Graphics& g) override
  228. {
  229. if (videoLoaded)
  230. context->handleUpdateNowIfNeeded();
  231. else
  232. g.fillAll (Colours::grey);
  233. }
  234. void updateContextPosition()
  235. {
  236. context->updateContextPosition();
  237. if (getWidth() > 0 && getHeight() > 0)
  238. if (auto* peer = getTopLevelComponent()->getPeer())
  239. context->updateWindowPosition ((peer->getAreaCoveredBy (*this).toDouble()
  240. * peer->getPlatformScaleFactor()).toNearestInt());
  241. }
  242. void updateContextVisibility()
  243. {
  244. context->showWindow (isShowing());
  245. }
  246. void recreateNativeWindowAsync()
  247. {
  248. context->recreateNativeWindowAsync();
  249. repaint();
  250. }
  251. void playbackStarted()
  252. {
  253. if (owner.onPlaybackStarted != nullptr)
  254. owner.onPlaybackStarted();
  255. }
  256. void playbackStopped()
  257. {
  258. if (owner.onPlaybackStopped != nullptr)
  259. owner.onPlaybackStopped();
  260. }
  261. void errorOccurred (const String& errorMessage)
  262. {
  263. if (owner.onErrorOccurred != nullptr)
  264. owner.onErrorOccurred (errorMessage);
  265. }
  266. File currentFile;
  267. URL currentURL;
  268. private:
  269. VideoComponent& owner;
  270. ComponentPeer* currentPeer = nullptr;
  271. bool videoLoaded = false;
  272. //==============================================================================
  273. void nativeScaleFactorChanged (double /*newScaleFactor*/) override
  274. {
  275. if (videoLoaded)
  276. updateContextPosition();
  277. }
  278. //==============================================================================
  279. struct ComponentWatcher : public ComponentMovementWatcher
  280. {
  281. ComponentWatcher (Pimpl& c) : ComponentMovementWatcher (&c), owner (c)
  282. {
  283. }
  284. void componentMovedOrResized (bool, bool) override
  285. {
  286. if (owner.videoLoaded)
  287. owner.updateContextPosition();
  288. }
  289. void componentPeerChanged() override
  290. {
  291. if (owner.currentPeer != nullptr)
  292. owner.currentPeer->removeScaleFactorListener (&owner);
  293. if (owner.videoLoaded)
  294. owner.recreateNativeWindowAsync();
  295. }
  296. void componentVisibilityChanged() override
  297. {
  298. if (owner.videoLoaded)
  299. owner.updateContextVisibility();
  300. }
  301. Pimpl& owner;
  302. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentWatcher)
  303. };
  304. std::unique_ptr<ComponentWatcher> componentWatcher;
  305. //==============================================================================
  306. struct DirectShowContext : public AsyncUpdater
  307. {
  308. DirectShowContext (Pimpl& c) : component (c)
  309. {
  310. ignoreUnused (CoInitialize (nullptr));
  311. }
  312. ~DirectShowContext() override
  313. {
  314. release();
  315. CoUninitialize();
  316. }
  317. //==============================================================================
  318. void updateWindowPosition (const Rectangle<int>& newBounds)
  319. {
  320. nativeWindow->setWindowPosition (newBounds);
  321. }
  322. void showWindow (bool shouldBeVisible)
  323. {
  324. nativeWindow->showWindow (shouldBeVisible);
  325. }
  326. //==============================================================================
  327. void repaint()
  328. {
  329. if (hasVideo)
  330. videoRenderer->repaintVideo (nativeWindow->hwnd, nativeWindow->hdc);
  331. }
  332. void updateVideoPosition()
  333. {
  334. if (hasVideo)
  335. videoRenderer->setVideoPosition (nativeWindow->hwnd);
  336. }
  337. void displayResolutionChanged()
  338. {
  339. if (hasVideo)
  340. videoRenderer->displayModeChanged();
  341. }
  342. //==============================================================================
  343. void peerChanged()
  344. {
  345. deleteNativeWindow();
  346. mediaEvent->SetNotifyWindow (0, 0, 0);
  347. if (videoRenderer != nullptr)
  348. videoRenderer->setVideoWindow (nullptr);
  349. createNativeWindow();
  350. mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
  351. mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
  352. if (videoRenderer != nullptr)
  353. videoRenderer->setVideoWindow (hwnd);
  354. }
  355. void handleAsyncUpdate() override
  356. {
  357. if (hwnd != nullptr)
  358. {
  359. if (needToRecreateNativeWindow)
  360. {
  361. peerChanged();
  362. needToRecreateNativeWindow = false;
  363. }
  364. if (needToUpdateViewport)
  365. {
  366. updateVideoPosition();
  367. needToUpdateViewport = false;
  368. }
  369. repaint();
  370. }
  371. else
  372. {
  373. triggerAsyncUpdate();
  374. }
  375. }
  376. void recreateNativeWindowAsync()
  377. {
  378. needToRecreateNativeWindow = true;
  379. triggerAsyncUpdate();
  380. }
  381. void updateContextPosition()
  382. {
  383. needToUpdateViewport = true;
  384. triggerAsyncUpdate();
  385. }
  386. //==============================================================================
  387. Result loadFile (const String& fileOrURLPath)
  388. {
  389. jassert (state == uninitializedState);
  390. if (! createNativeWindow())
  391. return Result::fail ("Can't create window");
  392. HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
  393. // basic playback interfaces
  394. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaControl);
  395. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaPosition);
  396. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaEvent);
  397. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (basicAudio);
  398. // video renderer interface
  399. if (SUCCEEDED (hr))
  400. {
  401. if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
  402. {
  403. videoRenderer.reset (new VideoRenderers::EVR());
  404. hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
  405. if (FAILED (hr))
  406. videoRenderer = nullptr;
  407. }
  408. if (videoRenderer == nullptr)
  409. {
  410. videoRenderer.reset (new VideoRenderers::VMR7());
  411. hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
  412. }
  413. }
  414. // build filter graph
  415. if (SUCCEEDED (hr))
  416. {
  417. hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
  418. if (FAILED (hr))
  419. {
  420. #if JUCE_MODAL_LOOPS_PERMITTED
  421. // Annoyingly, if we don't run the msg loop between failing and deleting the window, the
  422. // whole OS message-dispatch system gets itself into a state, and refuses to deliver any
  423. // more messages for the whole app. (That's what happens in Win7, anyway)
  424. MessageManager::getInstance()->runDispatchLoopUntil (200);
  425. #endif
  426. }
  427. }
  428. // remove video renderer if not connected (no video)
  429. if (SUCCEEDED (hr))
  430. {
  431. if (isRendererConnected())
  432. {
  433. hasVideo = true;
  434. }
  435. else
  436. {
  437. hasVideo = false;
  438. graphBuilder->RemoveFilter (baseFilter);
  439. videoRenderer = nullptr;
  440. baseFilter = nullptr;
  441. }
  442. }
  443. // set window to receive events
  444. if (SUCCEEDED (hr))
  445. {
  446. mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
  447. hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
  448. }
  449. if (SUCCEEDED (hr))
  450. {
  451. state = stoppedState;
  452. pause();
  453. return Result::ok();
  454. }
  455. // Note that if you're trying to open a file and this method fails, you may
  456. // just need to install a suitable codec. It seems that by default DirectShow
  457. // doesn't support a very good range of formats.
  458. release();
  459. return getErrorMessageFromResult (hr);
  460. }
  461. static Result getErrorMessageFromResult (HRESULT hr)
  462. {
  463. switch (hr)
  464. {
  465. case VFW_E_INVALID_FILE_FORMAT: return Result::fail ("Invalid file format");
  466. case VFW_E_NOT_FOUND: return Result::fail ("File not found");
  467. case VFW_E_UNKNOWN_FILE_TYPE: return Result::fail ("Unknown file type");
  468. case VFW_E_UNSUPPORTED_STREAM: return Result::fail ("Unsupported stream");
  469. case VFW_E_CANNOT_CONNECT: return Result::fail ("Cannot connect");
  470. case VFW_E_CANNOT_LOAD_SOURCE_FILTER: return Result::fail ("Cannot load source filter");
  471. }
  472. TCHAR messageBuffer[512] = { 0 };
  473. FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  474. nullptr, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
  475. messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
  476. return Result::fail (String (messageBuffer));
  477. }
  478. void release()
  479. {
  480. if (mediaControl != nullptr)
  481. mediaControl->Stop();
  482. if (mediaEvent != nullptr)
  483. mediaEvent->SetNotifyWindow (0, 0, 0);
  484. if (videoRenderer != nullptr)
  485. videoRenderer->setVideoWindow (nullptr);
  486. hasVideo = false;
  487. videoRenderer = nullptr;
  488. baseFilter = nullptr;
  489. basicAudio = nullptr;
  490. mediaEvent = nullptr;
  491. mediaPosition = nullptr;
  492. mediaControl = nullptr;
  493. graphBuilder = nullptr;
  494. state = uninitializedState;
  495. if (nativeWindow != nullptr)
  496. deleteNativeWindow();
  497. }
  498. void graphEventProc()
  499. {
  500. LONG ec = 0;
  501. LONG_PTR p1 = {}, p2 = {};
  502. jassert (mediaEvent != nullptr);
  503. while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
  504. {
  505. mediaEvent->FreeEventParams (ec, p1, p2);
  506. switch (ec)
  507. {
  508. case EC_REPAINT:
  509. component.repaint();
  510. break;
  511. case EC_COMPLETE:
  512. component.stop();
  513. component.setPosition (0.0);
  514. break;
  515. case EC_ERRORABORT:
  516. case EC_ERRORABORTEX:
  517. component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
  518. // intentional fallthrough
  519. case EC_USERABORT:
  520. component.close();
  521. break;
  522. case EC_STATE_CHANGE:
  523. switch (p1)
  524. {
  525. case State_Paused: component.playbackStopped(); break;
  526. case State_Running: component.playbackStarted(); break;
  527. default: break;
  528. }
  529. default:
  530. break;
  531. }
  532. }
  533. }
  534. //==============================================================================
  535. void play()
  536. {
  537. mediaControl->Run();
  538. state = runningState;
  539. }
  540. void stop()
  541. {
  542. mediaControl->Stop();
  543. state = stoppedState;
  544. }
  545. void pause()
  546. {
  547. mediaControl->Pause();
  548. state = pausedState;
  549. }
  550. //==============================================================================
  551. Rectangle<int> getVideoSize() const noexcept
  552. {
  553. long width = 0, height = 0;
  554. if (hasVideo)
  555. videoRenderer->getVideoSize (width, height);
  556. return { (int) width, (int) height };
  557. }
  558. //==============================================================================
  559. double getDuration() const
  560. {
  561. REFTIME duration;
  562. mediaPosition->get_Duration (&duration);
  563. return duration;
  564. }
  565. double getSpeed() const
  566. {
  567. double speed;
  568. mediaPosition->get_Rate (&speed);
  569. return speed;
  570. }
  571. double getPosition() const
  572. {
  573. REFTIME seconds;
  574. mediaPosition->get_CurrentPosition (&seconds);
  575. return seconds;
  576. }
  577. void setSpeed (double newSpeed) { mediaPosition->put_Rate (newSpeed); }
  578. void setPosition (double seconds) { mediaPosition->put_CurrentPosition (seconds); }
  579. void setVolume (float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
  580. // in DirectShow, full volume is 0, silence is -10000
  581. static long convertToDShowVolume (float vol) noexcept
  582. {
  583. if (vol >= 1.0f) return 0;
  584. if (vol <= 0.0f) return -10000;
  585. return roundToInt ((vol * 10000.0f) - 10000.0f);
  586. }
  587. float getVolume() const
  588. {
  589. long volume;
  590. basicAudio->get_Volume (&volume);
  591. return (float) (volume + 10000) / 10000.0f;
  592. }
  593. enum State { uninitializedState, runningState, pausedState, stoppedState };
  594. State state = uninitializedState;
  595. private:
  596. //==============================================================================
  597. enum { graphEventID = WM_APP + 0x43f0 };
  598. Pimpl& component;
  599. HWND hwnd = {};
  600. HDC hdc = {};
  601. ComSmartPtr<IGraphBuilder> graphBuilder;
  602. ComSmartPtr<IMediaControl> mediaControl;
  603. ComSmartPtr<IMediaPosition> mediaPosition;
  604. ComSmartPtr<IMediaEventEx> mediaEvent;
  605. ComSmartPtr<IBasicAudio> basicAudio;
  606. ComSmartPtr<IBaseFilter> baseFilter;
  607. std::unique_ptr<VideoRenderers::Base> videoRenderer;
  608. bool hasVideo = false, needToUpdateViewport = true, needToRecreateNativeWindow = false;
  609. //==============================================================================
  610. bool createNativeWindow()
  611. {
  612. jassert (nativeWindow == nullptr);
  613. if (auto* topLevelPeer = component.getTopLevelComponent()->getPeer())
  614. {
  615. nativeWindow.reset (new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this));
  616. hwnd = nativeWindow->hwnd;
  617. component.currentPeer = topLevelPeer;
  618. component.currentPeer->addScaleFactorListener (&component);
  619. if (hwnd != nullptr)
  620. {
  621. hdc = GetDC (hwnd);
  622. component.updateContextPosition();
  623. component.updateContextVisibility();
  624. return true;
  625. }
  626. nativeWindow = nullptr;
  627. }
  628. else
  629. {
  630. jassertfalse;
  631. }
  632. return false;
  633. }
  634. void deleteNativeWindow()
  635. {
  636. jassert (nativeWindow != nullptr);
  637. ReleaseDC (hwnd, hdc);
  638. hwnd = {};
  639. hdc = {};
  640. nativeWindow = nullptr;
  641. }
  642. bool isRendererConnected()
  643. {
  644. ComSmartPtr<IEnumPins> enumPins;
  645. HRESULT hr = baseFilter->EnumPins (enumPins.resetAndGetPointerAddress());
  646. if (SUCCEEDED (hr))
  647. hr = enumPins->Reset();
  648. ComSmartPtr<IPin> pin;
  649. while (SUCCEEDED (hr)
  650. && enumPins->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
  651. {
  652. ComSmartPtr<IPin> otherPin;
  653. hr = pin->ConnectedTo (otherPin.resetAndGetPointerAddress());
  654. if (SUCCEEDED (hr))
  655. {
  656. PIN_DIRECTION direction;
  657. hr = pin->QueryDirection (&direction);
  658. if (SUCCEEDED (hr) && direction == PINDIR_INPUT)
  659. return true;
  660. }
  661. else if (hr == VFW_E_NOT_CONNECTED)
  662. {
  663. hr = S_OK;
  664. }
  665. }
  666. return false;
  667. }
  668. //==============================================================================
  669. struct NativeWindowClass : private DeletedAtShutdown
  670. {
  671. bool isRegistered() const noexcept { return atom != 0; }
  672. LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) (pointer_sized_uint) MAKELONG (atom, 0); }
  673. JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (NativeWindowClass)
  674. private:
  675. NativeWindowClass()
  676. {
  677. String windowClassName ("JUCE_DIRECTSHOW_");
  678. windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);
  679. HINSTANCE moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
  680. TCHAR moduleFile [1024] = {};
  681. GetModuleFileName (moduleHandle, moduleFile, 1024);
  682. WNDCLASSEX wcex = {};
  683. wcex.cbSize = sizeof (wcex);
  684. wcex.style = CS_OWNDC;
  685. wcex.lpfnWndProc = (WNDPROC) wndProc;
  686. wcex.lpszClassName = windowClassName.toWideCharPointer();
  687. wcex.hInstance = moduleHandle;
  688. atom = RegisterClassEx (&wcex);
  689. jassert (atom != 0);
  690. }
  691. ~NativeWindowClass()
  692. {
  693. if (atom != 0)
  694. UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
  695. clearSingletonInstance();
  696. }
  697. static LRESULT CALLBACK wndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  698. {
  699. if (auto* c = (DirectShowContext*) GetWindowLongPtr (hwnd, GWLP_USERDATA))
  700. {
  701. switch (msg)
  702. {
  703. case WM_NCHITTEST: return HTTRANSPARENT;
  704. case WM_ERASEBKGND: return 1;
  705. case WM_DISPLAYCHANGE: c->displayResolutionChanged(); break;
  706. case graphEventID: c->graphEventProc(); return 0;
  707. default: break;
  708. }
  709. }
  710. return DefWindowProc (hwnd, msg, wParam, lParam);
  711. }
  712. ATOM atom = {};
  713. JUCE_DECLARE_NON_COPYABLE (NativeWindowClass)
  714. };
  715. //==============================================================================
  716. struct NativeWindow
  717. {
  718. NativeWindow (HWND parentToAddTo, void* userData)
  719. {
  720. auto* wc = NativeWindowClass::getInstance();
  721. if (wc->isRegistered())
  722. {
  723. DWORD exstyle = 0;
  724. DWORD type = WS_CHILD;
  725. hwnd = CreateWindowEx (exstyle, wc->getWindowClassName(),
  726. L"", type, 0, 0, 0, 0, parentToAddTo, nullptr,
  727. (HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr);
  728. if (hwnd != nullptr)
  729. {
  730. hdc = GetDC (hwnd);
  731. SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) userData);
  732. }
  733. }
  734. jassert (hwnd != nullptr);
  735. }
  736. ~NativeWindow()
  737. {
  738. if (hwnd != nullptr)
  739. {
  740. SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) 0);
  741. DestroyWindow (hwnd);
  742. }
  743. }
  744. void setWindowPosition (Rectangle<int> newBounds)
  745. {
  746. SetWindowPos (hwnd, nullptr, newBounds.getX(), newBounds.getY(),
  747. newBounds.getWidth(), newBounds.getHeight(),
  748. SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
  749. }
  750. void showWindow (bool shouldBeVisible)
  751. {
  752. ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
  753. }
  754. HWND hwnd = {};
  755. HDC hdc = {};
  756. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow)
  757. };
  758. std::unique_ptr<NativeWindow> nativeWindow;
  759. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext)
  760. };
  761. std::unique_ptr<DirectShowContext> context;
  762. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  763. };
  764. JUCE_IMPLEMENT_SINGLETON (VideoComponent::Pimpl::DirectShowContext::NativeWindowClass)