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.

835 lines
30KB

  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. #if JUCE_MAC
  19. using Base = NSViewComponent;
  20. #else
  21. using Base = UIViewComponent;
  22. #endif
  23. struct VideoComponent::Pimpl : public Base
  24. {
  25. Pimpl (VideoComponent& ownerToUse, bool useNativeControlsIfAvailable)
  26. : owner (ownerToUse),
  27. playerController (*this, useNativeControlsIfAvailable)
  28. {
  29. setVisible (true);
  30. auto* view = playerController.getView();
  31. setView (view);
  32. #if JUCE_MAC
  33. [view setNextResponder: [view superview]];
  34. [view setWantsLayer: YES];
  35. #endif
  36. }
  37. ~Pimpl()
  38. {
  39. close();
  40. setView (nil);
  41. }
  42. Result load (const File& file)
  43. {
  44. auto r = load (createNSURLFromFile (file));
  45. if (r.wasOk())
  46. currentFile = file;
  47. return r;
  48. }
  49. Result load (const URL& url)
  50. {
  51. auto r = load ([NSURL URLWithString: juceStringToNS (url.toString (true))]);
  52. if (r.wasOk())
  53. currentURL = url;
  54. return r;
  55. }
  56. Result load (NSURL* url)
  57. {
  58. if (url != nil)
  59. {
  60. close();
  61. return playerController.load (url);
  62. }
  63. return Result::fail ("Couldn't open movie");
  64. }
  65. void loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
  66. {
  67. if (url.isEmpty())
  68. {
  69. jassertfalse;
  70. return;
  71. }
  72. currentURL = url;
  73. jassert (callback != nullptr);
  74. loadFinishedCallback = std::move (callback);
  75. playerController.loadAsync (url);
  76. }
  77. void close()
  78. {
  79. stop();
  80. playerController.close();
  81. currentFile = File();
  82. currentURL = {};
  83. }
  84. bool isOpen() const noexcept { return playerController.getPlayer() != nil; }
  85. bool isPlaying() const noexcept { return getSpeed() != 0; }
  86. void play() noexcept { [playerController.getPlayer() play]; setSpeed (playSpeedMult); }
  87. void stop() noexcept { [playerController.getPlayer() pause]; }
  88. void setPosition (double newPosition)
  89. {
  90. if (auto* p = playerController.getPlayer())
  91. {
  92. CMTime t = { (CMTimeValue) (100000.0 * newPosition),
  93. (CMTimeScale) 100000, kCMTimeFlags_Valid, {} };
  94. [p seekToTime: t
  95. toleranceBefore: kCMTimeZero
  96. toleranceAfter: kCMTimeZero];
  97. }
  98. }
  99. double getPosition() const
  100. {
  101. if (auto* p = playerController.getPlayer())
  102. return toSeconds ([p currentTime]);
  103. return 0.0;
  104. }
  105. void setSpeed (double newSpeed)
  106. {
  107. playSpeedMult = newSpeed;
  108. // Calling non 0.0 speed on a paused player would start it...
  109. if (isPlaying())
  110. [playerController.getPlayer() setRate: (float) playSpeedMult];
  111. }
  112. double getSpeed() const
  113. {
  114. if (auto* p = playerController.getPlayer())
  115. return [p rate];
  116. return 0.0;
  117. }
  118. Rectangle<int> getNativeSize() const
  119. {
  120. if (auto* p = playerController.getPlayer())
  121. {
  122. auto s = [[p currentItem] presentationSize];
  123. return { (int) s.width, (int) s.height };
  124. }
  125. return {};
  126. }
  127. double getDuration() const
  128. {
  129. if (auto* p = playerController.getPlayer())
  130. return toSeconds ([[p currentItem] duration]);
  131. return 0.0;
  132. }
  133. void setVolume (float newVolume)
  134. {
  135. [playerController.getPlayer() setVolume: newVolume];
  136. }
  137. float getVolume() const
  138. {
  139. if (auto* p = playerController.getPlayer())
  140. return [p volume];
  141. return 0.0f;
  142. }
  143. File currentFile;
  144. URL currentURL;
  145. private:
  146. //==============================================================================
  147. template <typename Derived>
  148. class PlayerControllerBase
  149. {
  150. public:
  151. ~PlayerControllerBase()
  152. {
  153. // Derived classes must call detachPlayerStatusObserver() before destruction!
  154. jassert (! playerStatusObserverAttached);
  155. // Derived classes must call detachPlaybackObserver() before destruction!
  156. jassert (! playbackObserverAttached);
  157. // Note that it's unsafe to call detachPlayerStatusObserver and detachPlaybackObserver
  158. // directly here, because those functions call into the derived class, which will have
  159. // been destroyed at this point.
  160. }
  161. protected:
  162. //==============================================================================
  163. struct JucePlayerStatusObserverClass : public ObjCClass<NSObject>
  164. {
  165. JucePlayerStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerStatusObserverClass_")
  166. {
  167. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  168. addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged);
  169. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  170. addIvar<PlayerAsyncInitialiser*> ("owner");
  171. registerClass();
  172. }
  173. //==============================================================================
  174. static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
  175. static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
  176. private:
  177. static void valueChanged (id self, SEL, NSString* keyPath, id,
  178. NSDictionary<NSString*, id>* change, void*)
  179. {
  180. auto& owner = getOwner (self);
  181. if ([keyPath isEqualToString: nsStringLiteral ("rate")])
  182. {
  183. auto oldRate = [[change objectForKey: NSKeyValueChangeOldKey] floatValue];
  184. auto newRate = [[change objectForKey: NSKeyValueChangeNewKey] floatValue];
  185. if (oldRate == 0 && newRate != 0)
  186. owner.playbackStarted();
  187. else if (oldRate != 0 && newRate == 0)
  188. owner.playbackStopped();
  189. }
  190. else if ([keyPath isEqualToString: nsStringLiteral ("status")])
  191. {
  192. auto status = [[change objectForKey: NSKeyValueChangeNewKey] intValue];
  193. if (status == AVPlayerStatusFailed)
  194. owner.errorOccurred();
  195. }
  196. }
  197. };
  198. //==============================================================================
  199. struct JucePlayerItemPlaybackStatusObserverClass : public ObjCClass<NSObject>
  200. {
  201. JucePlayerItemPlaybackStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemPlaybackStatusObserverClass_")
  202. {
  203. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  204. addMethod (@selector (processNotification:), notificationReceived);
  205. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  206. addIvar<PlayerControllerBase*> ("owner");
  207. registerClass();
  208. }
  209. //==============================================================================
  210. static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
  211. static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
  212. private:
  213. static void notificationReceived (id self, SEL, NSNotification* notification)
  214. {
  215. if ([notification.name isEqualToString: AVPlayerItemDidPlayToEndTimeNotification])
  216. getOwner (self).playbackReachedEndTime();
  217. }
  218. };
  219. //==============================================================================
  220. class PlayerAsyncInitialiser
  221. {
  222. public:
  223. PlayerAsyncInitialiser (PlayerControllerBase& ownerToUse)
  224. : owner (ownerToUse),
  225. assetKeys ([[NSArray alloc] initWithObjects: nsStringLiteral ("duration"), nsStringLiteral ("tracks"),
  226. nsStringLiteral ("playable"), nil])
  227. {
  228. static JucePlayerItemPreparationStatusObserverClass cls;
  229. playerItemPreparationStatusObserver.reset ([cls.createInstance() init]);
  230. JucePlayerItemPreparationStatusObserverClass::setOwner (playerItemPreparationStatusObserver.get(), this);
  231. }
  232. ~PlayerAsyncInitialiser()
  233. {
  234. detachPreparationStatusObserver();
  235. }
  236. void loadAsync (URL url)
  237. {
  238. auto nsUrl = [NSURL URLWithString: juceStringToNS (url.toString (true))];
  239. asset.reset ([[AVURLAsset alloc] initWithURL: nsUrl options: nil]);
  240. [asset.get() loadValuesAsynchronouslyForKeys: assetKeys.get()
  241. completionHandler: ^() { checkAllKeysReadyFor (asset.get(), url); }];
  242. }
  243. private:
  244. //==============================================================================
  245. struct JucePlayerItemPreparationStatusObserverClass : public ObjCClass<NSObject>
  246. {
  247. JucePlayerItemPreparationStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemStatusObserverClass_")
  248. {
  249. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  250. addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged);
  251. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  252. addIvar<PlayerAsyncInitialiser*> ("owner");
  253. registerClass();
  254. }
  255. //==============================================================================
  256. static PlayerAsyncInitialiser& getOwner (id self) { return *getIvar<PlayerAsyncInitialiser*> (self, "owner"); }
  257. static void setOwner (id self, PlayerAsyncInitialiser* p) { object_setInstanceVariable (self, "owner", p); }
  258. private:
  259. static void valueChanged (id self, SEL, NSString*, id object,
  260. NSDictionary<NSString*, id>* change, void* context)
  261. {
  262. auto& owner = getOwner (self);
  263. if (context == &owner)
  264. {
  265. auto* playerItem = (AVPlayerItem*) object;
  266. auto* urlAsset = (AVURLAsset*) playerItem.asset;
  267. URL url (nsStringToJuce (urlAsset.URL.absoluteString));
  268. auto oldStatus = [[change objectForKey: NSKeyValueChangeOldKey] intValue];
  269. auto newStatus = [[change objectForKey: NSKeyValueChangeNewKey] intValue];
  270. // Ignore spurious notifications
  271. if (oldStatus == newStatus)
  272. return;
  273. if (newStatus == AVPlayerItemStatusFailed)
  274. {
  275. auto errorMessage = playerItem.error != nil
  276. ? nsStringToJuce (playerItem.error.localizedDescription)
  277. : String();
  278. owner.notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
  279. }
  280. else if (newStatus == AVPlayerItemStatusReadyToPlay)
  281. {
  282. owner.notifyOwnerPreparationFinished (url, Result::ok(), owner.player.get());
  283. }
  284. else
  285. {
  286. jassertfalse;
  287. }
  288. }
  289. }
  290. };
  291. //==============================================================================
  292. PlayerControllerBase& owner;
  293. std::unique_ptr<AVURLAsset, NSObjectDeleter> asset;
  294. std::unique_ptr<NSArray<NSString*>, NSObjectDeleter> assetKeys;
  295. std::unique_ptr<AVPlayerItem, NSObjectDeleter> playerItem;
  296. std::unique_ptr<NSObject, NSObjectDeleter> playerItemPreparationStatusObserver;
  297. std::unique_ptr<AVPlayer, NSObjectDeleter> player;
  298. //==============================================================================
  299. void checkAllKeysReadyFor (AVAsset* assetToCheck, const URL& url)
  300. {
  301. NSError* error = nil;
  302. int successCount = 0;
  303. for (NSString* key : assetKeys.get())
  304. {
  305. switch ([assetToCheck statusOfValueForKey: key error: &error])
  306. {
  307. case AVKeyValueStatusLoaded:
  308. {
  309. ++successCount;
  310. break;
  311. }
  312. case AVKeyValueStatusCancelled:
  313. {
  314. notifyOwnerPreparationFinished (url, Result::fail ("Loading cancelled"), nullptr);
  315. return;
  316. }
  317. case AVKeyValueStatusFailed:
  318. {
  319. auto errorMessage = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  320. notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
  321. return;
  322. }
  323. case AVKeyValueStatusUnknown:
  324. case AVKeyValueStatusLoading:
  325. default:
  326. break;
  327. }
  328. }
  329. jassert (successCount == (int) [assetKeys.get() count]);
  330. preparePlayerItem();
  331. }
  332. void preparePlayerItem()
  333. {
  334. playerItem.reset ([[AVPlayerItem alloc] initWithAsset: asset.get()]);
  335. attachPreparationStatusObserver();
  336. player.reset ([[AVPlayer alloc] initWithPlayerItem: playerItem.get()]);
  337. }
  338. //==============================================================================
  339. void attachPreparationStatusObserver()
  340. {
  341. [playerItem.get() addObserver: playerItemPreparationStatusObserver.get()
  342. forKeyPath: nsStringLiteral ("status")
  343. options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
  344. context: this];
  345. }
  346. void detachPreparationStatusObserver()
  347. {
  348. if (playerItem != nullptr && playerItemPreparationStatusObserver != nullptr)
  349. {
  350. [playerItem.get() removeObserver: playerItemPreparationStatusObserver.get()
  351. forKeyPath: nsStringLiteral ("status")
  352. context: this];
  353. }
  354. }
  355. //==============================================================================
  356. void notifyOwnerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
  357. {
  358. MessageManager::callAsync ([url, preparedPlayer, r,
  359. safeThis = WeakReference<PlayerAsyncInitialiser> { this }]() mutable
  360. {
  361. if (safeThis != nullptr)
  362. safeThis->owner.playerPreparationFinished (url, r, preparedPlayer);
  363. });
  364. }
  365. JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerAsyncInitialiser)
  366. };
  367. //==============================================================================
  368. Pimpl& owner;
  369. bool useNativeControls;
  370. PlayerAsyncInitialiser playerAsyncInitialiser;
  371. std::unique_ptr<NSObject, NSObjectDeleter> playerStatusObserver;
  372. std::unique_ptr<NSObject, NSObjectDeleter> playerItemPlaybackStatusObserver;
  373. //==============================================================================
  374. PlayerControllerBase (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  375. : owner (ownerToUse),
  376. useNativeControls (useNativeControlsIfAvailable),
  377. playerAsyncInitialiser (*this)
  378. {
  379. static JucePlayerStatusObserverClass playerObserverClass;
  380. playerStatusObserver.reset ([playerObserverClass.createInstance() init]);
  381. JucePlayerStatusObserverClass::setOwner (playerStatusObserver.get(), this);
  382. static JucePlayerItemPlaybackStatusObserverClass itemObserverClass;
  383. playerItemPlaybackStatusObserver.reset ([itemObserverClass.createInstance() init]);
  384. JucePlayerItemPlaybackStatusObserverClass::setOwner (playerItemPlaybackStatusObserver.get(), this);
  385. }
  386. //==============================================================================
  387. void attachPlayerStatusObserver()
  388. {
  389. [crtp().getPlayer() addObserver: playerStatusObserver.get()
  390. forKeyPath: nsStringLiteral ("rate")
  391. options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
  392. context: this];
  393. [crtp().getPlayer() addObserver: playerStatusObserver.get()
  394. forKeyPath: nsStringLiteral ("status")
  395. options: NSKeyValueObservingOptionNew
  396. context: this];
  397. playerStatusObserverAttached = true;
  398. }
  399. void detachPlayerStatusObserver()
  400. {
  401. if (crtp().getPlayer() != nullptr && playerStatusObserver != nullptr)
  402. {
  403. [crtp().getPlayer() removeObserver: playerStatusObserver.get()
  404. forKeyPath: nsStringLiteral ("rate")
  405. context: this];
  406. [crtp().getPlayer() removeObserver: playerStatusObserver.get()
  407. forKeyPath: nsStringLiteral ("status")
  408. context: this];
  409. }
  410. playerStatusObserverAttached = false;
  411. }
  412. void attachPlaybackObserver()
  413. {
  414. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  415. [[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
  416. selector: @selector (processNotification:)
  417. name: AVPlayerItemDidPlayToEndTimeNotification
  418. object: [crtp().getPlayer() currentItem]];
  419. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  420. playbackObserverAttached = true;
  421. }
  422. void detachPlaybackObserver()
  423. {
  424. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  425. [[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
  426. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  427. playbackObserverAttached = false;
  428. }
  429. private:
  430. //==============================================================================
  431. Derived& crtp() { return static_cast<Derived&> (*this); }
  432. //==============================================================================
  433. void playerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
  434. {
  435. if (preparedPlayer != nil)
  436. crtp().setPlayer (preparedPlayer);
  437. owner.playerPreparationFinished (url, r);
  438. }
  439. void playbackReachedEndTime()
  440. {
  441. MessageManager::callAsync ([safeThis = WeakReference<PlayerControllerBase> { this }]() mutable
  442. {
  443. if (safeThis != nullptr)
  444. safeThis->owner.playbackReachedEndTime();
  445. });
  446. }
  447. //==============================================================================
  448. void errorOccurred()
  449. {
  450. auto errorMessage = (crtp().getPlayer() != nil && crtp().getPlayer().error != nil)
  451. ? nsStringToJuce (crtp().getPlayer().error.localizedDescription)
  452. : String();
  453. owner.errorOccurred (errorMessage);
  454. }
  455. void playbackStarted()
  456. {
  457. owner.playbackStarted();
  458. }
  459. void playbackStopped()
  460. {
  461. owner.playbackStopped();
  462. }
  463. bool playerStatusObserverAttached = false, playbackObserverAttached = false;
  464. JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerControllerBase)
  465. };
  466. #if JUCE_MAC
  467. //==============================================================================
  468. class PlayerController : public PlayerControllerBase<PlayerController>
  469. {
  470. public:
  471. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  472. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  473. {
  474. wrappedPlayer = [&]() -> std::unique_ptr<WrappedPlayer>
  475. {
  476. if (@available (macOS 10.9, *))
  477. if (useNativeControls)
  478. return std::make_unique<WrappedPlayerView>();
  479. return std::make_unique<WrappedPlayerLayer> ();
  480. }();
  481. }
  482. NSView* getView()
  483. {
  484. return wrappedPlayer->getView();
  485. }
  486. Result load (NSURL* url)
  487. {
  488. if (auto player = [AVPlayer playerWithURL: url])
  489. {
  490. setPlayer (player);
  491. return Result::ok();
  492. }
  493. return Result::fail ("Couldn't open movie");
  494. }
  495. void loadAsync (URL url)
  496. {
  497. playerAsyncInitialiser.loadAsync (url);
  498. }
  499. void close() { setPlayer (nil); }
  500. void setPlayer (AVPlayer* player)
  501. {
  502. detachPlayerStatusObserver();
  503. detachPlaybackObserver();
  504. wrappedPlayer->setPlayer (player);
  505. if (player != nil)
  506. {
  507. attachPlayerStatusObserver();
  508. attachPlaybackObserver();
  509. }
  510. }
  511. AVPlayer* getPlayer() const
  512. {
  513. return wrappedPlayer->getPlayer();
  514. }
  515. private:
  516. struct WrappedPlayer
  517. {
  518. virtual ~WrappedPlayer() = default;
  519. virtual NSView* getView() const = 0;
  520. virtual AVPlayer* getPlayer() const = 0;
  521. virtual void setPlayer (AVPlayer*) = 0;
  522. };
  523. class WrappedPlayerLayer : public WrappedPlayer
  524. {
  525. public:
  526. WrappedPlayerLayer () { [view.get() setLayer: playerLayer.get()]; }
  527. NSView* getView() const override { return view.get(); }
  528. AVPlayer* getPlayer() const override { return [playerLayer.get() player]; }
  529. void setPlayer (AVPlayer* player) override { [playerLayer.get() setPlayer: player]; }
  530. private:
  531. NSUniquePtr<NSView> view { [[NSView alloc] init] };
  532. NSUniquePtr<AVPlayerLayer> playerLayer { [[AVPlayerLayer alloc] init] };
  533. };
  534. class API_AVAILABLE (macos (10.9)) WrappedPlayerView : public WrappedPlayer
  535. {
  536. public:
  537. WrappedPlayerView() = default;
  538. NSView* getView() const override { return playerView.get(); }
  539. AVPlayer* getPlayer() const override { return [playerView.get() player]; }
  540. void setPlayer (AVPlayer* player) override { [playerView.get() setPlayer: player]; }
  541. private:
  542. NSUniquePtr<AVPlayerView> playerView { [[AVPlayerView alloc] init] };
  543. };
  544. std::unique_ptr<WrappedPlayer> wrappedPlayer;
  545. };
  546. #else
  547. //==============================================================================
  548. class PlayerController : public PlayerControllerBase<PlayerController>
  549. {
  550. public:
  551. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  552. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  553. {
  554. if (useNativeControls)
  555. {
  556. playerViewController.reset ([[AVPlayerViewController alloc] init]);
  557. }
  558. else
  559. {
  560. static JuceVideoViewerClass cls;
  561. playerView.reset ([cls.createInstance() init]);
  562. playerLayer.reset ([[AVPlayerLayer alloc] init]);
  563. [playerView.get().layer addSublayer: playerLayer.get()];
  564. }
  565. }
  566. UIView* getView()
  567. {
  568. if (useNativeControls)
  569. return [playerViewController.get() view];
  570. // Should call getView() only once.
  571. jassert (playerView != nil);
  572. return playerView.release();
  573. }
  574. Result load (NSURL*)
  575. {
  576. jassertfalse;
  577. return Result::fail ("Synchronous loading is not supported on iOS, use loadAsync()");
  578. }
  579. void loadAsync (URL url)
  580. {
  581. playerAsyncInitialiser.loadAsync (url);
  582. }
  583. void close() { setPlayer (nil); }
  584. AVPlayer* getPlayer() const
  585. {
  586. if (useNativeControls)
  587. return [playerViewController.get() player];
  588. return [playerLayer.get() player];
  589. }
  590. void setPlayer (AVPlayer* playerToUse)
  591. {
  592. detachPlayerStatusObserver();
  593. detachPlaybackObserver();
  594. if (useNativeControls)
  595. [playerViewController.get() setPlayer: playerToUse];
  596. else
  597. [playerLayer.get() setPlayer: playerToUse];
  598. if (playerToUse != nil)
  599. {
  600. attachPlayerStatusObserver();
  601. attachPlaybackObserver();
  602. }
  603. }
  604. private:
  605. //==============================================================================
  606. struct JuceVideoViewerClass : public ObjCClass<UIView>
  607. {
  608. JuceVideoViewerClass() : ObjCClass<UIView> ("JuceVideoViewerClass_")
  609. {
  610. addMethod (@selector (layoutSubviews), layoutSubviews);
  611. registerClass();
  612. }
  613. private:
  614. static void layoutSubviews (id self, SEL)
  615. {
  616. sendSuperclassMessage<void> (self, @selector (layoutSubviews));
  617. UIView* asUIView = (UIView*) self;
  618. if (auto* previewLayer = getPreviewLayer (self))
  619. previewLayer.frame = asUIView.bounds;
  620. }
  621. static AVPlayerLayer* getPreviewLayer (id self)
  622. {
  623. UIView* asUIView = (UIView*) self;
  624. if (asUIView.layer.sublayers != nil && [asUIView.layer.sublayers count] > 0)
  625. if ([asUIView.layer.sublayers[0] isKindOfClass: [AVPlayerLayer class]])
  626. return (AVPlayerLayer*) asUIView.layer.sublayers[0];
  627. return nil;
  628. }
  629. };
  630. //==============================================================================
  631. std::unique_ptr<AVPlayerViewController, NSObjectDeleter> playerViewController;
  632. std::unique_ptr<UIView, NSObjectDeleter> playerView;
  633. std::unique_ptr<AVPlayerLayer, NSObjectDeleter> playerLayer;
  634. };
  635. #endif
  636. //==============================================================================
  637. VideoComponent& owner;
  638. PlayerController playerController;
  639. std::function<void (const URL&, Result)> loadFinishedCallback;
  640. double playSpeedMult = 1.0;
  641. static double toSeconds (const CMTime& t) noexcept
  642. {
  643. return t.timescale != 0 ? (t.value / (double) t.timescale) : 0.0;
  644. }
  645. void playerPreparationFinished (const URL& url, Result r)
  646. {
  647. owner.resized();
  648. loadFinishedCallback (url, r);
  649. loadFinishedCallback = nullptr;
  650. }
  651. void errorOccurred (const String& errorMessage)
  652. {
  653. if (owner.onErrorOccurred != nullptr)
  654. owner.onErrorOccurred (errorMessage);
  655. }
  656. void playbackStarted()
  657. {
  658. if (owner.onPlaybackStarted != nullptr)
  659. owner.onPlaybackStarted();
  660. }
  661. void playbackStopped()
  662. {
  663. if (owner.onPlaybackStopped != nullptr)
  664. owner.onPlaybackStopped();
  665. }
  666. void playbackReachedEndTime()
  667. {
  668. stop();
  669. setPosition (0.0);
  670. }
  671. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  672. };