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) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #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. detachPlayerStatusObserver();
  154. detachPlaybackObserver();
  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, "v@:@@@?");
  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[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. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  199. addMethod (@selector (processNotification:), notificationReceived, "v@:@");
  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, "v@:@@@?");
  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[NSKeyValueChangeOldKey] intValue];
  264. auto newStatus = [change[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. }
  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. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  408. [[NSNotificationCenter defaultCenter] addObserver: playerItemPlaybackStatusObserver.get()
  409. selector: @selector (processNotification:)
  410. name: AVPlayerItemDidPlayToEndTimeNotification
  411. object: [crtp().getPlayer() currentItem]];
  412. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  413. }
  414. void detachPlaybackObserver()
  415. {
  416. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  417. [[NSNotificationCenter defaultCenter] removeObserver: playerItemPlaybackStatusObserver.get()];
  418. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  419. }
  420. private:
  421. //==============================================================================
  422. Derived& crtp() { return static_cast<Derived&> (*this); }
  423. //==============================================================================
  424. void playerPreparationFinished (const URL& url, Result r, AVPlayer* preparedPlayer)
  425. {
  426. if (preparedPlayer != nil)
  427. crtp().setPlayer (preparedPlayer);
  428. owner.playerPreparationFinished (url, r);
  429. }
  430. void playbackReachedEndTime()
  431. {
  432. MessageManager::callAsync ([safeThis = WeakReference<PlayerControllerBase> { this }]() mutable
  433. {
  434. if (safeThis != nullptr)
  435. safeThis->owner.playbackReachedEndTime();
  436. });
  437. }
  438. //==============================================================================
  439. void errorOccurred()
  440. {
  441. auto errorMessage = (crtp().getPlayer() != nil && crtp().getPlayer().error != nil)
  442. ? nsStringToJuce (crtp().getPlayer().error.localizedDescription)
  443. : String();
  444. owner.errorOccurred (errorMessage);
  445. }
  446. void playbackStarted()
  447. {
  448. owner.playbackStarted();
  449. }
  450. void playbackStopped()
  451. {
  452. owner.playbackStopped();
  453. }
  454. JUCE_DECLARE_WEAK_REFERENCEABLE (PlayerControllerBase)
  455. };
  456. #if JUCE_MAC
  457. //==============================================================================
  458. class PlayerController : public PlayerControllerBase<PlayerController>
  459. {
  460. public:
  461. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  462. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  463. {
  464. #if JUCE_32BIT
  465. // 32-bit builds don't have AVPlayerView, so need to use a layer
  466. useNativeControls = false;
  467. #endif
  468. if (useNativeControls)
  469. {
  470. #if ! JUCE_32BIT
  471. playerView = [[AVPlayerView alloc] init];
  472. #endif
  473. }
  474. else
  475. {
  476. view = [[NSView alloc] init];
  477. playerLayer = [[AVPlayerLayer alloc] init];
  478. [view setLayer: playerLayer];
  479. }
  480. }
  481. ~PlayerController()
  482. {
  483. #if JUCE_32BIT
  484. [view release];
  485. [playerLayer release];
  486. #else
  487. [playerView release];
  488. #endif
  489. }
  490. NSView* getView()
  491. {
  492. #if ! JUCE_32BIT
  493. if (useNativeControls)
  494. return playerView;
  495. #endif
  496. return view;
  497. }
  498. Result load (NSURL* url)
  499. {
  500. if (auto player = [AVPlayer playerWithURL: url])
  501. {
  502. setPlayer (player);
  503. return Result::ok();
  504. }
  505. return Result::fail ("Couldn't open movie");
  506. }
  507. void loadAsync (URL url)
  508. {
  509. playerAsyncInitialiser.loadAsync (url);
  510. }
  511. void close() { setPlayer (nil); }
  512. void setPlayer (AVPlayer* player)
  513. {
  514. #if ! JUCE_32BIT
  515. if (useNativeControls)
  516. [playerView setPlayer: player];
  517. else
  518. #endif
  519. [playerLayer setPlayer: player];
  520. if (player != nil)
  521. {
  522. attachPlayerStatusObserver();
  523. attachPlaybackObserver();
  524. }
  525. else
  526. {
  527. detachPlayerStatusObserver();
  528. detachPlaybackObserver();
  529. }
  530. }
  531. AVPlayer* getPlayer() const
  532. {
  533. #if ! JUCE_32BIT
  534. if (useNativeControls)
  535. return [playerView player];
  536. #endif
  537. return [playerLayer player];
  538. }
  539. private:
  540. NSView* view = nil;
  541. AVPlayerLayer* playerLayer = nil;
  542. #if ! JUCE_32BIT
  543. // 32-bit builds don't have AVPlayerView
  544. AVPlayerView* playerView = nil;
  545. #endif
  546. };
  547. #else
  548. //==============================================================================
  549. class PlayerController : public PlayerControllerBase<PlayerController>
  550. {
  551. public:
  552. PlayerController (Pimpl& ownerToUse, bool useNativeControlsIfAvailable)
  553. : PlayerControllerBase (ownerToUse, useNativeControlsIfAvailable)
  554. {
  555. if (useNativeControls)
  556. {
  557. playerViewController.reset ([[AVPlayerViewController alloc] init]);
  558. }
  559. else
  560. {
  561. static JuceVideoViewerClass cls;
  562. playerView.reset ([cls.createInstance() init]);
  563. playerLayer.reset ([[AVPlayerLayer alloc] init]);
  564. [playerView.get().layer addSublayer: playerLayer.get()];
  565. }
  566. }
  567. UIView* getView()
  568. {
  569. if (useNativeControls)
  570. return [playerViewController.get() view];
  571. // Should call getView() only once.
  572. jassert (playerView != nil);
  573. return playerView.release();
  574. }
  575. Result load (NSURL*)
  576. {
  577. jassertfalse;
  578. return Result::fail ("Synchronous loading is not supported on iOS, use loadAsync()");
  579. }
  580. void loadAsync (URL url)
  581. {
  582. playerAsyncInitialiser.loadAsync (url);
  583. }
  584. void close() { setPlayer (nil); }
  585. AVPlayer* getPlayer() const
  586. {
  587. if (useNativeControls)
  588. return [playerViewController.get() player];
  589. return [playerLayer.get() player];
  590. }
  591. void setPlayer (AVPlayer* playerToUse)
  592. {
  593. if (useNativeControls)
  594. [playerViewController.get() setPlayer: playerToUse];
  595. else
  596. [playerLayer.get() setPlayer: playerToUse];
  597. attachPlayerStatusObserver();
  598. attachPlaybackObserver();
  599. }
  600. private:
  601. //==============================================================================
  602. struct JuceVideoViewerClass : public ObjCClass<UIView>
  603. {
  604. JuceVideoViewerClass() : ObjCClass<UIView> ("JuceVideoViewerClass_")
  605. {
  606. addMethod (@selector (layoutSubviews), layoutSubviews, "v@:");
  607. registerClass();
  608. }
  609. private:
  610. static void layoutSubviews (id self, SEL)
  611. {
  612. sendSuperclassMessage<void> (self, @selector (layoutSubviews));
  613. UIView* asUIView = (UIView*) self;
  614. if (auto* previewLayer = getPreviewLayer (self))
  615. previewLayer.frame = asUIView.bounds;
  616. }
  617. static AVPlayerLayer* getPreviewLayer (id self)
  618. {
  619. UIView* asUIView = (UIView*) self;
  620. if (asUIView.layer.sublayers != nil && [asUIView.layer.sublayers count] > 0)
  621. if ([asUIView.layer.sublayers[0] isKindOfClass: [AVPlayerLayer class]])
  622. return (AVPlayerLayer*) asUIView.layer.sublayers[0];
  623. return nil;
  624. }
  625. };
  626. //==============================================================================
  627. std::unique_ptr<AVPlayerViewController, NSObjectDeleter> playerViewController;
  628. std::unique_ptr<UIView, NSObjectDeleter> playerView;
  629. std::unique_ptr<AVPlayerLayer, NSObjectDeleter> playerLayer;
  630. };
  631. #endif
  632. //==============================================================================
  633. VideoComponent& owner;
  634. PlayerController playerController;
  635. std::function<void (const URL&, Result)> loadFinishedCallback;
  636. double playSpeedMult = 1.0;
  637. static double toSeconds (const CMTime& t) noexcept
  638. {
  639. return t.timescale != 0 ? (t.value / (double) t.timescale) : 0.0;
  640. }
  641. void playerPreparationFinished (const URL& url, Result r)
  642. {
  643. owner.resized();
  644. loadFinishedCallback (url, r);
  645. loadFinishedCallback = nullptr;
  646. }
  647. void errorOccurred (const String& errorMessage)
  648. {
  649. if (owner.onErrorOccurred != nullptr)
  650. owner.onErrorOccurred (errorMessage);
  651. }
  652. void playbackStarted()
  653. {
  654. if (owner.onPlaybackStarted != nullptr)
  655. owner.onPlaybackStarted();
  656. }
  657. void playbackStopped()
  658. {
  659. if (owner.onPlaybackStopped != nullptr)
  660. owner.onPlaybackStopped();
  661. }
  662. void playbackReachedEndTime()
  663. {
  664. stop();
  665. setPosition (0.0);
  666. }
  667. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  668. };