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.

961 lines
31KB

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