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.

828 lines
30KB

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