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.

964 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. if (owner.onPlaybackStarted != nullptr)
  256. owner.onPlaybackStarted();
  257. }
  258. void playbackStopped()
  259. {
  260. if (owner.onPlaybackStopped != nullptr)
  261. owner.onPlaybackStopped();
  262. }
  263. void errorOccurred (const String& errorMessage)
  264. {
  265. if (owner.onErrorOccurred != nullptr)
  266. owner.onErrorOccurred (errorMessage);
  267. }
  268. File currentFile;
  269. URL currentURL;
  270. private:
  271. VideoComponent& owner;
  272. ComponentPeer* currentPeer = nullptr;
  273. bool videoLoaded = false;
  274. //==============================================================================
  275. void nativeScaleFactorChanged (double /*newScaleFactor*/) override
  276. {
  277. if (videoLoaded)
  278. updateContextPosition();
  279. }
  280. //==============================================================================
  281. struct ComponentWatcher : public ComponentMovementWatcher
  282. {
  283. ComponentWatcher (Pimpl& c) : ComponentMovementWatcher (&c), owner (c)
  284. {
  285. }
  286. using ComponentMovementWatcher::componentMovedOrResized;
  287. void componentMovedOrResized (bool, bool) override
  288. {
  289. if (owner.videoLoaded)
  290. owner.updateContextPosition();
  291. }
  292. void componentPeerChanged() override
  293. {
  294. if (owner.currentPeer != nullptr)
  295. owner.currentPeer->removeScaleFactorListener (&owner);
  296. if (owner.videoLoaded)
  297. owner.recreateNativeWindowAsync();
  298. }
  299. using ComponentMovementWatcher::componentVisibilityChanged;
  300. void componentVisibilityChanged() override
  301. {
  302. if (owner.videoLoaded)
  303. owner.updateContextVisibility();
  304. }
  305. Pimpl& owner;
  306. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentWatcher)
  307. };
  308. std::unique_ptr<ComponentWatcher> componentWatcher;
  309. //==============================================================================
  310. struct DirectShowContext : public AsyncUpdater
  311. {
  312. DirectShowContext (Pimpl& c) : component (c)
  313. {
  314. [[maybe_unused]] const auto result = CoInitialize (nullptr);
  315. }
  316. ~DirectShowContext() override
  317. {
  318. release();
  319. CoUninitialize();
  320. }
  321. //==============================================================================
  322. void updateWindowPosition (const Rectangle<int>& newBounds)
  323. {
  324. nativeWindow->setWindowPosition (newBounds);
  325. }
  326. void showWindow (bool shouldBeVisible)
  327. {
  328. nativeWindow->showWindow (shouldBeVisible);
  329. }
  330. //==============================================================================
  331. void repaint()
  332. {
  333. if (hasVideo)
  334. videoRenderer->repaintVideo (nativeWindow->hwnd, nativeWindow->hdc);
  335. }
  336. void updateVideoPosition()
  337. {
  338. if (hasVideo)
  339. videoRenderer->setVideoPosition (nativeWindow->hwnd);
  340. }
  341. void displayResolutionChanged()
  342. {
  343. if (hasVideo)
  344. videoRenderer->displayModeChanged();
  345. }
  346. //==============================================================================
  347. void peerChanged()
  348. {
  349. deleteNativeWindow();
  350. mediaEvent->SetNotifyWindow (0, 0, 0);
  351. if (videoRenderer != nullptr)
  352. videoRenderer->setVideoWindow (nullptr);
  353. createNativeWindow();
  354. mediaEvent->CancelDefaultHandling (ComTypes::EC_STATE_CHANGE);
  355. mediaEvent->SetNotifyWindow ((ComTypes::OAHWND) hwnd, graphEventID, 0);
  356. if (videoRenderer != nullptr)
  357. videoRenderer->setVideoWindow (hwnd);
  358. }
  359. void handleAsyncUpdate() override
  360. {
  361. if (hwnd != nullptr)
  362. {
  363. if (needToRecreateNativeWindow)
  364. {
  365. peerChanged();
  366. needToRecreateNativeWindow = false;
  367. }
  368. if (needToUpdateViewport)
  369. {
  370. updateVideoPosition();
  371. needToUpdateViewport = false;
  372. }
  373. repaint();
  374. }
  375. else
  376. {
  377. triggerAsyncUpdate();
  378. }
  379. }
  380. void recreateNativeWindowAsync()
  381. {
  382. needToRecreateNativeWindow = true;
  383. triggerAsyncUpdate();
  384. }
  385. void updateContextPosition()
  386. {
  387. needToUpdateViewport = true;
  388. triggerAsyncUpdate();
  389. }
  390. //==============================================================================
  391. Result loadFile (const String& fileOrURLPath)
  392. {
  393. jassert (state == uninitializedState);
  394. if (! createNativeWindow())
  395. return Result::fail ("Can't create window");
  396. HRESULT hr = graphBuilder.CoCreateInstance (ComTypes::CLSID_FilterGraph);
  397. // basic playback interfaces
  398. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaControl);
  399. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaPosition);
  400. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaEvent);
  401. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (basicAudio);
  402. // video renderer interface
  403. if (SUCCEEDED (hr))
  404. {
  405. if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
  406. {
  407. videoRenderer.reset (new VideoRenderers::EVR());
  408. hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
  409. if (FAILED (hr))
  410. videoRenderer = nullptr;
  411. }
  412. if (videoRenderer == nullptr)
  413. {
  414. videoRenderer.reset (new VideoRenderers::VMR7());
  415. hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
  416. }
  417. }
  418. // build filter graph
  419. if (SUCCEEDED (hr))
  420. {
  421. hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
  422. if (FAILED (hr))
  423. {
  424. #if JUCE_MODAL_LOOPS_PERMITTED
  425. // Annoyingly, if we don't run the msg loop between failing and deleting the window, the
  426. // whole OS message-dispatch system gets itself into a state, and refuses to deliver any
  427. // more messages for the whole app. (That's what happens in Win7, anyway)
  428. MessageManager::getInstance()->runDispatchLoopUntil (200);
  429. #endif
  430. }
  431. }
  432. // remove video renderer if not connected (no video)
  433. if (SUCCEEDED (hr))
  434. {
  435. if (isRendererConnected())
  436. {
  437. hasVideo = true;
  438. }
  439. else
  440. {
  441. hasVideo = false;
  442. graphBuilder->RemoveFilter (baseFilter);
  443. videoRenderer = nullptr;
  444. baseFilter = nullptr;
  445. }
  446. }
  447. // set window to receive events
  448. if (SUCCEEDED (hr))
  449. {
  450. mediaEvent->CancelDefaultHandling (ComTypes::EC_STATE_CHANGE);
  451. hr = mediaEvent->SetNotifyWindow ((ComTypes::OAHWND) hwnd, graphEventID, 0);
  452. }
  453. if (SUCCEEDED (hr))
  454. {
  455. state = stoppedState;
  456. pause();
  457. return Result::ok();
  458. }
  459. // Note that if you're trying to open a file and this method fails, you may
  460. // just need to install a suitable codec. It seems that by default DirectShow
  461. // doesn't support a very good range of formats.
  462. release();
  463. return getErrorMessageFromResult (hr);
  464. }
  465. static Result getErrorMessageFromResult (HRESULT hr)
  466. {
  467. switch (hr)
  468. {
  469. case ComTypes::VFW_E_INVALID_FILE_FORMAT: return Result::fail ("Invalid file format");
  470. case ComTypes::VFW_E_NOT_FOUND: return Result::fail ("File not found");
  471. case ComTypes::VFW_E_UNKNOWN_FILE_TYPE: return Result::fail ("Unknown file type");
  472. case ComTypes::VFW_E_UNSUPPORTED_STREAM: return Result::fail ("Unsupported stream");
  473. case ComTypes::VFW_E_CANNOT_CONNECT: return Result::fail ("Cannot connect");
  474. case ComTypes::VFW_E_CANNOT_LOAD_SOURCE_FILTER: return Result::fail ("Cannot load source filter");
  475. }
  476. TCHAR messageBuffer[512] = { 0 };
  477. FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  478. nullptr, (DWORD) hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
  479. messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
  480. return Result::fail (String (messageBuffer));
  481. }
  482. void release()
  483. {
  484. if (mediaControl != nullptr)
  485. mediaControl->Stop();
  486. if (mediaEvent != nullptr)
  487. mediaEvent->SetNotifyWindow (0, 0, 0);
  488. if (videoRenderer != nullptr)
  489. videoRenderer->setVideoWindow (nullptr);
  490. hasVideo = false;
  491. videoRenderer = nullptr;
  492. baseFilter = nullptr;
  493. basicAudio = nullptr;
  494. mediaEvent = nullptr;
  495. mediaPosition = nullptr;
  496. mediaControl = nullptr;
  497. graphBuilder = nullptr;
  498. state = uninitializedState;
  499. if (nativeWindow != nullptr)
  500. deleteNativeWindow();
  501. }
  502. void graphEventProc()
  503. {
  504. LONG ec = 0;
  505. LONG_PTR p1 = {}, p2 = {};
  506. jassert (mediaEvent != nullptr);
  507. while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
  508. {
  509. mediaEvent->FreeEventParams (ec, p1, p2);
  510. switch (ec)
  511. {
  512. case ComTypes::EC_REPAINT:
  513. component.repaint();
  514. break;
  515. case ComTypes::EC_COMPLETE:
  516. component.stop();
  517. component.setPosition (0.0);
  518. break;
  519. case ComTypes::EC_ERRORABORT:
  520. case ComTypes::EC_ERRORABORTEX:
  521. component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
  522. // intentional fallthrough
  523. case ComTypes::EC_USERABORT:
  524. component.close();
  525. break;
  526. case ComTypes::EC_STATE_CHANGE:
  527. switch (p1)
  528. {
  529. case ComTypes::State_Paused: component.playbackStopped(); break;
  530. case ComTypes::State_Running: component.playbackStarted(); break;
  531. default: break;
  532. }
  533. default:
  534. break;
  535. }
  536. }
  537. }
  538. //==============================================================================
  539. void play()
  540. {
  541. mediaControl->Run();
  542. state = runningState;
  543. }
  544. void stop()
  545. {
  546. mediaControl->Stop();
  547. state = stoppedState;
  548. }
  549. void pause()
  550. {
  551. mediaControl->Pause();
  552. state = pausedState;
  553. }
  554. //==============================================================================
  555. Rectangle<int> getVideoSize() const noexcept
  556. {
  557. long width = 0, height = 0;
  558. if (hasVideo)
  559. videoRenderer->getVideoSize (width, height);
  560. return { (int) width, (int) height };
  561. }
  562. //==============================================================================
  563. double getDuration() const
  564. {
  565. ComTypes::REFTIME duration;
  566. mediaPosition->get_Duration (&duration);
  567. return duration;
  568. }
  569. double getSpeed() const
  570. {
  571. double speed;
  572. mediaPosition->get_Rate (&speed);
  573. return speed;
  574. }
  575. double getPosition() const
  576. {
  577. ComTypes::REFTIME seconds;
  578. mediaPosition->get_CurrentPosition (&seconds);
  579. return seconds;
  580. }
  581. void setSpeed (double newSpeed) { mediaPosition->put_Rate (newSpeed); }
  582. void setPosition (double seconds) { mediaPosition->put_CurrentPosition (seconds); }
  583. void setVolume (float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
  584. // in DirectShow, full volume is 0, silence is -10000
  585. static long convertToDShowVolume (float vol) noexcept
  586. {
  587. if (vol >= 1.0f) return 0;
  588. if (vol <= 0.0f) return -10000;
  589. return roundToInt ((vol * 10000.0f) - 10000.0f);
  590. }
  591. float getVolume() const
  592. {
  593. long volume;
  594. basicAudio->get_Volume (&volume);
  595. return (float) (volume + 10000) / 10000.0f;
  596. }
  597. enum State { uninitializedState, runningState, pausedState, stoppedState };
  598. State state = uninitializedState;
  599. private:
  600. //==============================================================================
  601. enum { graphEventID = WM_APP + 0x43f0 };
  602. Pimpl& component;
  603. HWND hwnd = {};
  604. HDC hdc = {};
  605. ComSmartPtr<ComTypes::IGraphBuilder> graphBuilder;
  606. ComSmartPtr<ComTypes::IMediaControl> mediaControl;
  607. ComSmartPtr<ComTypes::IMediaPosition> mediaPosition;
  608. ComSmartPtr<ComTypes::IMediaEventEx> mediaEvent;
  609. ComSmartPtr<ComTypes::IBasicAudio> basicAudio;
  610. ComSmartPtr<ComTypes::IBaseFilter> baseFilter;
  611. std::unique_ptr<VideoRenderers::Base> videoRenderer;
  612. bool hasVideo = false, needToUpdateViewport = true, needToRecreateNativeWindow = false;
  613. //==============================================================================
  614. bool createNativeWindow()
  615. {
  616. jassert (nativeWindow == nullptr);
  617. if (auto* topLevelPeer = component.getTopLevelComponent()->getPeer())
  618. {
  619. nativeWindow.reset (new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this));
  620. hwnd = nativeWindow->hwnd;
  621. component.currentPeer = topLevelPeer;
  622. component.currentPeer->addScaleFactorListener (&component);
  623. if (hwnd != nullptr)
  624. {
  625. hdc = GetDC (hwnd);
  626. component.updateContextPosition();
  627. component.updateContextVisibility();
  628. return true;
  629. }
  630. nativeWindow = nullptr;
  631. }
  632. else
  633. {
  634. jassertfalse;
  635. }
  636. return false;
  637. }
  638. void deleteNativeWindow()
  639. {
  640. jassert (nativeWindow != nullptr);
  641. ReleaseDC (hwnd, hdc);
  642. hwnd = {};
  643. hdc = {};
  644. nativeWindow = nullptr;
  645. }
  646. bool isRendererConnected()
  647. {
  648. ComSmartPtr<ComTypes::IEnumPins> enumPins;
  649. HRESULT hr = baseFilter->EnumPins (enumPins.resetAndGetPointerAddress());
  650. if (SUCCEEDED (hr))
  651. hr = enumPins->Reset();
  652. ComSmartPtr<ComTypes::IPin> pin;
  653. while (SUCCEEDED (hr)
  654. && enumPins->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
  655. {
  656. ComSmartPtr<ComTypes::IPin> otherPin;
  657. hr = pin->ConnectedTo (otherPin.resetAndGetPointerAddress());
  658. if (SUCCEEDED (hr))
  659. {
  660. ComTypes::PIN_DIRECTION direction;
  661. hr = pin->QueryDirection (&direction);
  662. if (SUCCEEDED (hr) && direction == ComTypes::PINDIR_INPUT)
  663. return true;
  664. }
  665. else if (hr == ComTypes::VFW_E_NOT_CONNECTED)
  666. {
  667. hr = S_OK;
  668. }
  669. }
  670. return false;
  671. }
  672. //==============================================================================
  673. struct NativeWindowClass : private DeletedAtShutdown
  674. {
  675. bool isRegistered() const noexcept { return atom != 0; }
  676. LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) (pointer_sized_uint) MAKELONG (atom, 0); }
  677. JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (NativeWindowClass)
  678. private:
  679. NativeWindowClass()
  680. {
  681. String windowClassName ("JUCE_DIRECTSHOW_");
  682. windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);
  683. HINSTANCE moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
  684. TCHAR moduleFile [1024] = {};
  685. GetModuleFileName (moduleHandle, moduleFile, 1024);
  686. WNDCLASSEX wcex = {};
  687. wcex.cbSize = sizeof (wcex);
  688. wcex.style = CS_OWNDC;
  689. wcex.lpfnWndProc = (WNDPROC) wndProc;
  690. wcex.lpszClassName = windowClassName.toWideCharPointer();
  691. wcex.hInstance = moduleHandle;
  692. atom = RegisterClassEx (&wcex);
  693. jassert (atom != 0);
  694. }
  695. ~NativeWindowClass()
  696. {
  697. if (atom != 0)
  698. UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
  699. clearSingletonInstance();
  700. }
  701. static LRESULT CALLBACK wndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  702. {
  703. if (auto* c = (DirectShowContext*) GetWindowLongPtr (hwnd, GWLP_USERDATA))
  704. {
  705. switch (msg)
  706. {
  707. case WM_NCHITTEST: return HTTRANSPARENT;
  708. case WM_ERASEBKGND: return 1;
  709. case WM_DISPLAYCHANGE: c->displayResolutionChanged(); break;
  710. case graphEventID: c->graphEventProc(); return 0;
  711. default: break;
  712. }
  713. }
  714. return DefWindowProc (hwnd, msg, wParam, lParam);
  715. }
  716. ATOM atom = {};
  717. JUCE_DECLARE_NON_COPYABLE (NativeWindowClass)
  718. };
  719. //==============================================================================
  720. struct NativeWindow
  721. {
  722. NativeWindow (HWND parentToAddTo, void* userData)
  723. {
  724. auto* wc = NativeWindowClass::getInstance();
  725. if (wc->isRegistered())
  726. {
  727. DWORD exstyle = 0;
  728. DWORD type = WS_CHILD;
  729. hwnd = CreateWindowEx (exstyle, wc->getWindowClassName(),
  730. L"", type, 0, 0, 0, 0, parentToAddTo, nullptr,
  731. (HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr);
  732. if (hwnd != nullptr)
  733. {
  734. hdc = GetDC (hwnd);
  735. SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) userData);
  736. }
  737. }
  738. jassert (hwnd != nullptr);
  739. }
  740. ~NativeWindow()
  741. {
  742. if (hwnd != nullptr)
  743. {
  744. SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) 0);
  745. DestroyWindow (hwnd);
  746. }
  747. }
  748. void setWindowPosition (Rectangle<int> newBounds)
  749. {
  750. SetWindowPos (hwnd, nullptr, newBounds.getX(), newBounds.getY(),
  751. newBounds.getWidth(), newBounds.getHeight(),
  752. SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
  753. }
  754. void showWindow (bool shouldBeVisible)
  755. {
  756. ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
  757. }
  758. HWND hwnd = {};
  759. HDC hdc = {};
  760. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow)
  761. };
  762. std::unique_ptr<NativeWindow> nativeWindow;
  763. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext)
  764. };
  765. std::unique_ptr<DirectShowContext> context;
  766. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  767. };
  768. JUCE_IMPLEMENT_SINGLETON (VideoComponent::Pimpl::DirectShowContext::NativeWindowClass)