2017-01-11 05:43:43 -07:00
# import < React / RCTConvert . h >
2015-03-30 23:07:55 -06:00
# import "RCTVideo.h"
2017-01-11 05:43:43 -07:00
# import < React / RCTBridgeModule . h >
# import < React / RCTEventDispatcher . h >
# import < React / UIView + React . h >
2018-06-21 10:08:37 -06:00
# include < MediaAccessibility / MediaAccessibility . h >
# include < AVFoundation / AVFoundation . h >
2015-03-30 23:07:55 -06:00
2015-04-08 00:49:14 -06:00
static NSString * const statusKeyPath = @ "status" ;
2015-08-05 06:52:30 -06:00
static NSString * const playbackLikelyToKeepUpKeyPath = @ "playbackLikelyToKeepUp" ;
2016-02-18 17:45:09 -07:00
static NSString * const playbackBufferEmptyKeyPath = @ "playbackBufferEmpty" ;
2016-04-28 06:25:45 -06:00
static NSString * const readyForDisplayKeyPath = @ "readyForDisplay" ;
2016-04-28 06:29:09 -06:00
static NSString * const playbackRate = @ "rate" ;
2017-02-13 19:38:02 -07:00
static NSString * const timedMetadata = @ "timedMetadata" ;
2018-09-13 06:06:12 -06:00
static NSString * const externalPlaybackActive = @ "externalPlaybackActive" ;
2015-04-08 00:49:14 -06:00
2018-07-29 18:42:09 -06:00
static int const RCTVideoUnset = -1 ;
2018-08-08 16:37:18 -06:00
# ifdef DEBUG
# define DebugLog ( . . . ) NSLog ( __VA _ARGS __ )
# else
# define DebugLog ( . . . ) ( void ) 0
# endif
2015-03-30 23:07:55 -06:00
@ implementation RCTVideo
{
2015-04-07 16:24:49 -06:00
AVPlayer * _player ;
2015-04-08 00:49:14 -06:00
AVPlayerItem * _playerItem ;
2018-12-13 20:30:38 -07:00
NSDictionary * _source ;
2015-08-05 06:52:30 -06:00
BOOL _playerItemObserversSet ;
2016-02-18 17:45:09 -07:00
BOOL _playerBufferEmpty ;
2015-04-07 16:24:49 -06:00
AVPlayerLayer * _playerLayer ;
2018-07-09 12:20:32 -06:00
BOOL _playerLayerObserverSet ;
2018-10-07 21:24:50 -06:00
RCTVideoPlayerViewController * _playerViewController ;
2015-04-07 16:24:49 -06:00
NSURL * _videoURL ;
2018-02-28 09:42:49 -07:00
2015-04-07 16:24:49 -06:00
/ * Required to publish events * /
RCTEventDispatcher * _eventDispatcher ;
2016-05-17 01:37:11 -06:00
BOOL _playbackRateObserverRegistered ;
2018-09-13 06:06:12 -06:00
BOOL _isExternalPlaybackActiveObserverRegistered ;
2018-06-25 12:43:51 -06:00
BOOL _videoLoadStarted ;
2019-12-09 21:40:49 -07:00
BOOL _isRequestAds ;
2018-08-09 10:58:03 -06:00
2015-04-08 16:15:57 -06:00
bool _pendingSeek ;
float _pendingSeekTime ;
float _lastSeekTime ;
2018-02-28 09:42:49 -07:00
2015-04-07 16:24:49 -06:00
/ * For sending videoProgress events * /
2015-12-22 16:39:04 -07:00
Float64 _progressUpdateInterval ;
BOOL _controls ;
id _timeObserver ;
2018-02-28 09:42:49 -07:00
2015-04-07 21:38:16 -06:00
/ * Keep track of any modifiers , need to be applied after each play * /
float _volume ;
float _rate ;
2018-11-26 15:50:31 -07:00
float _maxBitRate ;
2018-10-29 10:53:52 -06:00
2019-08-22 02:10:39 -06:00
BOOL _automaticallyWaitsToMinimizeStalling ;
2015-04-07 21:38:16 -06:00
BOOL _muted ;
2015-05-10 17:01:25 -06:00
BOOL _paused ;
2015-06-16 02:07:50 -06:00
BOOL _repeat ;
2018-06-05 19:40:12 -06:00
BOOL _allowsExternalPlayback ;
2018-06-21 10:08:37 -06:00
NSArray * _textTracks ;
2018-06-02 03:24:13 -06:00
NSDictionary * _selectedTextTrack ;
2018-07-17 15:14:21 -06:00
NSDictionary * _selectedAudioTrack ;
2016-04-28 06:38:21 -06:00
BOOL _playbackStalled ;
2015-10-30 03:34:54 -06:00
BOOL _playInBackground ;
2016-04-29 05:55:34 -06:00
BOOL _playWhenInactive ;
2018-11-26 15:23:04 -07:00
BOOL _pictureInPicture ;
2017-04-20 12:10:06 -06:00
NSString * _ignoreSilentSwitch ;
2015-06-16 02:07:50 -06:00
NSString * _resizeMode ;
2018-08-02 11:32:50 -06:00
BOOL _fullscreen ;
2018-10-18 16:21:46 -06:00
BOOL _fullscreenAutorotate ;
2018-10-07 21:24:50 -06:00
NSString * _fullscreenOrientation ;
2016-04-01 06:55:23 -06:00
BOOL _fullscreenPlayerPresented ;
2018-11-06 07:38:28 -07:00
NSString * _filterName ;
2019-12-09 21:40:49 -07:00
NSString * _adTagUrl ;
2018-12-13 20:30:38 -07:00
BOOL _filterEnabled ;
2016-03-31 12:34:22 -06:00
UIViewController * _presentingViewController ;
2018-07-18 18:06:09 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
2018-03-01 15:38:38 -07:00
RCTVideoCache * _videoCache ;
2018-07-18 18:06:09 -06:00
# endif
2019-03-11 20:55:36 -06:00
# if TARGET_OS _IOS
void ( ^ __strong _Nonnull _restoreUserInterfaceForPIPStopCompletionHandler ) ( BOOL ) ;
AVPictureInPictureController * _pipController ;
# endif
2015-03-30 23:07:55 -06:00
}
2015-05-30 13:50:45 -06:00
- ( instancetype ) initWithEventDispatcher : ( RCTEventDispatcher * ) eventDispatcher
{
2015-03-30 23:07:55 -06:00
if ( ( self = [ super init ] ) ) {
2015-04-06 13:17:32 -06:00
_eventDispatcher = eventDispatcher ;
2019-08-30 08:01:19 -06:00
_automaticallyWaitsToMinimizeStalling = YES ;
2016-05-17 01:37:11 -06:00
_playbackRateObserverRegistered = NO ;
2018-09-13 06:06:12 -06:00
_isExternalPlaybackActiveObserverRegistered = NO ;
2016-04-28 06:38:21 -06:00
_playbackStalled = NO ;
2015-04-08 11:34:27 -06:00
_rate = 1.0 ;
_volume = 1.0 ;
2015-06-16 22:14:14 -06:00
_resizeMode = @ "AVLayerVideoGravityResizeAspectFill" ;
2018-10-18 16:21:46 -06:00
_fullscreenAutorotate = YES ;
2018-10-09 17:01:41 -06:00
_fullscreenOrientation = @ "all" ;
2015-04-08 16:15:57 -06:00
_pendingSeek = false ;
_pendingSeekTime = 0.0 f ;
_lastSeekTime = 0.0 f ;
2015-12-22 16:39:04 -07:00
_progressUpdateInterval = 250 ;
_controls = NO ;
2016-02-18 17:45:09 -07:00
_playerBufferEmpty = YES ;
2016-06-01 23:58:38 -06:00
_playInBackground = false ;
2018-06-10 10:04:13 -06:00
_allowsExternalPlayback = YES ;
2016-04-29 05:55:34 -06:00
_playWhenInactive = false ;
2019-03-11 20:55:36 -06:00
_pictureInPicture = false ;
2019-12-09 21:40:49 -07:00
_isRequestAds = false ;
2017-04-20 12:10:06 -06:00
_ignoreSilentSwitch = @ "inherit" ; // inherit , ignore , obey
2019-03-11 20:55:36 -06:00
# if TARGET_OS _IOS
_restoreUserInterfaceForPIPStopCompletionHandler = NULL ;
# endif
2018-07-18 18:06:09 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
2018-03-01 15:38:38 -07:00
_videoCache = [ RCTVideoCache sharedInstance ] ;
2018-07-18 18:06:09 -06:00
# endif
2015-06-25 16:40:46 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
2015-08-05 06:52:30 -06:00
selector : @ selector ( applicationWillResignActive : )
name : UIApplicationWillResignActiveNotification
object : nil ] ;
2018-02-28 09:42:49 -07:00
2016-04-29 01:46:28 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationDidEnterBackground : )
name : UIApplicationDidEnterBackgroundNotification
object : nil ] ;
2018-02-28 09:42:49 -07:00
2015-06-25 16:40:46 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
2015-08-05 06:52:30 -06:00
selector : @ selector ( applicationWillEnterForeground : )
name : UIApplicationWillEnterForegroundNotification
object : nil ] ;
2018-08-09 10:58:03 -06:00
2018-07-12 22:48:58 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( audioRouteChanged : )
name : AVAudioSessionRouteChangeNotification
object : nil ] ;
2015-03-30 23:07:55 -06:00
}
2018-02-28 09:42:49 -07:00
2015-03-30 23:07:55 -06:00
return self ;
}
2018-10-07 21:24:50 -06:00
- ( RCTVideoPlayerViewController * ) createPlayerViewController : ( AVPlayer * ) player
withPlayerItem : ( AVPlayerItem * ) playerItem {
RCTVideoPlayerViewController * viewController = [ [ RCTVideoPlayerViewController alloc ] init ] ;
viewController . showsPlaybackControls = YES ;
viewController . rctDelegate = self ;
viewController . preferredOrientation = _fullscreenOrientation ;
2019-12-09 21:40:49 -07:00
self . contentPlayhead = [ [ IMAAVPlayerContentPlayhead alloc ] initWithAVPlayer : player ] ;
[ self setupAdsLoader ] ;
2018-09-04 16:44:19 -06:00
2018-10-07 21:24:50 -06:00
viewController . view . frame = self . bounds ;
viewController . player = player ;
return viewController ;
2015-12-22 16:39:04 -07:00
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Get the duration for a AVPlayerItem .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * /
- ( CMTime ) playerItemDuration
{
2018-02-28 09:42:49 -07:00
AVPlayerItem * playerItem = [ _player currentItem ] ;
if ( playerItem . status = = AVPlayerItemStatusReadyToPlay )
{
return ( [ playerItem duration ] ) ;
}
return ( kCMTimeInvalid ) ;
2015-12-22 16:39:04 -07:00
}
2016-09-06 20:23:13 -06:00
- ( CMTimeRange ) playerItemSeekableTimeRange
{
2018-02-28 09:42:49 -07:00
AVPlayerItem * playerItem = [ _player currentItem ] ;
if ( playerItem . status = = AVPlayerItemStatusReadyToPlay )
{
return [ playerItem seekableTimeRanges ] . firstObject . CMTimeRangeValue ;
}
return ( kCMTimeRangeZero ) ;
2016-09-06 20:23:13 -06:00
}
2017-09-28 19:37:26 -06:00
- ( void ) addPlayerTimeObserver
{
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000 ;
2018-05-29 17:11:15 -06:00
// @ see endScrubbing in AVPlayerDemoPlaybackViewController . m
// of https : // developer . apple . com / library / ios / samplecode / AVPlayerDemo / Introduction / Intro . html
2017-09-28 19:37:26 -06:00
__weak RCTVideo * weakSelf = self ;
_timeObserver = [ _player addPeriodicTimeObserverForInterval : CMTimeMakeWithSeconds ( progressUpdateIntervalMS , NSEC_PER _SEC )
queue : NULL
usingBlock : ^ ( CMTime time ) { [ weakSelf sendProgressUpdate ] ; }
] ;
}
2015-12-22 16:39:04 -07:00
/ * Cancels the previously registered time observer . * /
- ( void ) removePlayerTimeObserver
{
2018-02-28 09:42:49 -07:00
if ( _timeObserver )
{
[ _player removeTimeObserver : _timeObserver ] ;
_timeObserver = nil ;
}
2015-12-22 16:39:04 -07:00
}
# pragma mark - Progress
2015-06-26 12:41:41 -06:00
- ( void ) dealloc
{
2015-08-05 06:52:30 -06:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
2017-06-09 15:31:46 -06:00
[ self removePlayerLayer ] ;
2018-03-19 13:56:55 -06:00
[ self removePlayerItemObservers ] ;
2016-08-04 06:32:04 -06:00
[ _player removeObserver : self forKeyPath : playbackRate context : nil ] ;
2019-02-14 08:34:14 -07:00
[ _player removeObserver : self forKeyPath : externalPlaybackActive context : nil ] ;
2015-06-26 12:41:41 -06:00
}
2015-06-25 16:40:46 -06:00
# pragma mark - App lifecycle handlers
- ( void ) applicationWillResignActive : ( NSNotification * ) notification
{
2016-04-29 05:55:34 -06:00
if ( _playInBackground || _playWhenInactive || _paused ) return ;
2018-02-28 09:42:49 -07:00
2016-04-29 05:55:34 -06:00
[ _player pause ] ;
[ _player setRate : 0.0 ] ;
2015-06-25 16:40:46 -06:00
}
2016-04-29 01:46:28 -06:00
- ( void ) applicationDidEnterBackground : ( NSNotification * ) notification
{
2016-06-01 23:58:38 -06:00
if ( _playInBackground ) {
// Needed to play sound in background . See https : // developer . apple . com / library / ios / qa / qa1668 / _index . html
[ _playerLayer setPlayer : nil ] ;
2019-06-10 11:00:03 -06:00
[ _playerViewController setPlayer : nil ] ;
2016-06-01 23:58:38 -06:00
}
2016-04-29 01:46:28 -06:00
}
2015-06-25 16:40:46 -06:00
- ( void ) applicationWillEnterForeground : ( NSNotification * ) notification
{
[ self applyModifiers ] ;
2016-04-28 07:06:22 -06:00
if ( _playInBackground ) {
[ _playerLayer setPlayer : _player ] ;
2019-06-10 11:00:03 -06:00
[ _playerViewController setPlayer : _player ] ;
2016-04-28 07:06:22 -06:00
}
2015-06-25 16:40:46 -06:00
}
2018-07-12 22:48:58 -06:00
# pragma mark - Audio events
- ( void ) audioRouteChanged : ( NSNotification * ) notification
{
NSNumber * reason = [ [ notification userInfo ] objectForKey : AVAudioSessionRouteChangeReasonKey ] ;
NSNumber * previousRoute = [ [ notification userInfo ] objectForKey : AVAudioSessionRouteChangePreviousRouteKey ] ;
if ( reason . unsignedIntValue = = AVAudioSessionRouteChangeReasonOldDeviceUnavailable ) {
2018-09-04 16:44:19 -06:00
self . onVideoAudioBecomingNoisy ( @ { @ "target" : self . reactTag } ) ;
2018-07-12 22:48:58 -06:00
}
}
2015-04-08 09:46:13 -06:00
# pragma mark - Progress
2015-05-30 13:50:45 -06:00
- ( void ) sendProgressUpdate
{
2018-02-28 09:42:49 -07:00
AVPlayerItem * video = [ _player currentItem ] ;
if ( video = = nil || video . status ! = AVPlayerItemStatusReadyToPlay ) {
return ;
}
CMTime playerDuration = [ self playerItemDuration ] ;
if ( CMTIME_IS _INVALID ( playerDuration ) ) {
return ;
}
CMTime currentTime = _player . currentTime ;
const Float64 duration = CMTimeGetSeconds ( playerDuration ) ;
const Float64 currentTimeSecs = CMTimeGetSeconds ( currentTime ) ;
[ [ NSNotificationCenter defaultCenter ] postNotificationName : @ "RCTVideo_progress" object : nil userInfo : @ { @ "progress" : [ NSNumber numberWithDouble : currentTimeSecs / duration ] } ] ;
2019-12-09 21:40:49 -07:00
2018-02-28 09:42:49 -07:00
if ( currentTimeSecs >= 0 && self . onVideoProgress ) {
2019-12-09 23:40:26 -07:00
if ( ! _isRequestAds && currentTimeSecs >= 0.0001 ) {
2019-12-09 21:40:49 -07:00
[ self requestAds ] ;
_isRequestAds = true ;
}
2018-02-28 09:42:49 -07:00
self . onVideoProgress ( @ {
@ "currentTime" : [ NSNumber numberWithFloat : CMTimeGetSeconds ( currentTime ) ] ,
@ "playableDuration" : [ self calculatePlayableDuration ] ,
@ "atValue" : [ NSNumber numberWithLongLong : currentTime . value ] ,
@ "atTimescale" : [ NSNumber numberWithInt : currentTime . timescale ] ,
@ "target" : self . reactTag ,
@ "seekableDuration" : [ self calculateSeekableDuration ] ,
} ) ;
}
2015-04-06 14:05:05 -06:00
}
2015-06-10 15:29:38 -06:00
/ * !
* Calculates and returns the playable duration of the current player item using its loaded time ranges .
*
2015-06-11 19:11:55 -06:00
* \ returns The playable duration of the current player item in seconds .
2015-06-10 15:29:38 -06:00
* /
2015-08-05 06:52:30 -06:00
- ( NSNumber * ) calculatePlayableDuration
{
2015-06-10 15:29:38 -06:00
AVPlayerItem * video = _player . currentItem ;
if ( video . status = = AVPlayerItemStatusReadyToPlay ) {
__block CMTimeRange effectiveTimeRange ;
[ video . loadedTimeRanges enumerateObjectsUsingBlock : ^ ( id obj , NSUInteger idx , BOOL * stop ) {
CMTimeRange timeRange = [ obj CMTimeRangeValue ] ;
if ( CMTimeRangeContainsTime ( timeRange , video . currentTime ) ) {
effectiveTimeRange = timeRange ;
* stop = YES ;
}
} ] ;
2015-06-17 23:08:01 -06:00
Float64 playableDuration = CMTimeGetSeconds ( CMTimeRangeGetEnd ( effectiveTimeRange ) ) ;
if ( playableDuration > 0 ) {
return [ NSNumber numberWithFloat : playableDuration ] ;
}
2015-06-10 15:29:38 -06:00
}
2015-06-17 23:08:01 -06:00
return [ NSNumber numberWithInteger : 0 ] ;
2015-06-10 15:29:38 -06:00
}
2017-09-06 19:12:34 -06:00
- ( NSNumber * ) calculateSeekableDuration
{
2018-02-28 09:42:49 -07:00
CMTimeRange timeRange = [ self playerItemSeekableTimeRange ] ;
if ( CMTIME_IS _NUMERIC ( timeRange . duration ) )
{
return [ NSNumber numberWithFloat : CMTimeGetSeconds ( timeRange . duration ) ] ;
}
return [ NSNumber numberWithInteger : 0 ] ;
2017-09-06 19:12:34 -06:00
}
2015-08-05 06:52:30 -06:00
- ( void ) addPlayerItemObservers
2015-05-30 13:47:14 -06:00
{
[ _playerItem addObserver : self forKeyPath : statusKeyPath options : 0 context : nil ] ;
2016-02-18 17:45:09 -07:00
[ _playerItem addObserver : self forKeyPath : playbackBufferEmptyKeyPath options : 0 context : nil ] ;
2015-08-05 06:52:30 -06:00
[ _playerItem addObserver : self forKeyPath : playbackLikelyToKeepUpKeyPath options : 0 context : nil ] ;
2017-02-13 19:38:02 -07:00
[ _playerItem addObserver : self forKeyPath : timedMetadata options : NSKeyValueObservingOptionNew context : nil ] ;
2015-08-05 06:52:30 -06:00
_playerItemObserversSet = YES ;
2015-05-30 13:47:14 -06:00
}
/ * Fixes https : // github . com / brentvatne / react - native - video / issues / 43
* Crashes caused when trying to remove the observer when there is no
* observer set * /
2015-08-05 06:52:30 -06:00
- ( void ) removePlayerItemObservers
2015-05-30 13:47:14 -06:00
{
2015-08-05 06:52:30 -06:00
if ( _playerItemObserversSet ) {
2015-05-30 13:47:14 -06:00
[ _playerItem removeObserver : self forKeyPath : statusKeyPath ] ;
2016-02-18 17:45:09 -07:00
[ _playerItem removeObserver : self forKeyPath : playbackBufferEmptyKeyPath ] ;
2015-08-05 06:52:30 -06:00
[ _playerItem removeObserver : self forKeyPath : playbackLikelyToKeepUpKeyPath ] ;
2017-02-13 19:38:02 -07:00
[ _playerItem removeObserver : self forKeyPath : timedMetadata ] ;
2015-08-05 06:52:30 -06:00
_playerItemObserversSet = NO ;
2015-05-30 13:47:14 -06:00
}
}
2015-04-08 09:46:13 -06:00
# pragma mark - Player and source
2015-05-30 13:50:45 -06:00
- ( void ) setSrc : ( NSDictionary * ) source
{
2018-12-13 20:30:38 -07:00
_source = source ;
2018-03-19 13:56:55 -06:00
[ self removePlayerLayer ] ;
2015-12-22 16:39:04 -07:00
[ self removePlayerTimeObserver ] ;
2015-08-05 06:52:30 -06:00
[ self removePlayerItemObservers ] ;
2018-08-01 13:15:27 -06:00
2018-08-08 16:37:18 -06:00
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) 0 ) , dispatch_get _main _queue ( ) , ^ {
2018-08-01 13:15:27 -06:00
2018-06-21 10:08:37 -06:00
// perform on next run loop , otherwise other passed react - props may not be set
2018-07-17 06:36:03 -06:00
[ self playerItemForSource : source withCallback : ^ ( AVPlayerItem * playerItem ) {
2018-07-17 07:29:53 -06:00
_playerItem = playerItem ;
2018-07-17 06:36:03 -06:00
[ self addPlayerItemObservers ] ;
2018-11-18 10:15:03 -07:00
[ self setFilter : _filterName ] ;
2018-11-26 15:50:31 -07:00
[ self setMaxBitRate : _maxBitRate ] ;
2018-07-17 06:36:03 -06:00
[ _player pause ] ;
if ( _playbackRateObserverRegistered ) {
[ _player removeObserver : self forKeyPath : playbackRate context : nil ] ;
_playbackRateObserverRegistered = NO ;
}
2018-09-13 06:06:12 -06:00
if ( _isExternalPlaybackActiveObserverRegistered ) {
[ _player removeObserver : self forKeyPath : externalPlaybackActive context : nil ] ;
_isExternalPlaybackActiveObserverRegistered = NO ;
}
2018-07-17 06:36:03 -06:00
_player = [ AVPlayer playerWithPlayerItem : _playerItem ] ;
_player . actionAtItemEnd = AVPlayerActionAtItemEndNone ;
[ _player addObserver : self forKeyPath : playbackRate options : 0 context : nil ] ;
_playbackRateObserverRegistered = YES ;
2018-09-13 06:06:12 -06:00
[ _player addObserver : self forKeyPath : externalPlaybackActive options : 0 context : nil ] ;
_isExternalPlaybackActiveObserverRegistered = YES ;
2018-07-17 06:36:03 -06:00
[ self addPlayerTimeObserver ] ;
2019-08-30 08:01:19 -06:00
if ( @ available ( iOS 10.0 , * ) ) {
[ self setAutomaticallyWaitsToMinimizeStalling : _automaticallyWaitsToMinimizeStalling ] ;
}
2018-06-21 10:08:37 -06:00
2018-07-17 06:36:03 -06:00
// Perform on next run loop , otherwise onVideoLoadStart is nil
2018-08-08 16:37:18 -06:00
if ( self . onVideoLoadStart ) {
2018-07-17 06:36:03 -06:00
id uri = [ source objectForKey : @ "uri" ] ;
id type = [ source objectForKey : @ "type" ] ;
self . onVideoLoadStart ( @ { @ "src" : @ {
2018-07-09 18:00:47 -06:00
@ "uri" : uri ? uri : [ NSNull null ] ,
@ "type" : type ? type : [ NSNull null ] ,
@ "isNetwork" : [ NSNumber numberWithBool : ( bool ) [ source objectForKey : @ "isNetwork" ] ] } ,
2018-07-17 06:36:03 -06:00
@ "target" : self . reactTag
} ) ;
}
} ] ;
2016-12-12 17:16:11 -07:00
} ) ;
2018-06-25 12:43:51 -06:00
_videoLoadStarted = YES ;
2015-04-08 00:49:14 -06:00
}
2015-04-07 09:31:40 -06:00
2018-06-21 10:08:37 -06:00
- ( NSURL * ) urlFilePath : ( NSString * ) filepath {
2018-08-01 13:15:27 -06:00
if ( [ filepath containsString : @ "file://" ] ) {
return [ NSURL URLWithString : filepath ] ;
}
2018-06-21 10:08:37 -06:00
2018-08-20 12:52:06 -06:00
// if no file found , check if the file exists in the Document directory
2018-08-01 13:15:27 -06:00
NSArray * paths = NSSearchPathForDirectoriesInDomains ( NSDocumentDirectory , NSUserDomainMask , YES ) ;
2018-06-21 10:08:37 -06:00
NSString * relativeFilePath = [ filepath lastPathComponent ] ;
// the file may be multiple levels below the documents directory
NSArray * fileComponents = [ filepath componentsSeparatedByString : @ "Documents/" ] ;
2018-09-04 16:44:19 -06:00
if ( fileComponents . count > 1 ) {
2018-06-21 10:08:37 -06:00
relativeFilePath = [ fileComponents objectAtIndex : 1 ] ;
}
NSString * path = [ paths . firstObject stringByAppendingPathComponent : relativeFilePath ] ;
if ( [ [ NSFileManager defaultManager ] fileExistsAtPath : path ] ) {
return [ NSURL fileURLWithPath : path ] ;
}
return nil ;
}
2018-08-08 16:37:18 -06:00
- ( void ) playerItemPrepareText : ( AVAsset * ) asset assetOptions : ( NSDictionary * __nullable ) assetOptions withCallback : ( void ( ^ ) ( AVPlayerItem * ) ) handler
2015-05-30 13:50:45 -06:00
{
2018-10-18 16:21:46 -06:00
if ( ! _textTracks || _textTracks . count = = 0 ) {
2018-08-27 19:05:41 -06:00
handler ( [ AVPlayerItem playerItemWithAsset : asset ] ) ;
return ;
2018-07-09 17:28:38 -06:00
}
2018-10-18 16:21:46 -06:00
// AVPlayer can ' t airplay AVMutableCompositions
_allowsExternalPlayback = NO ;
2018-08-27 19:05:41 -06:00
2018-07-09 17:28:38 -06:00
// sideload text tracks
2018-06-21 10:08:37 -06:00
AVMutableComposition * mixComposition = [ [ AVMutableComposition alloc ] init ] ;
2018-07-09 17:28:38 -06:00
AVAssetTrack * videoAsset = [ asset tracksWithMediaType : AVMediaTypeVideo ] . firstObject ;
AVMutableCompositionTrack * videoCompTrack = [ mixComposition addMutableTrackWithMediaType : AVMediaTypeVideo preferredTrackID : kCMPersistentTrackID_Invalid ] ;
2018-06-21 10:08:37 -06:00
[ videoCompTrack insertTimeRange : CMTimeRangeMake ( kCMTimeZero , videoAsset . timeRange . duration )
2018-07-09 17:28:38 -06:00
ofTrack : videoAsset
atTime : kCMTimeZero
error : nil ] ;
2018-08-09 10:58:03 -06:00
2018-07-09 17:28:38 -06:00
AVAssetTrack * audioAsset = [ asset tracksWithMediaType : AVMediaTypeAudio ] . firstObject ;
AVMutableCompositionTrack * audioCompTrack = [ mixComposition addMutableTrackWithMediaType : AVMediaTypeAudio preferredTrackID : kCMPersistentTrackID_Invalid ] ;
2018-06-21 10:08:37 -06:00
[ audioCompTrack insertTimeRange : CMTimeRangeMake ( kCMTimeZero , videoAsset . timeRange . duration )
2018-07-09 17:28:38 -06:00
ofTrack : audioAsset
atTime : kCMTimeZero
error : nil ] ;
2018-08-09 10:58:03 -06:00
2018-07-31 17:56:19 -06:00
NSMutableArray * validTextTracks = [ NSMutableArray array ] ;
2018-07-09 17:28:38 -06:00
for ( int i = 0 ; i < _textTracks . count ; + + i ) {
AVURLAsset * textURLAsset ;
NSString * textUri = [ _textTracks objectAtIndex : i ] [ @ "uri" ] ;
if ( [ [ textUri lowercaseString ] hasPrefix : @ "http" ] ) {
textURLAsset = [ AVURLAsset URLAssetWithURL : [ NSURL URLWithString : textUri ] options : assetOptions ] ;
} else {
textURLAsset = [ AVURLAsset URLAssetWithURL : [ self urlFilePath : textUri ] options : nil ] ;
}
AVAssetTrack * textTrackAsset = [ textURLAsset tracksWithMediaType : AVMediaTypeText ] . firstObject ;
2018-07-31 17:56:19 -06:00
if ( ! textTrackAsset ) continue ; // fix when there ' s no textTrackAsset
[ validTextTracks addObject : [ _textTracks objectAtIndex : i ] ] ;
2018-07-09 17:28:38 -06:00
AVMutableCompositionTrack * textCompTrack = [ mixComposition
addMutableTrackWithMediaType : AVMediaTypeText
preferredTrackID : kCMPersistentTrackID_Invalid ] ;
[ textCompTrack insertTimeRange : CMTimeRangeMake ( kCMTimeZero , videoAsset . timeRange . duration )
2018-08-09 10:58:03 -06:00
ofTrack : textTrackAsset
atTime : kCMTimeZero
error : nil ] ;
2018-07-09 17:28:38 -06:00
}
2018-07-31 17:56:19 -06:00
if ( validTextTracks . count ! = _textTracks . count ) {
[ self setTextTracks : validTextTracks ] ;
}
2018-07-09 17:28:38 -06:00
2018-07-17 05:28:37 -06:00
handler ( [ AVPlayerItem playerItemWithAsset : mixComposition ] ) ;
2015-04-08 00:49:14 -06:00
}
2015-04-07 09:31:40 -06:00
2018-02-28 09:42:49 -07:00
- ( void ) playerItemForSource : ( NSDictionary * ) source withCallback : ( void ( ^ ) ( AVPlayerItem * ) ) handler
2015-05-30 13:50:45 -06:00
{
2015-04-08 11:34:27 -06:00
bool isNetwork = [ RCTConvert BOOL : [ source objectForKey : @ "isNetwork" ] ] ;
bool isAsset = [ RCTConvert BOOL : [ source objectForKey : @ "isAsset" ] ] ;
2019-01-24 05:15:58 -07:00
bool shouldCache = [ RCTConvert BOOL : [ source objectForKey : @ "shouldCache" ] ] ;
2015-04-08 11:34:27 -06:00
NSString * uri = [ source objectForKey : @ "uri" ] ;
NSString * type = [ source objectForKey : @ "type" ] ;
2019-02-18 22:34:34 -07:00
if ( ! uri || [ uri isEqualToString : @ "" ] ) {
2018-09-19 04:02:55 -06:00
DebugLog ( @ "Could not find video URL in source '%@'" , source ) ;
return ;
}
2018-07-17 05:28:37 -06:00
2018-08-08 16:37:18 -06:00
NSURL * url = isNetwork || isAsset
? [ NSURL URLWithString : uri ]
: [ [ NSURL alloc ] initFileURLWithPath : [ [ NSBundle mainBundle ] pathForResource : uri ofType : type ] ] ;
2018-07-17 06:36:03 -06:00
NSMutableDictionary * assetOptions = [ [ NSMutableDictionary alloc ] init ] ;
2018-02-28 09:42:49 -07:00
2016-10-06 15:34:01 -06:00
if ( isNetwork ) {
2018-08-27 19:05:41 -06:00
/ * Per #1091 , this is not a public API .
* We need to either get approval from Apple to use this or use a different approach .
NSDictionary * headers = [ source objectForKey : @ "requestHeaders" ] ;
if ( [ headers count ] > 0 ) {
[ assetOptions setObject : headers forKey : @ "AVURLAssetHTTPHeaderFieldsKey" ] ;
}
* /
NSArray * cookies = [ [ NSHTTPCookieStorage sharedHTTPCookieStorage ] cookies ] ;
[ assetOptions setObject : cookies forKey : AVURLAssetHTTPCookiesKey ] ;
2018-07-18 18:06:09 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
2019-01-24 05:15:58 -07:00
if ( shouldCache && ( ! _textTracks || ! _textTracks . count ) ) {
2018-08-27 19:05:41 -06:00
/ * The DVURLAsset created by cache doesn ' t have a tracksWithMediaType property , so trying
2019-01-24 05:15:58 -07:00
* to bring in the text track code will crash . I suspect this is because the asset hasn ' t fully loaded .
2018-08-27 19:05:41 -06:00
* Until this is fixed , we need to bypass caching when text tracks are specified .
* /
2018-08-27 19:16:59 -06:00
DebugLog ( @ "Caching is not supported for uri '%@' because text tracks are not compatible with the cache. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md" , uri ) ;
2018-08-27 19:05:41 -06:00
[ self playerItemForSourceUsingCache : uri assetOptions : assetOptions withCallback : handler ] ;
return ;
}
2018-07-18 18:06:09 -06:00
# endif
2018-08-27 19:05:41 -06:00
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : url options : assetOptions ] ;
[ self playerItemPrepareText : asset assetOptions : assetOptions withCallback : handler ] ;
2018-02-28 09:42:49 -07:00
return ;
2018-08-08 16:37:18 -06:00
} else if ( isAsset ) {
2015-04-08 11:34:27 -06:00
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : url options : nil ] ;
2018-07-17 06:36:03 -06:00
[ self playerItemPrepareText : asset assetOptions : assetOptions withCallback : handler ] ;
2018-02-28 09:42:49 -07:00
return ;
2015-04-08 11:34:27 -06:00
}
2018-07-17 05:28:37 -06:00
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : [ [ NSURL alloc ] initFileURLWithPath : [ [ NSBundle mainBundle ] pathForResource : uri ofType : type ] ] options : nil ] ;
2018-07-17 06:36:03 -06:00
[ self playerItemPrepareText : asset assetOptions : assetOptions withCallback : handler ] ;
2015-04-08 11:34:27 -06:00
}
2018-08-08 16:37:18 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
- ( void ) playerItemForSourceUsingCache : ( NSString * ) uri assetOptions : ( NSDictionary * ) options withCallback : ( void ( ^ ) ( AVPlayerItem * ) ) handler {
NSURL * url = [ NSURL URLWithString : uri ] ;
[ _videoCache getItemForUri : uri withCallback : ^ ( RCTVideoCacheStatus videoCacheStatus , AVAsset * _Nullable cachedAsset ) {
switch ( videoCacheStatus ) {
case RCTVideoCacheStatusMissingFileExtension : {
DebugLog ( @ "Could not generate cache key for uri '%@'. It is currently not supported to cache urls that do not include a file extension. The video file will not be cached. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md" , uri ) ;
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : url options : options ] ;
[ self playerItemPrepareText : asset assetOptions : options withCallback : handler ] ;
return ;
}
case RCTVideoCacheStatusUnsupportedFileExtension : {
DebugLog ( @ "Could not generate cache key for uri '%@'. The file extension of that uri is currently not supported. The video file will not be cached. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md" , uri ) ;
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : url options : options ] ;
[ self playerItemPrepareText : asset assetOptions : options withCallback : handler ] ;
return ;
}
default :
if ( cachedAsset ) {
DebugLog ( @ "Playing back uri '%@' from cache" , uri ) ;
2018-08-27 19:05:41 -06:00
// See note in playerItemForSource about not being able to support text tracks & caching
2018-10-23 08:09:19 -06:00
handler ( [ AVPlayerItem playerItemWithAsset : cachedAsset ] ) ;
2018-08-08 16:37:18 -06:00
return ;
}
}
2018-08-27 18:55:33 -06:00
DVURLAsset * asset = [ [ DVURLAsset alloc ] initWithURL : url options : options networkTimeout : 10000 ] ;
asset . loaderDelegate = self ;
2018-08-08 16:37:18 -06:00
2018-08-27 19:05:41 -06:00
/ * More granular code to have control over the DVURLAsset
2018-08-08 16:37:18 -06:00
DVAssetLoaderDelegate * resourceLoaderDelegate = [ [ DVAssetLoaderDelegate alloc ] initWithURL : url ] ;
resourceLoaderDelegate . delegate = self ;
NSURLComponents * components = [ [ NSURLComponents alloc ] initWithURL : url resolvingAgainstBaseURL : NO ] ;
components . scheme = [ DVAssetLoaderDelegate scheme ] ;
AVURLAsset * asset = [ [ AVURLAsset alloc ] initWithURL : [ components URL ] options : options ] ;
[ asset . resourceLoader setDelegate : resourceLoaderDelegate queue : dispatch_get _main _queue ( ) ] ;
2018-08-27 18:55:33 -06:00
* /
2018-08-08 16:37:18 -06:00
2018-08-27 18:55:33 -06:00
handler ( [ AVPlayerItem playerItemWithAsset : asset ] ) ;
2018-08-08 16:37:18 -06:00
} ] ;
}
# pragma mark - DVAssetLoaderDelegate
- ( void ) dvAssetLoaderDelegate : ( DVAssetLoaderDelegate * ) loaderDelegate
didLoadData : ( NSData * ) data
forURL : ( NSURL * ) url {
[ _videoCache storeItem : data forUri : [ url absoluteString ] withCallback : ^ ( BOOL success ) {
DebugLog ( @ "Cache data stored successfully 🎉" ) ;
} ] ;
2015-04-08 11:34:27 -06:00
}
2018-08-08 16:37:18 -06:00
# endif
2015-05-30 13:50:45 -06:00
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary * ) change context : ( void * ) context
{
2019-07-02 14:35:36 -06:00
2019-06-19 07:18:25 -06:00
if ( [ keyPath isEqualToString : readyForDisplayKeyPath ] && [ change objectForKey : NSKeyValueChangeNewKey ] && self . onReadyForDisplay ) {
self . onReadyForDisplay ( @ { @ "target" : self . reactTag } ) ;
return ;
}
2018-06-25 12:43:51 -06:00
if ( object = = _playerItem ) {
2017-02-13 19:38:02 -07:00
// When timeMetadata is read the event onTimedMetadata is triggered
2018-06-25 12:43:51 -06:00
if ( [ keyPath isEqualToString : timedMetadata ] ) {
NSArray < AVMetadataItem * > * items = [ change objectForKey : @ "new" ] ;
if ( items && ! [ items isEqual : [ NSNull null ] ] && items . count > 0 ) {
NSMutableArray * array = [ NSMutableArray new ] ;
for ( AVMetadataItem * item in items ) {
2018-07-09 17:28:38 -06:00
NSString * value = ( NSString * ) item . value ;
2018-06-25 12:43:51 -06:00
NSString * identifier = item . identifier ;
if ( ! [ value isEqual : [ NSNull null ] ] ) {
NSDictionary * dictionary = [ [ NSDictionary alloc ] initWithObjects : @ [ value , identifier ] forKeys : @ [ @ "value" , @ "identifier" ] ] ;
[ array addObject : dictionary ] ;
}
2017-02-13 19:38:02 -07:00
}
2018-06-25 12:43:51 -06:00
self . onTimedMetadata ( @ {
@ "target" : self . reactTag ,
@ "metadata" : array
} ) ;
}
2017-02-13 19:38:02 -07:00
}
2018-06-25 12:43:51 -06:00
2015-08-05 06:52:30 -06:00
if ( [ keyPath isEqualToString : statusKeyPath ] ) {
// Handle player item status change .
if ( _playerItem . status = = AVPlayerItemStatusReadyToPlay ) {
float duration = CMTimeGetSeconds ( _playerItem . asset . duration ) ;
2018-06-25 12:43:51 -06:00
2015-08-05 06:52:30 -06:00
if ( isnan ( duration ) ) {
duration = 0.0 ;
}
2018-06-25 12:43:51 -06:00
2016-04-08 03:10:22 -06:00
NSObject * width = @ "undefined" ;
NSObject * height = @ "undefined" ;
2016-05-01 08:41:30 -06:00
NSString * orientation = @ "undefined" ;
2018-06-25 12:43:51 -06:00
2016-04-08 03:10:22 -06:00
if ( [ _playerItem . asset tracksWithMediaType : AVMediaTypeVideo ] . count > 0 ) {
2016-05-01 09:26:56 -06:00
AVAssetTrack * videoTrack = [ [ _playerItem . asset tracksWithMediaType : AVMediaTypeVideo ] objectAtIndex : 0 ] ;
width = [ NSNumber numberWithFloat : videoTrack . naturalSize . width ] ;
height = [ NSNumber numberWithFloat : videoTrack . naturalSize . height ] ;
CGAffineTransform preferredTransform = [ videoTrack preferredTransform ] ;
2018-06-25 12:43:51 -06:00
2016-05-01 09:26:56 -06:00
if ( ( videoTrack . naturalSize . width = = preferredTransform . tx
2018-06-25 12:43:51 -06:00
&& videoTrack . naturalSize . height = = preferredTransform . ty )
|| ( preferredTransform . tx = = 0 && preferredTransform . ty = = 0 ) )
2016-05-01 09:26:56 -06:00
{
2016-05-01 08:41:30 -06:00
orientation = @ "landscape" ;
2018-06-25 12:43:51 -06:00
} else {
2016-05-01 08:41:30 -06:00
orientation = @ "portrait" ;
2018-06-25 12:43:51 -06:00
}
2016-04-08 03:10:22 -06:00
}
2018-06-25 12:43:51 -06:00
if ( self . onVideoLoad && _videoLoadStarted ) {
2017-01-16 09:27:08 -07:00
self . onVideoLoad ( @ { @ "duration" : [ NSNumber numberWithFloat : duration ] ,
@ "currentTime" : [ NSNumber numberWithFloat : CMTimeGetSeconds ( _playerItem . currentTime ) ] ,
@ "canPlayReverse" : [ NSNumber numberWithBool : _playerItem . canPlayReverse ] ,
@ "canPlayFastForward" : [ NSNumber numberWithBool : _playerItem . canPlayFastForward ] ,
@ "canPlaySlowForward" : [ NSNumber numberWithBool : _playerItem . canPlaySlowForward ] ,
@ "canPlaySlowReverse" : [ NSNumber numberWithBool : _playerItem . canPlaySlowReverse ] ,
@ "canStepBackward" : [ NSNumber numberWithBool : _playerItem . canStepBackward ] ,
@ "canStepForward" : [ NSNumber numberWithBool : _playerItem . canStepForward ] ,
@ "naturalSize" : @ {
2018-06-25 12:43:51 -06:00
@ "width" : width ,
@ "height" : height ,
@ "orientation" : orientation
} ,
2018-07-17 15:14:21 -06:00
@ "audioTracks" : [ self getAudioTrackInfo ] ,
2018-06-11 21:55:23 -06:00
@ "textTracks" : [ self getTextTrackInfo ] ,
2017-01-16 09:27:08 -07:00
@ "target" : self . reactTag } ) ;
2018-06-25 12:43:51 -06:00
}
_videoLoadStarted = NO ;
2015-08-05 06:52:30 -06:00
[ self attachListeners ] ;
[ self applyModifiers ] ;
2018-06-25 12:43:51 -06:00
} else if ( _playerItem . status = = AVPlayerItemStatusFailed && self . onVideoError ) {
2016-12-12 17:16:11 -07:00
self . onVideoError ( @ { @ "error" : @ { @ "code" : [ NSNumber numberWithInteger : _playerItem . error . code ] ,
@ "domain" : _playerItem . error . domain } ,
2018-06-25 12:43:51 -06:00
@ "target" : self . reactTag } ) ;
2015-08-05 06:52:30 -06:00
}
2016-02-18 17:45:09 -07:00
} else if ( [ keyPath isEqualToString : playbackBufferEmptyKeyPath ] ) {
_playerBufferEmpty = YES ;
2017-01-11 05:51:45 -07:00
self . onVideoBuffer ( @ { @ "isBuffering" : @ ( YES ) , @ "target" : self . reactTag } ) ;
2015-08-05 06:52:30 -06:00
} else if ( [ keyPath isEqualToString : playbackLikelyToKeepUpKeyPath ] ) {
// Continue playing ( or not if paused ) after being paused due to hitting an unbuffered zone .
2016-09-19 18:49:12 -06:00
if ( ( ! ( _controls || _fullscreenPlayerPresented ) || _playerBufferEmpty ) && _playerItem . playbackLikelyToKeepUp ) {
2015-08-05 06:52:30 -06:00
[ self setPaused : _paused ] ;
2015-05-11 00:54:58 -06:00
}
2016-02-18 17:45:09 -07:00
_playerBufferEmpty = NO ;
2017-01-11 05:51:45 -07:00
self . onVideoBuffer ( @ { @ "isBuffering" : @ ( NO ) , @ "target" : self . reactTag } ) ;
2015-04-08 00:49:14 -06:00
}
2016-04-28 06:37:45 -06:00
} else if ( object = = _player ) {
2018-06-25 12:43:51 -06:00
if ( [ keyPath isEqualToString : playbackRate ] ) {
if ( self . onPlaybackRateChange ) {
self . onPlaybackRateChange ( @ { @ "playbackRate" : [ NSNumber numberWithFloat : _player . rate ] ,
@ "target" : self . reactTag } ) ;
2016-04-28 06:37:45 -06:00
}
2018-06-25 12:43:51 -06:00
if ( _playbackStalled && _player . rate > 0 ) {
if ( self . onPlaybackResume ) {
self . onPlaybackResume ( @ { @ "playbackRate" : [ NSNumber numberWithFloat : _player . rate ] ,
@ "target" : self . reactTag } ) ;
}
_playbackStalled = NO ;
2016-04-28 06:37:45 -06:00
}
2018-06-25 12:43:51 -06:00
}
2018-09-13 06:06:12 -06:00
else if ( [ keyPath isEqualToString : externalPlaybackActive ] ) {
2018-10-07 16:59:10 -06:00
if ( self . onVideoExternalPlaybackChange ) {
self . onVideoExternalPlaybackChange ( @ { @ "isExternalPlaybackActive" : [ NSNumber numberWithBool : _player . isExternalPlaybackActive ] ,
2018-09-13 06:06:12 -06:00
@ "target" : self . reactTag } ) ;
}
}
2019-07-02 14:35:36 -06:00
} else if ( object = = _playerViewController . contentOverlayView ) {
// when controls = = true , this is a hack to reset the rootview when rotation happens in fullscreen
if ( [ keyPath isEqualToString : @ "frame" ] ) {
CGRect oldRect = [ change [ NSKeyValueChangeOldKey ] CGRectValue ] ;
CGRect newRect = [ change [ NSKeyValueChangeNewKey ] CGRectValue ] ;
if ( ! CGRectEqualToRect ( oldRect , newRect ) ) {
if ( CGRectEqualToRect ( newRect , [ UIScreen mainScreen ] . bounds ) ) {
NSLog ( @ "in fullscreen" ) ;
} else NSLog ( @ "not fullscreen" ) ;
[ self . reactViewController . view setFrame : [ UIScreen mainScreen ] . bounds ] ;
[ self . reactViewController . view setNeedsLayout ] ;
}
return ;
}
2015-04-08 00:49:14 -06:00
}
2015-03-30 23:07:55 -06:00
}
2019-12-09 21:40:49 -07:00
- ( void ) setupAdsLoader {
// Re - use this IMAAdsLoader instance for the entire lifecycle of your app .
self . adsLoader = [ [ IMAAdsLoader alloc ] initWithSettings : nil ] ;
// NOTE : This line will cause a warning until the next step , "Get the Ads Manager" .
self . adsLoader . delegate = self ;
}
- ( void ) requestAds {
// Create an ad display container for ad rendering .
IMAAdDisplayContainer * adDisplayContainer =
[ [ IMAAdDisplayContainer alloc ] initWithAdContainer : self companionSlots : nil ] ;
// Create an ad request with our ad tag , display container , and optional user context .
IMAAdsRequest * request = [ [ IMAAdsRequest alloc ] initWithAdTagUrl : _adTagUrl
adDisplayContainer : adDisplayContainer
contentPlayhead : self . contentPlayhead
userContext : nil ] ;
[ self . adsLoader requestAdsWithRequest : request ] ;
}
# pragma mark AdsLoader Delegates
- ( void ) adsLoader : ( IMAAdsLoader * ) loader adsLoadedWithData : ( IMAAdsLoadedData * ) adsLoadedData {
// Grab the instance of the IMAAdsManager and set ourselves as the delegate .
self . adsManager = adsLoadedData . adsManager ;
// NOTE : This line will cause a warning until the next step , "Display Ads" .
self . adsManager . delegate = self ;
// Create ads rendering settings and tell the SDK to use the in - app browser .
IMAAdsRenderingSettings * adsRenderingSettings = [ [ IMAAdsRenderingSettings alloc ] init ] ;
adsRenderingSettings . webOpenerPresentingController = _playerViewController ;
// Initialize the ads manager .
[ self . adsManager initializeWithAdsRenderingSettings : adsRenderingSettings ] ;
}
- ( void ) adsLoader : ( IMAAdsLoader * ) loader failedWithErrorData : ( IMAAdLoadingErrorData * ) adErrorData {
// Something went wrong loading ads . Log the error and play the content .
NSLog ( @ "Error loading ads: %@" , adErrorData . adError . message ) ;
[ _player play ] ;
}
# pragma mark AdsManager Delegates
- ( void ) adsManager : ( IMAAdsManager * ) adsManager didReceiveAdEvent : ( IMAAdEvent * ) event {
if ( event . type = = kIMAAdEvent_LOADED ) {
// When the SDK notifies us that ads have been loaded , play them .
[ adsManager start ] ;
}
}
- ( void ) adsManager : ( IMAAdsManager * ) adsManager didReceiveAdError : ( IMAAdError * ) error {
// Something went wrong with the ads manager after ads were loaded . Log the error and play the
// content .
NSLog ( @ "AdsManager error: %@" , error . message ) ;
[ _player play ] ;
}
- ( void ) adsManagerDidRequestContentPause : ( IMAAdsManager * ) adsManager {
// The SDK is going to play ads , so pause the content .
[ _player pause ] ;
}
- ( void ) adsManagerDidRequestContentResume : ( IMAAdsManager * ) adsManager {
// The SDK is done playing ads ( at least for now ) , so resume the content .
[ _player play ] ;
}
2015-05-30 13:50:45 -06:00
- ( void ) attachListeners
{
2015-07-18 06:38:56 -06:00
// listen for end of file
2017-10-24 01:47:43 -06:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self
name : AVPlayerItemDidPlayToEndTimeNotification
object : [ _player currentItem ] ] ;
2015-07-18 06:38:56 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( playerItemDidReachEnd : )
name : AVPlayerItemDidPlayToEndTimeNotification
object : [ _player currentItem ] ] ;
2018-08-09 10:58:03 -06:00
2017-10-24 01:47:43 -06:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self
name : AVPlayerItemPlaybackStalledNotification
object : nil ] ;
2016-04-28 06:38:21 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( playbackStalled : )
name : AVPlayerItemPlaybackStalledNotification
object : nil ] ;
2018-09-18 08:39:32 -06:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self
name : AVPlayerItemNewAccessLogEntryNotification
object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( handleAVPlayerAccess : )
name : AVPlayerItemNewAccessLogEntryNotification
object : nil ] ;
}
- ( void ) handleAVPlayerAccess : ( NSNotification * ) notification {
AVPlayerItemAccessLog * accessLog = [ ( ( AVPlayerItem * ) notification . object ) accessLog ] ;
AVPlayerItemAccessLogEvent * lastEvent = accessLog . events . lastObject ;
2018-09-19 06:28:49 -06:00
2018-12-31 23:06:50 -07:00
/ * TODO : get this working
2018-09-19 06:28:49 -06:00
if ( self . onBandwidthUpdate ) {
2018-12-31 22:39:12 -07:00
self . onBandwidthUpdate ( @ { @ "bitrate" : [ NSNumber numberWithFloat : lastEvent . observedBitrate ] } ) ;
2018-09-19 06:28:49 -06:00
}
2018-12-31 23:06:50 -07:00
* /
2016-04-28 06:38:21 -06:00
}
- ( void ) playbackStalled : ( NSNotification * ) notification
{
2017-01-16 09:27:08 -07:00
if ( self . onPlaybackStalled ) {
self . onPlaybackStalled ( @ { @ "target" : self . reactTag } ) ;
}
2016-04-28 06:38:21 -06:00
_playbackStalled = YES ;
2015-04-10 20:58:36 -06:00
}
2015-06-26 16:13:03 -06:00
- ( void ) playerItemDidReachEnd : ( NSNotification * ) notification
2015-05-30 13:50:45 -06:00
{
2019-12-09 21:40:49 -07:00
if ( notification . object = = _player . currentItem ) {
[ self . adsLoader contentComplete ] ;
}
2017-01-16 09:27:08 -07:00
if ( self . onVideoEnd ) {
2018-02-28 09:42:49 -07:00
self . onVideoEnd ( @ { @ "target" : self . reactTag } ) ;
2017-01-16 09:27:08 -07:00
}
2018-02-28 09:42:49 -07:00
2015-06-24 20:09:05 -06:00
if ( _repeat ) {
2015-04-08 11:34:27 -06:00
AVPlayerItem * item = [ notification object ] ;
[ item seekToTime : kCMTimeZero ] ;
[ self applyModifiers ] ;
2018-05-15 23:19:12 -06:00
} else {
[ self removePlayerTimeObserver ] ;
2015-06-24 20:09:05 -06:00
}
2015-04-08 09:46:13 -06:00
}
2015-04-08 11:34:27 -06:00
# pragma mark - Prop setters
2015-04-08 09:46:13 -06:00
2015-05-30 13:50:45 -06:00
- ( void ) setResizeMode : ( NSString * ) mode
{
2015-12-22 16:39:04 -07:00
if ( _controls )
{
_playerViewController . videoGravity = mode ;
}
else
{
_playerLayer . videoGravity = mode ;
}
2015-06-16 02:07:50 -06:00
_resizeMode = mode ;
2015-04-04 18:55:37 -06:00
}
2015-10-30 03:34:54 -06:00
- ( void ) setPlayInBackground : ( BOOL ) playInBackground
{
2016-06-02 00:33:18 -06:00
_playInBackground = playInBackground ;
2015-10-30 03:34:54 -06:00
}
2018-06-05 19:40:12 -06:00
- ( void ) setAllowsExternalPlayback : ( BOOL ) allowsExternalPlayback
{
_allowsExternalPlayback = allowsExternalPlayback ;
_player . allowsExternalPlayback = _allowsExternalPlayback ;
}
2016-04-29 05:55:34 -06:00
- ( void ) setPlayWhenInactive : ( BOOL ) playWhenInactive
{
2016-06-02 00:33:18 -06:00
_playWhenInactive = playWhenInactive ;
2016-04-29 05:55:34 -06:00
}
2018-10-26 14:33:03 -06:00
- ( void ) setPictureInPicture : ( BOOL ) pictureInPicture
{
2019-03-11 20:55:36 -06:00
# if TARGET_OS _IOS
2018-11-26 15:23:04 -07:00
if ( _pictureInPicture = = pictureInPicture ) {
return ;
}
_pictureInPicture = pictureInPicture ;
if ( _pipController && _pictureInPicture && ! [ _pipController isPictureInPictureActive ] ) {
2018-10-26 14:33:03 -06:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ _pipController startPictureInPicture ] ;
} ) ;
2018-11-26 15:23:04 -07:00
} else if ( _pipController && ! _pictureInPicture && [ _pipController isPictureInPictureActive ] ) {
2018-10-26 14:33:03 -06:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ _pipController stopPictureInPicture ] ;
} ) ;
}
2019-03-11 20:55:36 -06:00
# endif
2018-10-26 14:33:03 -06:00
}
2019-03-11 20:55:36 -06:00
# if TARGET_OS _IOS
2018-10-26 14:33:03 -06:00
- ( void ) setRestoreUserInterfaceForPIPStopCompletionHandler : ( BOOL ) restore
{
if ( _restoreUserInterfaceForPIPStopCompletionHandler ! = NULL ) {
_restoreUserInterfaceForPIPStopCompletionHandler ( restore ) ;
_restoreUserInterfaceForPIPStopCompletionHandler = NULL ;
}
}
- ( void ) setupPipController {
2018-11-26 15:23:04 -07:00
if ( ! _pipController && _playerLayer && [ AVPictureInPictureController isPictureInPictureSupported ] ) {
// Create new controller passing reference to the AVPlayerLayer
_pipController = [ [ AVPictureInPictureController alloc ] initWithPlayerLayer : _playerLayer ] ;
_pipController . delegate = self ;
2018-10-26 14:33:03 -06:00
}
}
2019-03-11 20:55:36 -06:00
# endif
2018-10-26 14:33:03 -06:00
2017-04-20 12:10:06 -06:00
- ( void ) setIgnoreSilentSwitch : ( NSString * ) ignoreSilentSwitch
{
_ignoreSilentSwitch = ignoreSilentSwitch ;
[ self applyModifiers ] ;
}
2015-05-30 13:50:45 -06:00
- ( void ) setPaused : ( BOOL ) paused
{
2015-04-07 21:38:16 -06:00
if ( paused ) {
2015-12-22 16:39:04 -07:00
[ _player pause ] ;
2015-07-20 10:46:49 -06:00
[ _player setRate : 0.0 ] ;
2015-04-07 09:31:40 -06:00
} else {
2017-04-20 12:10:06 -06:00
if ( [ _ignoreSilentSwitch isEqualToString : @ "ignore" ] ) {
[ [ AVAudioSession sharedInstance ] setCategory : AVAudioSessionCategoryPlayback error : nil ] ;
} else if ( [ _ignoreSilentSwitch isEqualToString : @ "obey" ] ) {
[ [ AVAudioSession sharedInstance ] setCategory : AVAudioSessionCategoryAmbient error : nil ] ;
}
2019-12-09 21:40:49 -07:00
2019-09-21 23:40:53 -06:00
if ( @ available ( iOS 10.0 , * ) && ! _automaticallyWaitsToMinimizeStalling ) {
[ _player playImmediatelyAtRate : _rate ] ;
} else {
[ _player play ] ;
[ _player setRate : _rate ] ;
}
2015-07-20 10:46:49 -06:00
[ _player setRate : _rate ] ;
2015-04-07 09:31:40 -06:00
}
2018-02-28 09:42:49 -07:00
2015-05-10 17:01:25 -06:00
_paused = paused ;
2015-04-05 11:17:03 -06:00
}
2015-12-22 16:39:04 -07:00
- ( float ) getCurrentTime
{
return _playerItem ! = NULL ? CMTimeGetSeconds ( _playerItem . currentTime ) : 0 ;
}
- ( void ) setCurrentTime : ( float ) currentTime
{
2018-07-09 12:20:32 -06:00
NSDictionary * info = @ {
@ "time" : [ NSNumber numberWithFloat : currentTime ] ,
@ "tolerance" : [ NSNumber numberWithInt : 100 ]
} ;
[ self setSeek : info ] ;
2015-12-22 16:39:04 -07:00
}
2018-07-09 12:20:32 -06:00
- ( void ) setSeek : ( NSDictionary * ) info
2015-05-30 13:50:45 -06:00
{
2018-07-09 12:20:32 -06:00
NSNumber * seekTime = info [ @ "time" ] ;
NSNumber * seekTolerance = info [ @ "tolerance" ] ;
int timeScale = 1000 ;
2015-04-08 16:15:57 -06:00
AVPlayerItem * item = _player . currentItem ;
if ( item && item . status = = AVPlayerItemStatusReadyToPlay ) {
// TODO check loadedTimeRanges
2018-07-09 12:20:32 -06:00
CMTime cmSeekTime = CMTimeMakeWithSeconds ( [ seekTime floatValue ] , timeScale ) ;
2015-04-08 16:15:57 -06:00
CMTime current = item . currentTime ;
// TODO figure out a good tolerance level
2018-07-09 12:20:32 -06:00
CMTime tolerance = CMTimeMake ( [ seekTolerance floatValue ] , timeScale ) ;
2017-03-21 14:25:58 -06:00
BOOL wasPaused = _paused ;
2018-07-09 12:20:32 -06:00
2015-04-08 16:15:57 -06:00
if ( CMTimeCompare ( current , cmSeekTime ) ! = 0 ) {
2017-03-21 14:25:58 -06:00
if ( ! wasPaused ) [ _player pause ] ;
2015-07-18 06:38:56 -06:00
[ _player seekToTime : cmSeekTime toleranceBefore : tolerance toleranceAfter : tolerance completionHandler : ^ ( BOOL finished ) {
2018-05-15 23:19:12 -06:00
if ( ! _timeObserver ) {
[ self addPlayerTimeObserver ] ;
}
2017-09-07 06:16:44 -06:00
if ( ! wasPaused ) {
2018-07-09 12:20:32 -06:00
[ self setPaused : false ] ;
2017-09-07 06:16:44 -06:00
}
2017-01-16 09:27:08 -07:00
if ( self . onVideoSeek ) {
2018-07-09 12:20:32 -06:00
self . onVideoSeek ( @ { @ "currentTime" : [ NSNumber numberWithFloat : CMTimeGetSeconds ( item . currentTime ) ] ,
@ "seekTime" : seekTime ,
@ "target" : self . reactTag } ) ;
2017-01-16 09:27:08 -07:00
}
2015-07-18 06:38:56 -06:00
} ] ;
2018-07-09 12:20:32 -06:00
2015-04-09 08:19:09 -06:00
_pendingSeek = false ;
2015-04-08 16:15:57 -06:00
}
2018-07-09 12:20:32 -06:00
2015-04-08 16:15:57 -06:00
} else {
2015-08-05 06:52:30 -06:00
// TODO : See if this makes sense and if so , actually implement it
2015-04-08 16:15:57 -06:00
_pendingSeek = true ;
2018-07-09 12:20:32 -06:00
_pendingSeekTime = [ seekTime floatValue ] ;
2015-04-08 16:15:57 -06:00
}
}
2015-05-30 13:50:45 -06:00
- ( void ) setRate : ( float ) rate
{
2015-04-07 21:38:16 -06:00
_rate = rate ;
[ self applyModifiers ] ;
}
2015-05-30 13:50:45 -06:00
- ( void ) setMuted : ( BOOL ) muted
{
2015-04-07 21:38:16 -06:00
_muted = muted ;
[ self applyModifiers ] ;
}
2015-05-30 13:50:45 -06:00
- ( void ) setVolume : ( float ) volume
{
2015-04-07 21:38:16 -06:00
_volume = volume ;
[ self applyModifiers ] ;
}
2018-11-26 15:50:31 -07:00
- ( void ) setMaxBitRate : ( float ) maxBitRate {
_maxBitRate = maxBitRate ;
2018-12-22 16:33:43 -07:00
_playerItem . preferredPeakBitRate = maxBitRate ;
2018-11-26 15:50:31 -07:00
}
2019-08-22 02:10:39 -06:00
- ( void ) setAutomaticallyWaitsToMinimizeStalling : ( BOOL ) waits
{
_automaticallyWaitsToMinimizeStalling = waits ;
_player . automaticallyWaitsToMinimizeStalling = waits ;
}
2018-11-26 15:50:31 -07:00
2015-05-30 13:50:45 -06:00
- ( void ) applyModifiers
{
2015-04-07 21:38:16 -06:00
if ( _muted ) {
2019-07-06 05:58:18 -06:00
if ( ! _controls ) {
[ _player setVolume : 0 ] ;
}
2015-07-18 06:38:56 -06:00
[ _player setMuted : YES ] ;
2015-04-07 21:38:16 -06:00
} else {
2015-07-18 06:38:56 -06:00
[ _player setVolume : _volume ] ;
[ _player setMuted : NO ] ;
2015-04-07 21:38:16 -06:00
}
2018-08-09 10:58:03 -06:00
2018-12-22 16:33:43 -07:00
[ self setMaxBitRate : _maxBitRate ] ;
2018-07-17 15:14:21 -06:00
[ self setSelectedAudioTrack : _selectedAudioTrack ] ;
2018-06-02 03:24:13 -06:00
[ self setSelectedTextTrack : _selectedTextTrack ] ;
2015-06-16 02:07:50 -06:00
[ self setResizeMode : _resizeMode ] ;
[ self setRepeat : _repeat ] ;
2015-05-10 17:01:25 -06:00
[ self setPaused : _paused ] ;
2015-12-22 16:39:04 -07:00
[ self setControls : _controls ] ;
2018-06-05 19:40:12 -06:00
[ self setAllowsExternalPlayback : _allowsExternalPlayback ] ;
2015-04-07 21:38:16 -06:00
}
2015-04-04 18:55:37 -06:00
2015-04-08 00:49:14 -06:00
- ( void ) setRepeat : ( BOOL ) repeat {
2015-06-16 02:07:50 -06:00
_repeat = repeat ;
2015-03-31 00:29:15 -06:00
}
2018-07-17 15:14:21 -06:00
- ( void ) setMediaSelectionTrackForCharacteristic : ( AVMediaCharacteristic ) characteristic
withCriteria : ( NSDictionary * ) criteria
{
NSString * type = criteria [ @ "type" ] ;
AVMediaSelectionGroup * group = [ _player . currentItem . asset
mediaSelectionGroupForMediaCharacteristic : characteristic ] ;
AVMediaSelectionOption * mediaOption ;
2018-08-09 10:58:03 -06:00
2018-07-17 15:14:21 -06:00
if ( [ type isEqualToString : @ "disabled" ] ) {
2018-09-04 16:44:19 -06:00
// Do nothing . We want to ensure option is nil
2018-07-17 15:14:21 -06:00
} else if ( [ type isEqualToString : @ "language" ] || [ type isEqualToString : @ "title" ] ) {
NSString * value = criteria [ @ "value" ] ;
for ( int i = 0 ; i < group . options . count ; + + i ) {
AVMediaSelectionOption * currentOption = [ group . options objectAtIndex : i ] ;
NSString * optionValue ;
if ( [ type isEqualToString : @ "language" ] ) {
2018-09-04 16:44:19 -06:00
optionValue = [ currentOption extendedLanguageTag ] ;
2018-07-17 15:14:21 -06:00
} else {
2018-09-04 16:44:19 -06:00
optionValue = [ [ [ currentOption commonMetadata ]
valueForKey : @ "value" ]
objectAtIndex : 0 ] ;
2018-07-17 15:14:21 -06:00
}
if ( [ value isEqualToString : optionValue ] ) {
2018-09-04 16:44:19 -06:00
mediaOption = currentOption ;
break ;
2018-07-17 15:14:21 -06:00
}
2018-08-09 10:58:03 -06:00
}
2018-09-04 16:44:19 -06:00
// } else if ( [ type isEqualToString : @ "default" ] ) {
// option = group . defaultOption ; * /
2018-07-17 15:14:21 -06:00
} else if ( [ type isEqualToString : @ "index" ] ) {
if ( [ criteria [ @ "value" ] isKindOfClass : [ NSNumber class ] ] ) {
2018-09-04 16:44:19 -06:00
int index = [ criteria [ @ "value" ] intValue ] ;
if ( group . options . count > index ) {
mediaOption = [ group . options objectAtIndex : index ] ;
}
2018-07-17 15:14:21 -06:00
}
} else { // default . invalid type or "system"
2018-09-04 16:44:19 -06:00
[ _player . currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup : group ] ;
return ;
2018-07-17 15:14:21 -06:00
}
2018-08-09 10:58:03 -06:00
2018-07-17 15:14:21 -06:00
// If a match isn ' t found , option will be nil and text tracks will be disabled
[ _player . currentItem selectMediaOption : mediaOption inMediaSelectionGroup : group ] ;
}
- ( void ) setSelectedAudioTrack : ( NSDictionary * ) selectedAudioTrack {
_selectedAudioTrack = selectedAudioTrack ;
[ self setMediaSelectionTrackForCharacteristic : AVMediaCharacteristicAudible
2018-09-04 16:44:19 -06:00
withCriteria : _selectedAudioTrack ] ;
2018-07-17 15:14:21 -06:00
}
2018-07-06 17:38:24 -06:00
- ( void ) setSelectedTextTrack : ( NSDictionary * ) selectedTextTrack {
2018-07-09 17:28:38 -06:00
_selectedTextTrack = selectedTextTrack ;
2018-07-17 15:14:21 -06:00
if ( _textTracks ) { // sideloaded text tracks
2018-07-09 17:28:38 -06:00
[ self setSideloadedText ] ;
2018-07-17 15:14:21 -06:00
} else { // text tracks included in the HLS playlist
[ self setMediaSelectionTrackForCharacteristic : AVMediaCharacteristicLegible
withCriteria : _selectedTextTrack ] ;
2018-07-09 17:28:38 -06:00
}
2018-06-21 10:08:37 -06:00
}
2018-07-09 12:20:32 -06:00
- ( void ) setSideloadedText {
2018-06-21 10:08:37 -06:00
NSString * type = _selectedTextTrack [ @ "type" ] ;
2018-07-29 18:42:09 -06:00
NSArray * textTracks = [ self getTextTrackInfo ] ;
2018-06-21 10:08:37 -06:00
2018-07-09 17:28:38 -06:00
// The first few tracks will be audio & video track
int firstTextIndex = 0 ;
for ( firstTextIndex = 0 ; firstTextIndex < _player . currentItem . tracks . count ; + + firstTextIndex ) {
if ( [ _player . currentItem . tracks [ firstTextIndex ] . assetTrack hasMediaCharacteristic : AVMediaCharacteristicLegible ] ) {
break ;
}
}
2018-07-29 18:42:09 -06:00
int selectedTrackIndex = RCTVideoUnset ;
2018-06-21 10:08:37 -06:00
if ( [ type isEqualToString : @ "disabled" ] ) {
// Do nothing . We want to ensure option is nil
} else if ( [ type isEqualToString : @ "language" ] ) {
NSString * selectedValue = _selectedTextTrack [ @ "value" ] ;
for ( int i = 0 ; i < textTracks . count ; + + i ) {
2018-07-09 12:20:32 -06:00
NSDictionary * currentTextTrack = [ textTracks objectAtIndex : i ] ;
if ( [ selectedValue isEqualToString : currentTextTrack [ @ "language" ] ] ) {
2018-07-09 17:28:38 -06:00
selectedTrackIndex = i ;
2018-06-21 10:08:37 -06:00
break ;
}
}
} else if ( [ type isEqualToString : @ "title" ] ) {
NSString * selectedValue = _selectedTextTrack [ @ "value" ] ;
for ( int i = 0 ; i < textTracks . count ; + + i ) {
2018-07-09 12:20:32 -06:00
NSDictionary * currentTextTrack = [ textTracks objectAtIndex : i ] ;
if ( [ selectedValue isEqualToString : currentTextTrack [ @ "title" ] ] ) {
2018-07-09 17:28:38 -06:00
selectedTrackIndex = i ;
2018-06-21 10:08:37 -06:00
break ;
}
}
} else if ( [ type isEqualToString : @ "index" ] ) {
if ( [ _selectedTextTrack [ @ "value" ] isKindOfClass : [ NSNumber class ] ] ) {
int index = [ _selectedTextTrack [ @ "value" ] intValue ] ;
if ( textTracks . count > index ) {
2018-07-09 17:28:38 -06:00
selectedTrackIndex = index ;
2018-06-21 10:08:37 -06:00
}
}
2018-07-31 17:56:19 -06:00
}
// in the situation that a selected text track is not available ( eg . specifies a textTrack not available )
if ( ! [ type isEqualToString : @ "disabled" ] && selectedTrackIndex = = RCTVideoUnset ) {
2018-07-29 18:42:09 -06:00
CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics ( kMACaptionAppearanceDomainUser ) ;
NSArray * captionSettings = ( __bridge NSArray * ) captioningMediaCharacteristics ;
if ( [ captionSettings containsObject : AVMediaCharacteristicTranscribesSpokenDialogForAccessibility ] ) {
selectedTrackIndex = 0 ; // If we can ' t find a match , use the first available track
NSString * systemLanguage = [ [ NSLocale preferredLanguages ] firstObject ] ;
for ( int i = 0 ; i < textTracks . count ; + + i ) {
NSDictionary * currentTextTrack = [ textTracks objectAtIndex : i ] ;
if ( [ systemLanguage isEqualToString : currentTextTrack [ @ "language" ] ] ) {
selectedTrackIndex = i ;
break ;
2018-07-09 12:20:32 -06:00
}
2018-06-21 10:08:37 -06:00
}
2018-07-29 18:42:09 -06:00
}
2018-06-21 10:08:37 -06:00
}
2018-08-09 10:58:03 -06:00
2018-07-09 17:28:38 -06:00
for ( int i = firstTextIndex ; i < _player . currentItem . tracks . count ; + + i ) {
2018-07-29 18:42:09 -06:00
BOOL isEnabled = NO ;
if ( selectedTrackIndex ! = RCTVideoUnset ) {
isEnabled = i = = selectedTrackIndex + firstTextIndex ;
}
2018-07-09 17:28:38 -06:00
[ _player . currentItem . tracks [ i ] setEnabled : isEnabled ] ;
}
2018-06-21 10:08:37 -06:00
}
2018-07-09 12:20:32 -06:00
- ( void ) setStreamingText {
2018-06-21 10:08:37 -06:00
NSString * type = _selectedTextTrack [ @ "type" ] ;
2018-06-02 03:24:13 -06:00
AVMediaSelectionGroup * group = [ _player . currentItem . asset
mediaSelectionGroupForMediaCharacteristic : AVMediaCharacteristicLegible ] ;
2018-07-09 12:20:32 -06:00
AVMediaSelectionOption * mediaOption ;
2018-06-21 10:08:37 -06:00
2018-06-02 03:24:13 -06:00
if ( [ type isEqualToString : @ "disabled" ] ) {
// Do nothing . We want to ensure option is nil
} else if ( [ type isEqualToString : @ "language" ] || [ type isEqualToString : @ "title" ] ) {
2018-06-21 10:08:37 -06:00
NSString * value = _selectedTextTrack [ @ "value" ] ;
2018-06-02 03:24:13 -06:00
for ( int i = 0 ; i < group . options . count ; + + i ) {
AVMediaSelectionOption * currentOption = [ group . options objectAtIndex : i ] ;
NSString * optionValue ;
if ( [ type isEqualToString : @ "language" ] ) {
optionValue = [ currentOption extendedLanguageTag ] ;
} else {
optionValue = [ [ [ currentOption commonMetadata ]
valueForKey : @ "value" ]
objectAtIndex : 0 ] ;
}
if ( [ value isEqualToString : optionValue ] ) {
2018-07-09 12:20:32 -06:00
mediaOption = currentOption ;
2018-06-02 03:24:13 -06:00
break ;
}
}
2018-06-21 10:08:37 -06:00
// } else if ( [ type isEqualToString : @ "default" ] ) {
// option = group . defaultOption ; * /
2018-06-02 03:24:13 -06:00
} else if ( [ type isEqualToString : @ "index" ] ) {
2018-06-21 10:08:37 -06:00
if ( [ _selectedTextTrack [ @ "value" ] isKindOfClass : [ NSNumber class ] ] ) {
int index = [ _selectedTextTrack [ @ "value" ] intValue ] ;
2018-06-02 03:24:13 -06:00
if ( group . options . count > index ) {
2018-07-09 12:20:32 -06:00
mediaOption = [ group . options objectAtIndex : index ] ;
2018-06-02 03:24:13 -06:00
}
}
} else { // default . invalid type or "system"
2018-06-02 20:41:25 -06:00
[ _player . currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup : group ] ;
2018-06-02 03:24:13 -06:00
return ;
}
// If a match isn ' t found , option will be nil and text tracks will be disabled
2018-07-09 12:20:32 -06:00
[ _player . currentItem selectMediaOption : mediaOption inMediaSelectionGroup : group ] ;
2018-06-02 03:24:13 -06:00
}
2018-06-21 10:08:37 -06:00
- ( void ) setTextTracks : ( NSArray * ) textTracks ;
{
_textTracks = textTracks ;
2018-08-09 10:58:03 -06:00
2018-07-09 12:20:32 -06:00
// in case textTracks was set after selectedTextTrack
if ( _selectedTextTrack ) [ self setSelectedTextTrack : _selectedTextTrack ] ;
2018-06-21 10:08:37 -06:00
}
2018-07-17 15:14:21 -06:00
- ( NSArray * ) getAudioTrackInfo
{
NSMutableArray * audioTracks = [ [ NSMutableArray alloc ] init ] ;
AVMediaSelectionGroup * group = [ _player . currentItem . asset
mediaSelectionGroupForMediaCharacteristic : AVMediaCharacteristicAudible ] ;
for ( int i = 0 ; i < group . options . count ; + + i ) {
AVMediaSelectionOption * currentOption = [ group . options objectAtIndex : i ] ;
NSString * title = @ "" ;
NSArray * values = [ [ currentOption commonMetadata ] valueForKey : @ "value" ] ;
if ( values . count > 0 ) {
2018-09-04 16:44:19 -06:00
title = [ values objectAtIndex : 0 ] ;
2018-07-17 15:14:21 -06:00
}
NSString * language = [ currentOption extendedLanguageTag ] ? [ currentOption extendedLanguageTag ] : @ "" ;
NSDictionary * audioTrack = @ {
2018-09-04 16:44:19 -06:00
@ "index" : [ NSNumber numberWithInt : i ] ,
@ "title" : title ,
@ "language" : language
} ;
2018-07-17 15:14:21 -06:00
[ audioTracks addObject : audioTrack ] ;
}
return audioTracks ;
}
2018-06-11 21:55:23 -06:00
- ( NSArray * ) getTextTrackInfo
{
2018-06-21 10:08:37 -06:00
// if sideloaded , textTracks will already be set
if ( _textTracks ) return _textTracks ;
// if streaming video , we extract the text tracks
2018-06-11 21:55:23 -06:00
NSMutableArray * textTracks = [ [ NSMutableArray alloc ] init ] ;
AVMediaSelectionGroup * group = [ _player . currentItem . asset
mediaSelectionGroupForMediaCharacteristic : AVMediaCharacteristicLegible ] ;
for ( int i = 0 ; i < group . options . count ; + + i ) {
AVMediaSelectionOption * currentOption = [ group . options objectAtIndex : i ] ;
2018-07-09 12:20:32 -06:00
NSString * title = @ "" ;
NSArray * values = [ [ currentOption commonMetadata ] valueForKey : @ "value" ] ;
if ( values . count > 0 ) {
title = [ values objectAtIndex : 0 ] ;
}
NSString * language = [ currentOption extendedLanguageTag ] ? [ currentOption extendedLanguageTag ] : @ "" ;
2018-06-11 21:55:23 -06:00
NSDictionary * textTrack = @ {
@ "index" : [ NSNumber numberWithInt : i ] ,
@ "title" : title ,
2018-07-09 12:20:32 -06:00
@ "language" : language
2018-06-11 21:55:23 -06:00
} ;
[ textTracks addObject : textTrack ] ;
}
return textTracks ;
}
2016-03-31 12:34:22 -06:00
- ( BOOL ) getFullscreen
{
2018-02-28 09:42:49 -07:00
return _fullscreenPlayerPresented ;
2016-03-31 12:34:22 -06:00
}
2018-08-09 10:58:03 -06:00
- ( void ) setFullscreen : ( BOOL ) fullscreen {
2018-10-09 17:01:41 -06:00
if ( fullscreen && ! _fullscreenPlayerPresented && _player )
2018-08-09 10:58:03 -06:00
{
// Ensure player view controller is not null
if ( ! _playerViewController )
2016-03-31 12:34:22 -06:00
{
2018-02-28 09:42:49 -07:00
[ self usePlayerViewController ] ;
2016-03-31 12:34:22 -06:00
}
2018-02-28 09:42:49 -07:00
// Set presentation style to fullscreen
[ _playerViewController setModalPresentationStyle : UIModalPresentationFullScreen ] ;
// Find the nearest view controller
UIViewController * viewController = [ self firstAvailableUIViewController ] ;
if ( ! viewController )
2016-03-31 12:34:22 -06:00
{
2018-02-28 09:42:49 -07:00
UIWindow * keyWindow = [ [ UIApplication sharedApplication ] keyWindow ] ;
viewController = keyWindow . rootViewController ;
if ( viewController . childViewControllers . count > 0 )
{
viewController = viewController . childViewControllers . lastObject ;
}
2016-03-31 12:34:22 -06:00
}
2018-02-28 09:42:49 -07:00
if ( viewController )
2016-03-31 12:34:22 -06:00
{
2018-02-28 09:42:49 -07:00
_presentingViewController = viewController ;
if ( self . onVideoFullscreenPlayerWillPresent ) {
self . onVideoFullscreenPlayerWillPresent ( @ { @ "target" : self . reactTag } ) ;
}
[ viewController presentViewController : _playerViewController animated : true completion : ^ {
_playerViewController . showsPlaybackControls = YES ;
2018-10-07 21:24:50 -06:00
_fullscreenPlayerPresented = fullscreen ;
2018-10-18 16:21:46 -06:00
_playerViewController . autorotate = _fullscreenAutorotate ;
2018-02-28 09:42:49 -07:00
if ( self . onVideoFullscreenPlayerDidPresent ) {
self . onVideoFullscreenPlayerDidPresent ( @ { @ "target" : self . reactTag } ) ;
}
} ] ;
2016-03-31 12:34:22 -06:00
}
2018-02-28 09:42:49 -07:00
}
2018-10-07 21:24:50 -06:00
else if ( ! fullscreen && _fullscreenPlayerPresented )
2018-02-28 09:42:49 -07:00
{
[ self videoPlayerViewControllerWillDismiss : _playerViewController ] ;
[ _presentingViewController dismissViewControllerAnimated : true completion : ^ {
[ self videoPlayerViewControllerDidDismiss : _playerViewController ] ;
} ] ;
}
2016-03-31 12:34:22 -06:00
}
2018-10-18 16:21:46 -06:00
- ( void ) setFullscreenAutorotate : ( BOOL ) autorotate {
_fullscreenAutorotate = autorotate ;
if ( _fullscreenPlayerPresented ) {
_playerViewController . autorotate = autorotate ;
}
}
2018-10-07 21:24:50 -06:00
- ( void ) setFullscreenOrientation : ( NSString * ) orientation {
_fullscreenOrientation = orientation ;
if ( _fullscreenPlayerPresented ) {
_playerViewController . preferredOrientation = orientation ;
}
2016-03-31 12:34:22 -06:00
}
2015-12-22 16:39:04 -07:00
- ( void ) usePlayerViewController
{
2018-02-28 09:42:49 -07:00
if ( _player )
{
2019-06-19 07:18:25 -06:00
if ( ! _playerViewController ) {
_playerViewController = [ self createPlayerViewController : _player withPlayerItem : _playerItem ] ;
}
2018-02-28 09:42:49 -07:00
// to prevent video from being animated when resizeMode is ' cover '
// resize mode must be set before subview is added
[ self setResizeMode : _resizeMode ] ;
2019-01-21 15:05:03 -07:00
if ( _controls ) {
UIViewController * viewController = [ self reactViewController ] ;
[ viewController addChildViewController : _playerViewController ] ;
[ self addSubview : _playerViewController . view ] ;
}
2019-06-19 07:18:25 -06:00
[ _playerViewController addObserver : self forKeyPath : readyForDisplayKeyPath options : NSKeyValueObservingOptionNew context : nil ] ;
2019-01-21 15:05:03 -07:00
[ _playerViewController . contentOverlayView addObserver : self forKeyPath : @ "frame" options : NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context : NULL ] ;
2018-02-28 09:42:49 -07:00
}
2015-12-22 16:39:04 -07:00
}
- ( void ) usePlayerLayer
{
2018-07-09 12:20:32 -06:00
if ( _player )
{
_playerLayer = [ AVPlayerLayer playerLayerWithPlayer : _player ] ;
_playerLayer . frame = self . bounds ;
_playerLayer . needsDisplayOnBoundsChange = YES ;
// to prevent video from being animated when resizeMode is ' cover '
// resize mode must be set before layer is added
[ self setResizeMode : _resizeMode ] ;
[ _playerLayer addObserver : self forKeyPath : readyForDisplayKeyPath options : NSKeyValueObservingOptionNew context : nil ] ;
_playerLayerObserverSet = YES ;
[ self . layer addSublayer : _playerLayer ] ;
self . layer . needsDisplayOnBoundsChange = YES ;
2019-03-11 20:55:36 -06:00
# if TARGET_OS _IOS
2018-10-26 14:33:03 -06:00
[ self setupPipController ] ;
2019-03-11 20:55:36 -06:00
# endif
2018-07-09 12:20:32 -06:00
}
2015-12-22 16:39:04 -07:00
}
- ( void ) setControls : ( BOOL ) controls
{
2018-02-28 09:42:49 -07:00
if ( _controls ! = controls || ( ! _playerLayer && ! _playerViewController ) )
{
_controls = controls ;
if ( _controls )
2015-12-22 16:39:04 -07:00
{
2018-02-28 09:42:49 -07:00
[ self removePlayerLayer ] ;
[ self usePlayerViewController ] ;
}
else
{
[ _playerViewController . view removeFromSuperview ] ;
_playerViewController = nil ;
[ self usePlayerLayer ] ;
2015-12-22 16:39:04 -07:00
}
2018-02-28 09:42:49 -07:00
}
2015-12-22 16:39:04 -07:00
}
2016-10-01 12:23:50 -06:00
- ( void ) setProgressUpdateInterval : ( float ) progressUpdateInterval
{
_progressUpdateInterval = progressUpdateInterval ;
2018-08-09 10:58:03 -06:00
2017-09-28 19:37:52 -06:00
if ( _timeObserver ) {
[ self removePlayerTimeObserver ] ;
[ self addPlayerTimeObserver ] ;
}
2016-10-01 12:23:50 -06:00
}
2016-04-28 06:25:45 -06:00
- ( void ) removePlayerLayer
{
2018-07-09 12:20:32 -06:00
[ _playerLayer removeFromSuperlayer ] ;
if ( _playerLayerObserverSet ) {
2018-06-21 10:08:37 -06:00
[ _playerLayer removeObserver : self forKeyPath : readyForDisplayKeyPath ] ;
2018-07-09 12:20:32 -06:00
_playerLayerObserverSet = NO ;
}
_playerLayer = nil ;
2016-04-28 06:25:45 -06:00
}
2016-04-01 02:52:05 -06:00
# pragma mark - RCTVideoPlayerViewControllerDelegate
- ( void ) videoPlayerViewControllerWillDismiss : ( AVPlayerViewController * ) playerViewController
{
2018-02-28 09:42:49 -07:00
if ( _playerViewController = = playerViewController && _fullscreenPlayerPresented && self . onVideoFullscreenPlayerWillDismiss )
{
2019-11-19 13:47:15 -07:00
@ try {
[ _playerViewController . contentOverlayView removeObserver : self forKeyPath : @ "frame" ] ;
[ _playerViewController removeObserver : self forKeyPath : readyForDisplayKeyPath ] ;
} @ catch ( id anException ) {
}
2018-02-28 09:42:49 -07:00
self . onVideoFullscreenPlayerWillDismiss ( @ { @ "target" : self . reactTag } ) ;
}
2016-04-01 02:52:05 -06:00
}
- ( void ) videoPlayerViewControllerDidDismiss : ( AVPlayerViewController * ) playerViewController
{
2018-02-28 09:42:49 -07:00
if ( _playerViewController = = playerViewController && _fullscreenPlayerPresented )
{
_fullscreenPlayerPresented = false ;
_presentingViewController = nil ;
_playerViewController = nil ;
[ self applyModifiers ] ;
if ( self . onVideoFullscreenPlayerDidDismiss ) {
self . onVideoFullscreenPlayerDidDismiss ( @ { @ "target" : self . reactTag } ) ;
2016-04-01 02:52:05 -06:00
}
2018-02-28 09:42:49 -07:00
}
2016-04-01 02:52:05 -06:00
}
2018-11-06 07:38:28 -07:00
- ( void ) setFilter : ( NSString * ) filterName {
_filterName = filterName ;
2018-12-13 20:30:38 -07:00
if ( ! _filterEnabled ) {
return ;
} else if ( [ [ _source objectForKey : @ "uri" ] rangeOfString : @ "m3u8" ] . location ! = NSNotFound ) {
return ; // filters don ' t work for HLS . . . return
2018-12-20 11:45:14 -07:00
} else if ( ! _playerItem . asset ) {
2018-12-12 23:21:19 -07:00
return ;
2018-10-27 19:43:14 -06:00
}
2018-12-20 11:45:14 -07:00
2018-12-12 23:21:19 -07:00
CIFilter * filter = [ CIFilter filterWithName : filterName ] ;
_playerItem . videoComposition = [ AVVideoComposition
2018-12-20 11:45:14 -07:00
videoCompositionWithAsset : _playerItem . asset
2018-12-12 23:21:19 -07:00
applyingCIFiltersWithHandler : ^ ( AVAsynchronousCIImageFilteringRequest * _Nonnull request ) {
if ( filter = = nil ) {
[ request finishWithImage : request . sourceImage context : nil ] ;
} else {
CIImage * image = request . sourceImage . imageByClampingToExtent ;
[ filter setValue : image forKey : kCIInputImageKey ] ;
CIImage * output = [ filter . outputImage imageByCroppingToRect : request . sourceImage . extent ] ;
[ request finishWithImage : output context : nil ] ;
}
} ] ;
2018-10-27 19:43:14 -06:00
}
2018-12-13 20:30:38 -07:00
- ( void ) setFilterEnabled : ( BOOL ) filterEnabled {
_filterEnabled = filterEnabled ;
}
2019-12-09 21:40:49 -07:00
- ( void ) setAdTagUrl : ( NSString * ) adTagUrl {
_adTagUrl = adTagUrl ;
}
2015-04-08 11:34:27 -06:00
# pragma mark - React View Management
2015-04-08 09:46:13 -06:00
2015-05-30 13:50:45 -06:00
- ( void ) insertReactSubview : ( UIView * ) view atIndex : ( NSInteger ) atIndex
{
2015-12-22 16:39:04 -07:00
// We are early in the game and somebody wants to set a subview .
// That can only be in the context of playerViewController .
if ( ! _controls && ! _playerLayer && ! _playerViewController )
{
[ self setControls : true ] ;
}
2018-02-28 09:42:49 -07:00
2015-12-22 16:39:04 -07:00
if ( _controls )
{
2018-02-28 09:42:49 -07:00
view . frame = self . bounds ;
[ _playerViewController . contentOverlayView insertSubview : view atIndex : atIndex ] ;
2015-12-22 16:39:04 -07:00
}
else
{
2018-02-28 09:42:49 -07:00
RCTLogError ( @ "video cannot have any subviews" ) ;
2015-12-22 16:39:04 -07:00
}
2015-03-30 23:07:55 -06:00
return ;
}
2015-05-30 13:50:45 -06:00
- ( void ) removeReactSubview : ( UIView * ) subview
{
2015-12-22 16:39:04 -07:00
if ( _controls )
{
2018-02-28 09:42:49 -07:00
[ subview removeFromSuperview ] ;
2015-12-22 16:39:04 -07:00
}
else
{
RCTLogError ( @ "video cannot have any subviews" ) ;
}
2015-03-30 23:07:55 -06:00
return ;
}
2015-05-30 13:50:45 -06:00
- ( void ) layoutSubviews
{
2015-03-30 23:07:55 -06:00
[ super layoutSubviews ] ;
2015-12-22 16:39:04 -07:00
if ( _controls )
{
_playerViewController . view . frame = self . bounds ;
2018-02-28 09:42:49 -07:00
2015-12-22 16:39:04 -07:00
// also adjust all subviews of contentOverlayView
for ( UIView * subview in _playerViewController . contentOverlayView . subviews ) {
subview . frame = self . bounds ;
}
}
else
{
2018-02-28 09:42:49 -07:00
[ CATransaction begin ] ;
[ CATransaction setAnimationDuration : 0 ] ;
_playerLayer . frame = self . bounds ;
[ CATransaction commit ] ;
2015-12-22 16:39:04 -07:00
}
2015-04-04 18:55:37 -06:00
}
2015-04-08 11:34:27 -06:00
# pragma mark - Lifecycle
2015-04-08 09:46:13 -06:00
2015-05-30 13:50:45 -06:00
- ( void ) removeFromSuperview
{
2015-04-10 01:15:19 -06:00
[ _player pause ] ;
2016-05-17 01:40:58 -06:00
if ( _playbackRateObserverRegistered ) {
2016-05-19 12:27:23 -06:00
[ _player removeObserver : self forKeyPath : playbackRate context : nil ] ;
2016-05-17 01:38:35 -06:00
_playbackRateObserverRegistered = NO ;
2016-05-17 01:40:58 -06:00
}
2018-09-13 06:06:12 -06:00
if ( _isExternalPlaybackActiveObserverRegistered ) {
[ _player removeObserver : self forKeyPath : externalPlaybackActive context : nil ] ;
_isExternalPlaybackActiveObserverRegistered = NO ;
}
2015-04-10 01:15:19 -06:00
_player = nil ;
2018-02-28 09:42:49 -07:00
2016-04-28 06:25:45 -06:00
[ self removePlayerLayer ] ;
2018-02-28 09:42:49 -07:00
2019-01-21 15:05:03 -07:00
[ _playerViewController . contentOverlayView removeObserver : self forKeyPath : @ "frame" ] ;
2019-06-19 07:18:25 -06:00
[ _playerViewController removeObserver : self forKeyPath : readyForDisplayKeyPath ] ;
2015-12-22 16:39:04 -07:00
[ _playerViewController . view removeFromSuperview ] ;
2019-07-03 00:44:55 -06:00
_playerViewController . rctDelegate = nil ;
_playerViewController . player = nil ;
2015-12-22 16:39:04 -07:00
_playerViewController = nil ;
2018-02-28 09:42:49 -07:00
2015-12-22 16:39:04 -07:00
[ self removePlayerTimeObserver ] ;
2015-08-05 06:52:30 -06:00
[ self removePlayerItemObservers ] ;
2018-02-28 09:42:49 -07:00
2015-04-10 01:15:19 -06:00
_eventDispatcher = nil ;
2015-04-04 18:55:37 -06:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
2018-02-28 09:42:49 -07:00
2015-06-24 22:26:36 -06:00
[ super removeFromSuperview ] ;
2015-03-30 23:07:55 -06:00
}
2018-10-27 19:43:14 -06:00
# pragma mark - Export
2018-10-26 07:21:41 -06:00
- ( void ) save : ( NSDictionary * ) options resolve : ( RCTPromiseResolveBlock ) resolve reject : ( RCTPromiseRejectBlock ) reject {
AVAsset * asset = _playerItem . asset ;
2018-10-25 07:56:20 -06:00
2018-10-26 07:21:41 -06:00
if ( asset ! = nil ) {
AVAssetExportSession * exportSession = [ AVAssetExportSession
exportSessionWithAsset : asset presetName : AVAssetExportPresetHighestQuality ] ;
if ( exportSession ! = nil ) {
NSString * path = nil ;
NSArray * array = NSSearchPathForDirectoriesInDomains ( NSCachesDirectory , NSUserDomainMask , YES ) ;
2018-11-06 19:33:33 -07:00
path = [ self generatePathInDirectory : [ [ self cacheDirectoryPath ] stringByAppendingPathComponent : @ "Videos" ]
2018-10-26 07:21:41 -06:00
withExtension : @ ".mp4" ] ;
NSURL * url = [ NSURL fileURLWithPath : path ] ;
exportSession . outputFileType = AVFileTypeMPEG4 ;
exportSession . outputURL = url ;
exportSession . videoComposition = _playerItem . videoComposition ;
exportSession . shouldOptimizeForNetworkUse = true ;
[ exportSession exportAsynchronouslyWithCompletionHandler : ^ {
switch ( [ exportSession status ] ) {
case AVAssetExportSessionStatusFailed :
reject ( @ "ERROR_COULD_NOT_EXPORT_VIDEO" , @ "Could not export video" , exportSession . error ) ;
break ;
case AVAssetExportSessionStatusCancelled :
reject ( @ "ERROR_EXPORT_SESSION_CANCELLED" , @ "Export session was cancelled" , exportSession . error ) ;
break ;
default :
resolve ( @ { @ "uri" : url . absoluteString } ) ;
break ;
}
} ] ;
} else {
2018-10-25 07:56:20 -06:00
2018-10-26 07:21:41 -06:00
reject ( @ "ERROR_COULD_NOT_CREATE_EXPORT_SESSION" , @ "Could not create export session" , nil ) ;
}
} else {
2018-10-25 07:56:20 -06:00
2018-10-26 07:21:41 -06:00
reject ( @ "ERROR_ASSET_NIL" , @ "Asset is nil" , nil ) ;
2018-10-25 07:56:20 -06:00
2018-10-26 07:21:41 -06:00
}
}
- ( BOOL ) ensureDirExistsWithPath : ( NSString * ) path {
BOOL isDir = NO ;
NSError * error ;
BOOL exists = [ [ NSFileManager defaultManager ] fileExistsAtPath : path isDirectory : & isDir ] ;
if ( ! ( exists && isDir ) ) {
[ [ NSFileManager defaultManager ] createDirectoryAtPath : path withIntermediateDirectories : YES attributes : nil error : & error ] ;
if ( error ) {
return NO ;
}
}
return YES ;
}
- ( NSString * ) generatePathInDirectory : ( NSString * ) directory withExtension : ( NSString * ) extension {
NSString * fileName = [ [ [ NSUUID UUID ] UUIDString ] stringByAppendingString : extension ] ;
[ self ensureDirExistsWithPath : directory ] ;
return [ directory stringByAppendingPathComponent : fileName ] ;
}
2018-10-25 07:56:20 -06:00
2018-10-26 07:21:41 -06:00
- ( NSString * ) cacheDirectoryPath {
NSArray * array = NSSearchPathForDirectoriesInDomains ( NSCachesDirectory , NSUserDomainMask , YES ) ;
return array [ 0 ] ;
2015-03-30 23:07:55 -06:00
}
2018-10-26 14:33:03 -06:00
# pragma mark - Picture in Picture
2019-03-11 20:55:36 -06:00
# if TARGET_OS _IOS
2018-10-26 14:33:03 -06:00
- ( void ) pictureInPictureControllerDidStopPictureInPicture : ( AVPictureInPictureController * ) pictureInPictureController {
2018-11-26 15:23:04 -07:00
if ( self . onPictureInPictureStatusChanged ) {
self . onPictureInPictureStatusChanged ( @ {
2019-03-11 20:55:36 -06:00
@ "isActive" : [ NSNumber numberWithBool : false ]
} ) ;
2018-10-26 14:33:03 -06:00
}
}
- ( void ) pictureInPictureControllerDidStartPictureInPicture : ( AVPictureInPictureController * ) pictureInPictureController {
2018-11-26 15:23:04 -07:00
if ( self . onPictureInPictureStatusChanged ) {
self . onPictureInPictureStatusChanged ( @ {
2019-03-11 20:55:36 -06:00
@ "isActive" : [ NSNumber numberWithBool : true ]
} ) ;
2018-10-26 14:33:03 -06:00
}
}
- ( void ) pictureInPictureControllerWillStopPictureInPicture : ( AVPictureInPictureController * ) pictureInPictureController {
}
- ( void ) pictureInPictureControllerWillStartPictureInPicture : ( AVPictureInPictureController * ) pictureInPictureController {
}
- ( void ) pictureInPictureController : ( AVPictureInPictureController * ) pictureInPictureController failedToStartPictureInPictureWithError : ( NSError * ) error {
}
- ( void ) pictureInPictureController : ( AVPictureInPictureController * ) pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler : ( void ( ^ ) ( BOOL ) ) completionHandler {
NSAssert ( _restoreUserInterfaceForPIPStopCompletionHandler = = NULL , @ "restoreUserInterfaceForPIPStopCompletionHandler was not called after picture in picture was exited." ) ;
2018-11-26 15:23:04 -07:00
if ( self . onRestoreUserInterfaceForPictureInPictureStop ) {
self . onRestoreUserInterfaceForPictureInPictureStop ( @ { } ) ;
}
2018-10-26 14:33:03 -06:00
_restoreUserInterfaceForPIPStopCompletionHandler = completionHandler ;
}
2019-03-11 20:55:36 -06:00
# endif
2018-10-26 14:33:03 -06:00
2015-03-30 23:07:55 -06:00
@ end