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.

965 lines
30KB

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