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.

820 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2018 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #if JUCE_MAC
  18. using Base = NSViewComponent;
  19. #else
  20. using Base = UIViewComponent;
  21. #endif
  22. struct VideoComponent::Pimpl : public Base
  23. {
  24. Pimpl (VideoComponent& ownerToUse, bool useNativeControlsIfAvailable)
  25. : owner (ownerToUse),
  26. playerController (*this, useNativeControlsIfAvailable)
  27. {
  28. setVisible (true);
  29. auto* view = playerController.getView();
  30. setView (view);
  31. #if JUCE_MAC
  32. [view setNextResponder: [view superview]];
  33. [view setWantsLayer: YES];
  34. #endif
  35. }
  36. ~Pimpl()
  37. {
  38. close();
  39. setView (nil);
  40. }
  41. Result load (const File& file)
  42. {
  43. auto r = load (createNSURLFromFile (file));
  44. if (r.wasOk())
  45. currentFile = file;
  46. return r;
  47. }
  48. Result load (const URL& url)
  49. {
  50. auto r = load ([NSURL URLWithString: juceStringToNS (url.toString (true))]);
  51. if (r.wasOk())
  52. currentURL = url;
  53. return r;
  54. }
  55. Result load (NSURL* url)
  56. {
  57. if (url != nil)
  58. {
  59. close();
  60. return playerController.load (url);
  61. }
  62. return Result::fail ("Couldn't open movie");
  63. }
  64. void loadAsync (const URL& url, std::function<void (const URL&, Result)> callback)
  65. {
  66. if (url.isEmpty())
  67. {
  68. jassertfalse;
  69. return;
  70. }
  71. currentURL = url;
  72. jassert (callback != nullptr);
  73. loadFinishedCallback = std::move (callback);
  74. playerController.loadAsync (url);
  75. }
  76. void close()
  77. {
  78. stop();
  79. playerController.close();
  80. currentFile = File();
  81. currentURL = {};
  82. }
  83. bool isOpen() const noexcept { return playerController.getPlayer() != nil; }
  84. bool isPlaying() const noexcept { return getSpeed() != 0; }
  85. void play() noexcept { [playerController.getPlayer() play]; setSpeed (playSpeedMult); }
  86. void stop() noexcept { [playerController.getPlayer() pause]; }
  87. void setPosition (double newPosition)
  88. {
  89. if (auto* p = playerController.getPlayer())
  90. {
  91. CMTime t = { (CMTimeValue) (100000.0 * newPosition),
  92. (CMTimeScale) 100000, kCMTimeFlags_Valid, {} };
  93. [p seekToTime: t
  94. toleranceBefore: kCMTimeZero
  95. toleranceAfter: kCMTimeZero];
  96. }
  97. }
  98. double getPosition() const
  99. {
  100. if (auto* p = playerController.getPlayer())
  101. return toSeconds ([p currentTime]);
  102. return 0.0;
  103. }
  104. void setSpeed (double newSpeed)
  105. {
  106. playSpeedMult = newSpeed;
  107. // Calling non 0.0 speed on a paused player would start it...
  108. if (isPlaying())
  109. [playerController.getPlayer() setRate: (float) playSpeedMult];
  110. }
  111. double getSpeed() const
  112. {
  113. if (auto* p = playerController.getPlayer())
  114. return [p rate];
  115. return 0.0;
  116. }
  117. Rectangle<int> getNativeSize() const
  118. {
  119. if (auto* p = playerController.getPlayer())
  120. {
  121. auto s = [[p currentItem] presentationSize];
  122. return { (int) s.width, (int) s.height };
  123. }
  124. return {};
  125. }
  126. double getDuration() const
  127. {
  128. if (auto* p = playerController.getPlayer())
  129. return toSeconds ([[p currentItem] duration]);
  130. return 0.0;
  131. }
  132. void setVolume (float newVolume)
  133. {
  134. [playerController.getPlayer() setVolume: newVolume];
  135. }
  136. float getVolume() const
  137. {
  138. if (auto* p = playerController.getPlayer())
  139. return [p volume];
  140. return 0.0f;
  141. }
  142. File currentFile;
  143. URL currentURL;
  144. private:
  145. //==============================================================================
  146. template <typename Derived>
  147. class PlayerControllerBase
  148. {
  149. public:
  150. ~PlayerControllerBase()
  151. {
  152. detachPlayerStatusObserver();
  153. detachPlaybackObserver();
  154. }
  155. protected:
  156. //==============================================================================
  157. struct JucePlayerStatusObserverClass : public ObjCClass<NSObject>
  158. {
  159. JucePlayerStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerStatusObserverClass_")
  160. {
  161. #pragma clang diagnostic push
  162. #pragma clang diagnostic ignored "-Wundeclared-selector"
  163. addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
  164. #pragma clang diagnostic pop
  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<NSKeyValueChangeKey, id>* change, void*)
  174. {
  175. auto& owner = getOwner (self);
  176. if ([keyPath isEqualToString: nsStringLiteral ("rate")])
  177. {
  178. auto oldRate = [change[NSKeyValueChangeOldKey] floatValue];
  179. auto newRate = [change[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[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. #pragma clang diagnostic push
  199. #pragma clang diagnostic ignored "-Wundeclared-selector"
  200. addMethod (@selector (processNotification:), notificationReceived, "v@:@");
  201. #pragma clang diagnostic pop
  202. addIvar<PlayerControllerBase*> ("owner");
  203. registerClass();
  204. }
  205. //==============================================================================
  206. static PlayerControllerBase& getOwner (id self) { return *getIvar<PlayerControllerBase*> (self, "owner"); }
  207. static void setOwner (id self, PlayerControllerBase* p) { object_setInstanceVariable (self, "owner", p); }
  208. private:
  209. static void notificationReceived (id self, SEL, NSNotification* notification)
  210. {
  211. if ([notification.name isEqualToString: AVPlayerItemDidPlayToEndTimeNotification])
  212. getOwner (self).playbackReachedEndTime();
  213. }
  214. };
  215. //==============================================================================
  216. class PlayerAsyncInitialiser
  217. {
  218. public:
  219. PlayerAsyncInitialiser (PlayerControllerBase& ownerToUse)
  220. : owner (ownerToUse),
  221. assetKeys ([[NSArray alloc] initWithObjects: nsStringLiteral ("duration"), nsStringLiteral ("tracks"),
  222. nsStringLiteral ("playable"), nil])
  223. {
  224. static JucePlayerItemPreparationStatusObserverClass cls;
  225. playerItemPreparationStatusObserver.reset ([cls.createInstance() init]);
  226. JucePlayerItemPreparationStatusObserverClass::setOwner (playerItemPreparationStatusObserver.get(), this);
  227. }
  228. ~PlayerAsyncInitialiser()
  229. {
  230. detachPreparationStatusObserver();
  231. }
  232. void loadAsync (URL url)
  233. {
  234. auto* nsUrl = [NSURL URLWithString: juceStringToNS (url.toString (true))];
  235. asset.reset ([[AVURLAsset alloc] initWithURL: nsUrl options: nil]);
  236. [asset.get() loadValuesAsynchronouslyForKeys: assetKeys.get()
  237. completionHandler: ^() { checkAllKeysReadyFor (asset.get(), url); }];
  238. }
  239. private:
  240. //==============================================================================
  241. struct JucePlayerItemPreparationStatusObserverClass : public ObjCClass<NSObject>
  242. {
  243. JucePlayerItemPreparationStatusObserverClass() : ObjCClass<NSObject> ("JucePlayerItemStatusObserverClass_")
  244. {
  245. #pragma clang diagnostic push
  246. #pragma clang diagnostic ignored "-Wundeclared-selector"
  247. addMethod (@selector (observeValueForKeyPath:ofObject:change:context:), valueChanged, "v@:@@@?");
  248. #pragma clang diagnostic pop
  249. addIvar<PlayerAsyncInitialiser*> ("owner");
  250. registerClass();
  251. }
  252. //==============================================================================
  253. static PlayerAsyncInitialiser& getOwner (id self) { return *getIvar<PlayerAsyncInitialiser*> (self, "owner"); }
  254. static void setOwner (id self, PlayerAsyncInitialiser* p) { object_setInstanceVariable (self, "owner", p); }
  255. private:
  256. static void valueChanged (id self, SEL, NSString*, id object,
  257. NSDictionary<NSKeyValueChangeKey, id>* change, void* context)
  258. {
  259. auto& owner = getOwner (self);
  260. if (context == &owner)
  261. {
  262. auto* playerItem = (AVPlayerItem*) object;
  263. auto* urlAsset = (AVURLAsset*) playerItem.asset;
  264. URL url (nsStringToJuce (urlAsset.URL.absoluteString));
  265. auto oldStatus = [change[NSKeyValueChangeOldKey] intValue];
  266. auto newStatus = [change[NSKeyValueChangeNewKey] intValue];
  267. // Ignore spurious notifications
  268. if (oldStatus == newStatus)
  269. return;
  270. if (newStatus == AVPlayerItemStatusFailed)
  271. {
  272. auto errorMessage = playerItem.error != nil
  273. ? nsStringToJuce (playerItem.error.localizedDescription)
  274. : String();
  275. owner.notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
  276. }
  277. else if (newStatus == AVPlayerItemStatusReadyToPlay)
  278. {
  279. owner.notifyOwnerPreparationFinished (url, Result::ok(), owner.player.release());
  280. }
  281. else
  282. {
  283. jassertfalse;
  284. }
  285. }
  286. }
  287. };
  288. //==============================================================================
  289. PlayerControllerBase& owner;
  290. std::unique_ptr<AVURLAsset, NSObjectDeleter> asset;
  291. std::unique_ptr<NSArray<NSString*>, NSObjectDeleter> assetKeys;
  292. std::unique_ptr<AVPlayerItem, NSObjectDeleter> playerItem;
  293. std::unique_ptr<NSObject, NSObjectDeleter> playerItemPreparationStatusObserver;
  294. std::unique_ptr<AVPlayer, NSObjectDeleter> player;
  295. //==============================================================================
  296. void checkAllKeysReadyFor (AVAsset* assetToCheck, const URL& url)
  297. {
  298. NSError* error = nil;
  299. int successCount = 0;
  300. for (NSString* key : assetKeys.get())
  301. {
  302. switch ([assetToCheck statusOfValueForKey: key error: &error])
  303. {
  304. case AVKeyValueStatusLoaded:
  305. {
  306. ++successCount;
  307. break;
  308. }
  309. case AVKeyValueStatusCancelled:
  310. {
  311. notifyOwnerPreparationFinished (url, Result::fail ("Loading cancelled"), nullptr);
  312. return;
  313. }
  314. case AVKeyValueStatusFailed:
  315. {
  316. auto errorMessage = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  317. notifyOwnerPreparationFinished (url, Result::fail (errorMessage), nullptr);
  318. return;
  319. }
  320. default:
  321. {}
  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. WeakReference<PlayerAsyncInitialiser> safeThis (this);
  354. MessageManager::callAsync ([safeThis, url, r, preparedPlayer]() 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. }
  393. void detachPlayerStatusObserver()
  394. {
  395. if (crtp().getPlayer() != nullptr && playerStatusObserver != nullptr)
  396. {
  397. [crtp().getPlayer() removeObserver: playerStatusObserver.get()
  398. forKeyPath: nsStringLiteral ("rate")
  399. context: this];
  400. [crtp().getPlayer() removeObserver: playerStatusObserver.get()
  401. forKeyPath: nsStringLiteral ("status")
  402. context: this];
  403. }
  404. }
  405. void attachPlaybackObserver()
  406. {
  407. #pragma clang diagnostic push
  408. #pragma clang diagnostic ignored "-Wundeclared-selector"
  409. [[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
  410. selector: @selector (processNotification:)
  411. name: AVPlayerItemDidPlayToEndTimeNotification
  412. object: [crtp().getPlayer() currentItem]];
  413. #pragma clang diagnostic pop
  414. }
  415. void detachPlaybackObserver()
  416. {
  417. #pragma clang diagnostic push
  418. #pragma clang diagnostic ignored "-Wundeclared-selector"
  419. [[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
  420. #pragma clang diagnostic pop
  421. }
  422. private:
  423. //==============================================================================
  424. Derived& crtp() { return static_cast<Derived&> (*this); }
  425. //==============================================================================
  426. void playerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
  427. {
  428. if (preparedPlayer != nil)
  429. crtp().setPlayer (preparedPlayer);
  430. owner.playerPreparationFinished (url, r);
  431. }
  432. void playbackReachedEndTime()
  433. {
  434. WeakReference<PlayerControllerBase> safeThis (this);
  435. MessageManager::callAsync ([safeThis]() mutable
  436. {
  437. if (safeThis != nullptr)
  438. safeThis->owner.playbackReachedEndTime();
  439. });
  440. }
  441. //==============================================================================
  442. void errorOccurred()
  443. {
  444. auto errorMessage = (crtp().getPlayer() != nil && crtp().getPlayer().error != nil)
  445. ? nsStringToJuce (crtp().getPlayer().error.localizedDescription)
  446. : String();
  447. owner.errorOccurred (errorMessage);
  448. }
  449. void playbackStarted()
  450. {
  451. owner.playbackStarted();
  452. }
  453. void playbackStopped()
  454. {
  455. owner.playbackStopped();
  456. }
  457. JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerControllerBase)
  458. };
  459. #if JUCE_MAC
  460. //==============================================================================
  461. class PlayerController : public PlayerControllerBase<PlayerController>
  462. {
  463. public:
  464. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  465. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  466. {
  467. #if JUCE_32BIT
  468. // 32-bit builds don't have AVPlayerView, so need to use a layer
  469. useNativeControls = false;
  470. #endif
  471. if (useNativeControls)
  472. {
  473. #if ! JUCE_32BIT
  474. playerView = [[AVPlayerView alloc] init];
  475. #endif
  476. }
  477. else
  478. {
  479. view = [[NSView alloc] init];
  480. playerLayer = [[AVPlayerLayer alloc] init];
  481. [view setLayer: playerLayer];
  482. }
  483. }
  484. ~PlayerController()
  485. {
  486. #if JUCE_32BIT
  487. [view release];
  488. [playerLayer release];
  489. #else
  490. [playerView release];
  491. #endif
  492. }
  493. NSView* getView()
  494. {
  495. #if ! JUCE_32BIT
  496. if (useNativeControls)
  497. return playerView;
  498. #endif
  499. return view;
  500. }
  501. Result load (NSURL* url)
  502. {
  503. if (auto* player = [AVPlayer playerWithURL: url])
  504. {
  505. setPlayer (player);
  506. return Result::ok();
  507. }
  508. return Result::fail ("Couldn't open movie");
  509. }
  510. void loadAsync (URL url)
  511. {
  512. playerAsyncInitialiser.loadAsync (url);
  513. }
  514. void close() { setPlayer (nil); }
  515. void setPlayer (AVPlayer* player)
  516. {
  517. #if ! JUCE_32BIT
  518. if (useNativeControls)
  519. {
  520. [playerView setPlayer: player];
  521. attachPlayerStatusObserver();
  522. attachPlaybackObserver();
  523. return;
  524. }
  525. #endif
  526. [playerLayer setPlayer: player];
  527. attachPlayerStatusObserver();
  528. attachPlaybackObserver();
  529. }
  530. AVPlayer* getPlayer() const
  531. {
  532. #if ! JUCE_32BIT
  533. if (useNativeControls)
  534. return [playerView player];
  535. #endif
  536. return [playerLayer player];
  537. }
  538. private:
  539. NSView* view = nil;
  540. AVPlayerLayer* playerLayer = nil;
  541. #if ! JUCE_32BIT
  542. // 32-bit builds don't have AVPlayerView
  543. AVPlayerView* playerView = nil;
  544. #endif
  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. if (useNativeControls)
  593. [playerViewController.get() setPlayer: playerToUse];
  594. else
  595. [playerLayer.get() setPlayer: playerToUse];
  596. attachPlayerStatusObserver();
  597. attachPlaybackObserver();
  598. }
  599. private:
  600. //==============================================================================
  601. struct JuceVideoViewerClass : public ObjCClass<UIView>
  602. {
  603. JuceVideoViewerClass() : ObjCClass<UIView> ("JuceVideoViewerClass_")
  604. {
  605. addMethod (@selector (layoutSubviews), layoutSubviews, "v@:");
  606. registerClass();
  607. }
  608. private:
  609. static void layoutSubviews (id self, SEL)
  610. {
  611. sendSuperclassMessage (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. };