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.

834 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 ! approximatelyEqual (getSpeed(), 0.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 (approximatelyEqual (oldRate, 0.0f) && ! approximatelyEqual (newRate, 0.0f))
  186. owner.playbackStarted();
  187. else if (! approximatelyEqual (oldRate, 0.0f) && approximatelyEqual (newRate, 0.0f))
  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. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
  240. asset.reset ([[AVURLAsset alloc] initWithURL: nsUrl options: nil]);
  241. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  242. [asset.get() loadValuesAsynchronouslyForKeys: assetKeys.get()
  243. completionHandler: ^() { checkAllKeysReadyFor (asset.get(), url); }];
  244. }
  245. private:
  246. //==============================================================================
  247. struct JucePlayerItemPreparationStatusObserverClass : public ObjCClass<NSObject>
  248. {
  249. JucePlayerItemPreparationStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemStatusObserverClass_")
  250. {
  251. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  252. addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged);
  253. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  254. addIvar<PlayerAsyncInitialiser*> ("owner");
  255. registerClass();
  256. }
  257. //==============================================================================
  258. static PlayerAsyncInitialiser& getOwner (id self) { return *getIvar<PlayerAsyncInitialiser*> (self, "owner"); }
  259. static void setOwner (id self, PlayerAsyncInitialiser* p) { object_setInstanceVariable (self, "owner", p); }
  260. private:
  261. static void valueChanged (id self, SEL, NSString*, id object,
  262. NSDictionary<NSString*, id>* change, void* context)
  263. {
  264. auto& owner = getOwner (self);
  265. if (context == &owner)
  266. {
  267. auto* playerItem = (AVPlayerItem*) object;
  268. auto* urlAsset = (AVURLAsset*) playerItem.asset;
  269. URL url (nsStringToJuce (urlAsset.URL.absoluteString));
  270. auto oldStatus = [[change objectForKey: NSKeyValueChangeOldKey] intValue];
  271. auto newStatus = [[change objectForKey: NSKeyValueChangeNewKey] intValue];
  272. // Ignore spurious notifications
  273. if (oldStatus == newStatus)
  274. return;
  275. if (newStatus == AVPlayerItemStatusFailed)
  276. {
  277. auto errorMessage = playerItem.error != nil
  278. ? nsStringToJuce (playerItem.error.localizedDescription)
  279. : String();
  280. owner.notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
  281. }
  282. else if (newStatus == AVPlayerItemStatusReadyToPlay)
  283. {
  284. owner.notifyOwnerPreparationFinished (url, Result::ok(), owner.player.get());
  285. }
  286. else
  287. {
  288. jassertfalse;
  289. }
  290. }
  291. }
  292. };
  293. //==============================================================================
  294. PlayerControllerBase& owner;
  295. std::unique_ptr<AVURLAsset, NSObjectDeleter> asset;
  296. std::unique_ptr<NSArray<NSString*>, NSObjectDeleter> assetKeys;
  297. std::unique_ptr<AVPlayerItem, NSObjectDeleter> playerItem;
  298. std::unique_ptr<NSObject, NSObjectDeleter> playerItemPreparationStatusObserver;
  299. std::unique_ptr<AVPlayer, NSObjectDeleter> player;
  300. //==============================================================================
  301. void checkAllKeysReadyFor (AVAsset* assetToCheck, const URL& url)
  302. {
  303. NSError* error = nil;
  304. [[maybe_unused]] int successCount = 0;
  305. for (NSString* key : assetKeys.get())
  306. {
  307. switch ([assetToCheck statusOfValueForKey: key error: &error])
  308. {
  309. case AVKeyValueStatusLoaded:
  310. {
  311. ++successCount;
  312. break;
  313. }
  314. case AVKeyValueStatusCancelled:
  315. {
  316. notifyOwnerPreparationFinished (url, Result::fail ("Loading cancelled"), nullptr);
  317. return;
  318. }
  319. case AVKeyValueStatusFailed:
  320. {
  321. auto errorMessage = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  322. notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
  323. return;
  324. }
  325. case AVKeyValueStatusUnknown:
  326. case AVKeyValueStatusLoading:
  327. default:
  328. break;
  329. }
  330. }
  331. jassert (successCount == (int) [assetKeys.get() count]);
  332. preparePlayerItem();
  333. }
  334. void preparePlayerItem()
  335. {
  336. playerItem.reset ([[AVPlayerItem alloc] initWithAsset: asset.get()]);
  337. attachPreparationStatusObserver();
  338. player.reset ([[AVPlayer alloc] initWithPlayerItem: playerItem.get()]);
  339. }
  340. //==============================================================================
  341. void attachPreparationStatusObserver()
  342. {
  343. [playerItem.get() addObserver: playerItemPreparationStatusObserver.get()
  344. forKeyPath: nsStringLiteral ("status")
  345. options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
  346. context: this];
  347. }
  348. void detachPreparationStatusObserver()
  349. {
  350. if (playerItem != nullptr && playerItemPreparationStatusObserver != nullptr)
  351. {
  352. [playerItem.get() removeObserver: playerItemPreparationStatusObserver.get()
  353. forKeyPath: nsStringLiteral ("status")
  354. context: this];
  355. }
  356. }
  357. //==============================================================================
  358. void notifyOwnerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
  359. {
  360. MessageManager::callAsync ([url, preparedPlayer, r,
  361. safeThis = WeakReference<PlayerAsyncInitialiser> { this }]() mutable
  362. {
  363. if (safeThis != nullptr)
  364. safeThis->owner.playerPreparationFinished (url, r, preparedPlayer);
  365. });
  366. }
  367. JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerAsyncInitialiser)
  368. };
  369. //==============================================================================
  370. Pimpl& owner;
  371. bool useNativeControls;
  372. PlayerAsyncInitialiser playerAsyncInitialiser;
  373. std::unique_ptr<NSObject, NSObjectDeleter> playerStatusObserver;
  374. std::unique_ptr<NSObject, NSObjectDeleter> playerItemPlaybackStatusObserver;
  375. //==============================================================================
  376. PlayerControllerBase (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  377. : owner (ownerToUse),
  378. useNativeControls (useNativeControlsIfAvailable),
  379. playerAsyncInitialiser (*this)
  380. {
  381. static JucePlayerStatusObserverClass playerObserverClass;
  382. playerStatusObserver.reset ([playerObserverClass.createInstance() init]);
  383. JucePlayerStatusObserverClass::setOwner (playerStatusObserver.get(), this);
  384. static JucePlayerItemPlaybackStatusObserverClass itemObserverClass;
  385. playerItemPlaybackStatusObserver.reset ([itemObserverClass.createInstance() init]);
  386. JucePlayerItemPlaybackStatusObserverClass::setOwner (playerItemPlaybackStatusObserver.get(), this);
  387. }
  388. //==============================================================================
  389. void attachPlayerStatusObserver()
  390. {
  391. [crtp().getPlayer() addObserver: playerStatusObserver.get()
  392. forKeyPath: nsStringLiteral ("rate")
  393. options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
  394. context: this];
  395. [crtp().getPlayer() addObserver: playerStatusObserver.get()
  396. forKeyPath: nsStringLiteral ("status")
  397. options: NSKeyValueObservingOptionNew
  398. context: this];
  399. playerStatusObserverAttached = true;
  400. }
  401. void detachPlayerStatusObserver()
  402. {
  403. if (crtp().getPlayer() != nullptr && playerStatusObserver != nullptr)
  404. {
  405. [crtp().getPlayer() removeObserver: playerStatusObserver.get()
  406. forKeyPath: nsStringLiteral ("rate")
  407. context: this];
  408. [crtp().getPlayer() removeObserver: playerStatusObserver.get()
  409. forKeyPath: nsStringLiteral ("status")
  410. context: this];
  411. }
  412. playerStatusObserverAttached = false;
  413. }
  414. void attachPlaybackObserver()
  415. {
  416. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  417. [[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
  418. selector: @selector (processNotification:)
  419. name: AVPlayerItemDidPlayToEndTimeNotification
  420. object: [crtp().getPlayer() currentItem]];
  421. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  422. playbackObserverAttached = true;
  423. }
  424. void detachPlaybackObserver()
  425. {
  426. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  427. [[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
  428. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  429. playbackObserverAttached = false;
  430. }
  431. private:
  432. //==============================================================================
  433. Derived& crtp() { return static_cast<Derived&> (*this); }
  434. //==============================================================================
  435. void playerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
  436. {
  437. if (preparedPlayer != nil)
  438. crtp().setPlayer (preparedPlayer);
  439. owner.playerPreparationFinished (url, r);
  440. }
  441. void playbackReachedEndTime()
  442. {
  443. MessageManager::callAsync ([safeThis = WeakReference<PlayerControllerBase> { this }]() mutable
  444. {
  445. if (safeThis != nullptr)
  446. safeThis->owner.playbackReachedEndTime();
  447. });
  448. }
  449. //==============================================================================
  450. void errorOccurred()
  451. {
  452. auto errorMessage = (crtp().getPlayer() != nil && crtp().getPlayer().error != nil)
  453. ? nsStringToJuce (crtp().getPlayer().error.localizedDescription)
  454. : String();
  455. owner.errorOccurred (errorMessage);
  456. }
  457. void playbackStarted()
  458. {
  459. owner.playbackStarted();
  460. }
  461. void playbackStopped()
  462. {
  463. owner.playbackStopped();
  464. }
  465. bool playerStatusObserverAttached = false, playbackObserverAttached = false;
  466. JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerControllerBase)
  467. };
  468. #if JUCE_MAC
  469. //==============================================================================
  470. class PlayerController : public PlayerControllerBase<PlayerController>
  471. {
  472. public:
  473. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  474. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  475. {
  476. wrappedPlayer = [&]() -> std::unique_ptr<WrappedPlayer>
  477. {
  478. if (@available (macOS 10.9, *))
  479. if (useNativeControls)
  480. return std::make_unique<WrappedPlayerView>();
  481. return std::make_unique<WrappedPlayerLayer>();
  482. }();
  483. }
  484. NSView* getView()
  485. {
  486. return wrappedPlayer->getView();
  487. }
  488. Result load (NSURL* url)
  489. {
  490. if (auto player = [AVPlayer playerWithURL: url])
  491. {
  492. setPlayer (player);
  493. return Result::ok();
  494. }
  495. return Result::fail ("Couldn't open movie");
  496. }
  497. void loadAsync (URL url)
  498. {
  499. playerAsyncInitialiser.loadAsync (url);
  500. }
  501. void close() { setPlayer (nil); }
  502. void setPlayer (AVPlayer* player)
  503. {
  504. detachPlayerStatusObserver();
  505. detachPlaybackObserver();
  506. wrappedPlayer->setPlayer (player);
  507. if (player != nil)
  508. {
  509. attachPlayerStatusObserver();
  510. attachPlaybackObserver();
  511. }
  512. }
  513. AVPlayer* getPlayer() const
  514. {
  515. return wrappedPlayer->getPlayer();
  516. }
  517. private:
  518. struct WrappedPlayer
  519. {
  520. virtual ~WrappedPlayer() = default;
  521. virtual NSView* getView() const = 0;
  522. virtual AVPlayer* getPlayer() const = 0;
  523. virtual void setPlayer (AVPlayer*) = 0;
  524. };
  525. class WrappedPlayerLayer : public WrappedPlayer
  526. {
  527. public:
  528. WrappedPlayerLayer() { [view.get() setLayer: playerLayer.get()]; }
  529. NSView* getView() const override { return view.get(); }
  530. AVPlayer* getPlayer() const override { return [playerLayer.get() player]; }
  531. void setPlayer (AVPlayer* player) override { [playerLayer.get() setPlayer: player]; }
  532. private:
  533. NSUniquePtr<NSView> view { [[NSView alloc] init] };
  534. NSUniquePtr<AVPlayerLayer> playerLayer { [[AVPlayerLayer alloc] init] };
  535. };
  536. class API_AVAILABLE (macos (10.9)) WrappedPlayerView : public WrappedPlayer
  537. {
  538. public:
  539. WrappedPlayerView() = default;
  540. NSView* getView() const override { return playerView.get(); }
  541. AVPlayer* getPlayer() const override { return [playerView.get() player]; }
  542. void setPlayer (AVPlayer* player) override { [playerView.get() setPlayer: player]; }
  543. private:
  544. NSUniquePtr<AVPlayerView> playerView { [[AVPlayerView alloc] init] };
  545. };
  546. std::unique_ptr<WrappedPlayer> wrappedPlayer;
  547. };
  548. #else
  549. //==============================================================================
  550. class PlayerController : public PlayerControllerBase<PlayerController>
  551. {
  552. public:
  553. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  554. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  555. {
  556. if (useNativeControls)
  557. {
  558. playerViewController.reset ([[AVPlayerViewController alloc] init]);
  559. }
  560. else
  561. {
  562. static JuceVideoViewerClass cls;
  563. playerView.reset ([cls.createInstance() init]);
  564. playerLayer.reset ([[AVPlayerLayer alloc] init]);
  565. [playerView.get().layer addSublayer: playerLayer.get()];
  566. }
  567. }
  568. UIView* getView()
  569. {
  570. if (useNativeControls)
  571. return [playerViewController.get() view];
  572. // Should call getView() only once.
  573. jassert (playerView != nil);
  574. return playerView.release();
  575. }
  576. Result load (NSURL*)
  577. {
  578. jassertfalse;
  579. return Result::fail ("Synchronous loading is not supported on iOS, use loadAsync()");
  580. }
  581. void loadAsync (URL url)
  582. {
  583. playerAsyncInitialiser.loadAsync (url);
  584. }
  585. void close() { setPlayer (nil); }
  586. AVPlayer* getPlayer() const
  587. {
  588. if (useNativeControls)
  589. return [playerViewController.get() player];
  590. return [playerLayer.get() player];
  591. }
  592. void setPlayer (AVPlayer* playerToUse)
  593. {
  594. detachPlayerStatusObserver();
  595. detachPlaybackObserver();
  596. if (useNativeControls)
  597. [playerViewController.get() setPlayer: playerToUse];
  598. else
  599. [playerLayer.get() setPlayer: playerToUse];
  600. if (playerToUse != nil)
  601. {
  602. attachPlayerStatusObserver();
  603. attachPlaybackObserver();
  604. }
  605. }
  606. private:
  607. //==============================================================================
  608. struct JuceVideoViewerClass : public ObjCClass<UIView>
  609. {
  610. JuceVideoViewerClass() : ObjCClass<UIView> ("JuceVideoViewerClass_")
  611. {
  612. addMethod (@selector (layoutSubviews), layoutSubviews);
  613. registerClass();
  614. }
  615. private:
  616. static void layoutSubviews (id self, SEL)
  617. {
  618. sendSuperclassMessage<void> (self, @selector (layoutSubviews));
  619. UIView* asUIView = (UIView*) self;
  620. if (auto* previewLayer = getPreviewLayer (self))
  621. previewLayer.frame = asUIView.bounds;
  622. }
  623. static AVPlayerLayer* getPreviewLayer (id self)
  624. {
  625. UIView* asUIView = (UIView*) self;
  626. if (asUIView.layer.sublayers != nil && [asUIView.layer.sublayers count] > 0)
  627. if ([asUIView.layer.sublayers[0] isKindOfClass: [AVPlayerLayer class]])
  628. return (AVPlayerLayer*) asUIView.layer.sublayers[0];
  629. return nil;
  630. }
  631. };
  632. //==============================================================================
  633. std::unique_ptr<AVPlayerViewController, NSObjectDeleter> playerViewController;
  634. std::unique_ptr<UIView, NSObjectDeleter> playerView;
  635. std::unique_ptr<AVPlayerLayer, NSObjectDeleter> playerLayer;
  636. };
  637. #endif
  638. //==============================================================================
  639. VideoComponent& owner;
  640. PlayerController playerController;
  641. std::function<void (const URL&, Result)> loadFinishedCallback;
  642. double playSpeedMult = 1.0;
  643. static double toSeconds (const CMTime& t) noexcept
  644. {
  645. return t.timescale != 0 ? ((double) t.value / (double) t.timescale) : 0.0;
  646. }
  647. void playerPreparationFinished (const URL& url, Result r)
  648. {
  649. owner.resized();
  650. loadFinishedCallback (url, r);
  651. loadFinishedCallback = nullptr;
  652. }
  653. void errorOccurred (const String& errorMessage)
  654. {
  655. NullCheckedInvocation::invoke (owner.onErrorOccurred, errorMessage);
  656. }
  657. void playbackStarted()
  658. {
  659. NullCheckedInvocation::invoke (owner.onPlaybackStarted);
  660. }
  661. void playbackStopped()
  662. {
  663. NullCheckedInvocation::invoke (owner.onPlaybackStopped);
  664. }
  665. void playbackReachedEndTime()
  666. {
  667. stop();
  668. setPosition (0.0);
  669. }
  670. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  671. };