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) 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. #if JUCE_WIN_PER_MONITOR_DPI_AWARE
  124. , public ComponentPeer::ScaleFactorListener
  125. #endif
  126. {
  127. Pimpl (VideoComponent& ownerToUse, bool)
  128. : owner (ownerToUse),
  129. videoLoaded (false)
  130. {
  131. setOpaque (true);
  132. context.reset (new DirectShowContext (*this));
  133. componentWatcher.reset (new ComponentWatcher (*this));
  134. }
  135. ~Pimpl() override
  136. {
  137. close();
  138. context = nullptr;
  139. componentWatcher = nullptr;
  140. #if JUCE_WIN_PER_MONITOR_DPI_AWARE
  141. for (int i = 0; i < ComponentPeer::getNumPeers(); ++i)
  142. if (auto* peer = ComponentPeer::getPeer (i))
  143. peer->removeScaleFactorListener (this);
  144. #endif
  145. }
  146. Result loadFromString (const String& fileOrURLPath)
  147. {
  148. close();
  149. auto r = context->loadFile (fileOrURLPath);
  150. if (r.wasOk())
  151. {
  152. videoLoaded = true;
  153. context->updateVideoPosition();
  154. }
  155. return r;
  156. }
  157. Result load (const File& file)
  158. {
  159. auto r = loadFromString (file.getFullPathName());
  160. if (r.wasOk())
  161. currentFile = file;
  162. return r;
  163. }
  164. Result load (const URL& url)
  165. {
  166. auto r = loadFromString (URL::removeEscapeChars (url.toString (true)));
  167. if (r.wasOk())
  168. currentURL = url;
  169. return r;
  170. }
  171. void close()
  172. {
  173. stop();
  174. context->release();
  175. videoLoaded = false;
  176. currentFile = File();
  177. currentURL = {};
  178. }
  179. bool isOpen() const
  180. {
  181. return videoLoaded;
  182. }
  183. bool isPlaying() const
  184. {
  185. return context->state == DirectShowContext::runningState;
  186. }
  187. void play()
  188. {
  189. if (videoLoaded)
  190. context->play();
  191. }
  192. void stop()
  193. {
  194. if (videoLoaded)
  195. context->pause();
  196. }
  197. void setPosition (double newPosition)
  198. {
  199. if (videoLoaded)
  200. context->setPosition (newPosition);
  201. }
  202. double getPosition() const
  203. {
  204. return videoLoaded ? context->getPosition() : 0.0;
  205. }
  206. void setSpeed (double newSpeed)
  207. {
  208. if (videoLoaded)
  209. context->setSpeed (newSpeed);
  210. }
  211. double getSpeed() const
  212. {
  213. return videoLoaded ? context->getSpeed() : 0.0;
  214. }
  215. Rectangle<int> getNativeSize() const
  216. {
  217. return videoLoaded ? context->getVideoSize()
  218. : Rectangle<int>();
  219. }
  220. double getDuration() const
  221. {
  222. return videoLoaded ? context->getDuration() : 0.0;
  223. }
  224. void setVolume (float newVolume)
  225. {
  226. if (videoLoaded)
  227. context->setVolume (newVolume);
  228. }
  229. float getVolume() const
  230. {
  231. return videoLoaded ? context->getVolume() : 0.0f;
  232. }
  233. void paint (Graphics& g) override
  234. {
  235. if (videoLoaded)
  236. context->handleUpdateNowIfNeeded();
  237. else
  238. g.fillAll (Colours::grey);
  239. }
  240. void updateContextPosition()
  241. {
  242. context->updateContextPosition();
  243. if (getWidth() > 0 && getHeight() > 0)
  244. if (auto* peer = getTopLevelComponent()->getPeer())
  245. context->updateWindowPosition ((peer->getAreaCoveredBy (*this).toDouble() * peer->getPlatformScaleFactor()).toNearestInt());
  246. }
  247. void updateContextVisibility()
  248. {
  249. context->showWindow (isShowing());
  250. }
  251. void recreateNativeWindowAsync()
  252. {
  253. context->recreateNativeWindowAsync();
  254. repaint();
  255. }
  256. void playbackStarted()
  257. {
  258. if (owner.onPlaybackStarted != nullptr)
  259. owner.onPlaybackStarted();
  260. }
  261. void playbackStopped()
  262. {
  263. if (owner.onPlaybackStopped != nullptr)
  264. owner.onPlaybackStopped();
  265. }
  266. void errorOccurred (const String& errorMessage)
  267. {
  268. if (owner.onErrorOccurred != nullptr)
  269. owner.onErrorOccurred (errorMessage);
  270. }
  271. #if JUCE_WIN_PER_MONITOR_DPI_AWARE
  272. void nativeScaleFactorChanged (double /*newScaleFactor*/) override
  273. {
  274. if (videoLoaded)
  275. updateContextPosition();
  276. }
  277. #endif
  278. File currentFile;
  279. URL currentURL;
  280. private:
  281. VideoComponent& owner;
  282. bool videoLoaded;
  283. //==============================================================================
  284. struct ComponentWatcher : public ComponentMovementWatcher
  285. {
  286. ComponentWatcher (Pimpl& c) : ComponentMovementWatcher (&c), owner (c)
  287. {
  288. }
  289. void componentMovedOrResized (bool, bool) override
  290. {
  291. if (owner.videoLoaded)
  292. owner.updateContextPosition();
  293. }
  294. void componentPeerChanged() override
  295. {
  296. if (owner.videoLoaded)
  297. owner.recreateNativeWindowAsync();
  298. }
  299. void componentVisibilityChanged() override
  300. {
  301. if (owner.videoLoaded)
  302. owner.updateContextVisibility();
  303. }
  304. Pimpl& owner;
  305. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentWatcher)
  306. };
  307. std::unique_ptr<ComponentWatcher> componentWatcher;
  308. //==============================================================================
  309. struct DirectShowContext : public AsyncUpdater
  310. {
  311. DirectShowContext (Pimpl& c) : component (c)
  312. {
  313. ignoreUnused (CoInitialize (nullptr));
  314. }
  315. ~DirectShowContext() override
  316. {
  317. release();
  318. CoUninitialize();
  319. }
  320. //==============================================================================
  321. void updateWindowPosition (const Rectangle<int>& newBounds)
  322. {
  323. nativeWindow->setWindowPosition (newBounds);
  324. }
  325. void showWindow (bool shouldBeVisible)
  326. {
  327. nativeWindow->showWindow (shouldBeVisible);
  328. }
  329. //==============================================================================
  330. void repaint()
  331. {
  332. if (hasVideo)
  333. videoRenderer->repaintVideo (nativeWindow->hwnd, nativeWindow->hdc);
  334. }
  335. void updateVideoPosition()
  336. {
  337. if (hasVideo)
  338. videoRenderer->setVideoPosition (nativeWindow->hwnd);
  339. }
  340. void displayResolutionChanged()
  341. {
  342. if (hasVideo)
  343. videoRenderer->displayModeChanged();
  344. }
  345. //==============================================================================
  346. void peerChanged()
  347. {
  348. deleteNativeWindow();
  349. mediaEvent->SetNotifyWindow (0, 0, 0);
  350. if (videoRenderer != nullptr)
  351. videoRenderer->setVideoWindow (nullptr);
  352. createNativeWindow();
  353. mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
  354. mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
  355. if (videoRenderer != nullptr)
  356. videoRenderer->setVideoWindow (hwnd);
  357. }
  358. void handleAsyncUpdate() override
  359. {
  360. if (hwnd != nullptr)
  361. {
  362. if (needToRecreateNativeWindow)
  363. {
  364. peerChanged();
  365. needToRecreateNativeWindow = false;
  366. }
  367. if (needToUpdateViewport)
  368. {
  369. updateVideoPosition();
  370. needToUpdateViewport = false;
  371. }
  372. repaint();
  373. }
  374. else
  375. {
  376. triggerAsyncUpdate();
  377. }
  378. }
  379. void recreateNativeWindowAsync()
  380. {
  381. needToRecreateNativeWindow = true;
  382. triggerAsyncUpdate();
  383. }
  384. void updateContextPosition()
  385. {
  386. needToUpdateViewport = true;
  387. triggerAsyncUpdate();
  388. }
  389. //==============================================================================
  390. Result loadFile (const String& fileOrURLPath)
  391. {
  392. jassert (state == uninitializedState);
  393. if (! createNativeWindow())
  394. return Result::fail ("Can't create window");
  395. HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
  396. // basic playback interfaces
  397. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaControl);
  398. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaPosition);
  399. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaEvent);
  400. if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (basicAudio);
  401. // video renderer interface
  402. if (SUCCEEDED (hr))
  403. {
  404. if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
  405. {
  406. videoRenderer.reset (new VideoRenderers::EVR());
  407. hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
  408. if (FAILED (hr))
  409. videoRenderer = nullptr;
  410. }
  411. if (videoRenderer == nullptr)
  412. {
  413. videoRenderer.reset (new VideoRenderers::VMR7());
  414. hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
  415. }
  416. }
  417. // build filter graph
  418. if (SUCCEEDED (hr))
  419. {
  420. hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
  421. if (FAILED (hr))
  422. {
  423. #if JUCE_MODAL_LOOPS_PERMITTED
  424. // Annoyingly, if we don't run the msg loop between failing and deleting the window, the
  425. // whole OS message-dispatch system gets itself into a state, and refuses to deliver any
  426. // more messages for the whole app. (That's what happens in Win7, anyway)
  427. MessageManager::getInstance()->runDispatchLoopUntil (200);
  428. #endif
  429. }
  430. }
  431. // remove video renderer if not connected (no video)
  432. if (SUCCEEDED (hr))
  433. {
  434. if (isRendererConnected())
  435. {
  436. hasVideo = true;
  437. }
  438. else
  439. {
  440. hasVideo = false;
  441. graphBuilder->RemoveFilter (baseFilter);
  442. videoRenderer = nullptr;
  443. baseFilter = nullptr;
  444. }
  445. }
  446. // set window to receive events
  447. if (SUCCEEDED (hr))
  448. {
  449. mediaEvent->CancelDefaultHandling (EC_STATE_CHANGE);
  450. hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
  451. }
  452. if (SUCCEEDED (hr))
  453. {
  454. state = stoppedState;
  455. pause();
  456. return Result::ok();
  457. }
  458. // Note that if you're trying to open a file and this method fails, you may
  459. // just need to install a suitable codec. It seems that by default DirectShow
  460. // doesn't support a very good range of formats.
  461. release();
  462. return getErrorMessageFromResult (hr);
  463. }
  464. static Result getErrorMessageFromResult (HRESULT hr)
  465. {
  466. switch (hr)
  467. {
  468. case VFW_E_INVALID_FILE_FORMAT: return Result::fail ("Invalid file format");
  469. case VFW_E_NOT_FOUND: return Result::fail ("File not found");
  470. case VFW_E_UNKNOWN_FILE_TYPE: return Result::fail ("Unknown file type");
  471. case VFW_E_UNSUPPORTED_STREAM: return Result::fail ("Unsupported stream");
  472. case VFW_E_CANNOT_CONNECT: return Result::fail ("Cannot connect");
  473. case VFW_E_CANNOT_LOAD_SOURCE_FILTER: return Result::fail ("Cannot load source filter");
  474. }
  475. TCHAR messageBuffer[512] = { 0 };
  476. FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  477. nullptr, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
  478. messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
  479. return Result::fail (String (messageBuffer));
  480. }
  481. void release()
  482. {
  483. if (mediaControl != nullptr)
  484. mediaControl->Stop();
  485. if (mediaEvent != nullptr)
  486. mediaEvent->SetNotifyWindow (0, 0, 0);
  487. if (videoRenderer != nullptr)
  488. videoRenderer->setVideoWindow (nullptr);
  489. hasVideo = false;
  490. videoRenderer = nullptr;
  491. baseFilter = nullptr;
  492. basicAudio = nullptr;
  493. mediaEvent = nullptr;
  494. mediaPosition = nullptr;
  495. mediaControl = nullptr;
  496. graphBuilder = nullptr;
  497. state = uninitializedState;
  498. if (nativeWindow != nullptr)
  499. deleteNativeWindow();
  500. }
  501. void graphEventProc()
  502. {
  503. LONG ec = 0;
  504. LONG_PTR p1 = {}, p2 = {};
  505. jassert (mediaEvent != nullptr);
  506. while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
  507. {
  508. mediaEvent->FreeEventParams (ec, p1, p2);
  509. switch (ec)
  510. {
  511. case EC_REPAINT:
  512. component.repaint();
  513. break;
  514. case EC_COMPLETE:
  515. component.stop();
  516. component.setPosition (0.0);
  517. break;
  518. case EC_ERRORABORT:
  519. case EC_ERRORABORTEX:
  520. component.errorOccurred (getErrorMessageFromResult ((HRESULT) p1).getErrorMessage());
  521. // intentional fallthrough
  522. case EC_USERABORT:
  523. component.close();
  524. break;
  525. case EC_STATE_CHANGE:
  526. switch (p1)
  527. {
  528. case State_Paused: component.playbackStopped(); break;
  529. case State_Running: component.playbackStarted(); break;
  530. default: break;
  531. }
  532. default:
  533. break;
  534. }
  535. }
  536. }
  537. //==============================================================================
  538. void play()
  539. {
  540. mediaControl->Run();
  541. state = runningState;
  542. }
  543. void stop()
  544. {
  545. mediaControl->Stop();
  546. state = stoppedState;
  547. }
  548. void pause()
  549. {
  550. mediaControl->Pause();
  551. state = pausedState;
  552. }
  553. //==============================================================================
  554. Rectangle<int> getVideoSize() const noexcept
  555. {
  556. long width = 0, height = 0;
  557. if (hasVideo)
  558. videoRenderer->getVideoSize (width, height);
  559. return { (int) width, (int) height };
  560. }
  561. //==============================================================================
  562. double getDuration() const
  563. {
  564. REFTIME duration;
  565. mediaPosition->get_Duration (&duration);
  566. return duration;
  567. }
  568. double getSpeed() const
  569. {
  570. double speed;
  571. mediaPosition->get_Rate (&speed);
  572. return speed;
  573. }
  574. double getPosition() const
  575. {
  576. REFTIME seconds;
  577. mediaPosition->get_CurrentPosition (&seconds);
  578. return seconds;
  579. }
  580. void setSpeed (double newSpeed) { mediaPosition->put_Rate (newSpeed); }
  581. void setPosition (double seconds) { mediaPosition->put_CurrentPosition (seconds); }
  582. void setVolume (float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
  583. // in DirectShow, full volume is 0, silence is -10000
  584. static long convertToDShowVolume (float vol) noexcept
  585. {
  586. if (vol >= 1.0f) return 0;
  587. if (vol <= 0.0f) return -10000;
  588. return roundToInt ((vol * 10000.0f) - 10000.0f);
  589. }
  590. float getVolume() const
  591. {
  592. long volume;
  593. basicAudio->get_Volume (&volume);
  594. return (float) (volume + 10000) / 10000.0f;
  595. }
  596. enum State { uninitializedState, runningState, pausedState, stoppedState };
  597. State state = uninitializedState;
  598. private:
  599. //==============================================================================
  600. enum { graphEventID = WM_APP + 0x43f0 };
  601. Pimpl& component;
  602. HWND hwnd = {};
  603. HDC hdc = {};
  604. ComSmartPtr<IGraphBuilder> graphBuilder;
  605. ComSmartPtr<IMediaControl> mediaControl;
  606. ComSmartPtr<IMediaPosition> mediaPosition;
  607. ComSmartPtr<IMediaEventEx> mediaEvent;
  608. ComSmartPtr<IBasicAudio> basicAudio;
  609. ComSmartPtr<IBaseFilter> baseFilter;
  610. std::unique_ptr<VideoRenderers::Base> videoRenderer;
  611. bool hasVideo = false, needToUpdateViewport = true, needToRecreateNativeWindow = false;
  612. //==============================================================================
  613. bool createNativeWindow()
  614. {
  615. jassert (nativeWindow == nullptr);
  616. if (auto* topLevelPeer = component.getTopLevelComponent()->getPeer())
  617. {
  618. nativeWindow.reset (new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this));
  619. hwnd = nativeWindow->hwnd;
  620. #if JUCE_WIN_PER_MONITOR_DPI_AWARE
  621. topLevelPeer->addScaleFactorListener (&component);
  622. #endif
  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<IEnumPins> enumPins;
  649. HRESULT hr = baseFilter->EnumPins (enumPins.resetAndGetPointerAddress());
  650. if (SUCCEEDED (hr))
  651. hr = enumPins->Reset();
  652. ComSmartPtr<IPin> pin;
  653. while (SUCCEEDED (hr)
  654. && enumPins->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
  655. {
  656. ComSmartPtr<IPin> otherPin;
  657. hr = pin->ConnectedTo (otherPin.resetAndGetPointerAddress());
  658. if (SUCCEEDED (hr))
  659. {
  660. PIN_DIRECTION direction;
  661. hr = pin->QueryDirection (&direction);
  662. if (SUCCEEDED (hr) && direction == PINDIR_INPUT)
  663. return true;
  664. }
  665. else if (hr == 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)