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
2018-10-27 19:50:33 -06:00
# define DebugLog ( . . . ) NSLog ( __VA _ARGS __ )
2018-08-08 16:37:18 -06:00
# else
2018-10-27 19:50:33 -06:00
# define DebugLog ( . . . ) ( void ) 0
2018-08-08 16:37:18 -06:00
# endif
2018-10-27 19:43:14 -06:00
@ implementation RCTVideo
{
2018-10-27 19:50:33 -06:00
AVPlayer * _player ;
AVPlayerItem * _playerItem ;
2018-12-13 20:30:38 -07:00
NSDictionary * _source ;
2018-10-27 19:50:33 -06:00
BOOL _playerItemObserversSet ;
BOOL _playerBufferEmpty ;
AVPlayerLayer * _playerLayer ;
BOOL _playerLayerObserverSet ;
RCTVideoPlayerViewController * _playerViewController ;
NSURL * _videoURL ;
/ * Required to publish events * /
RCTEventDispatcher * _eventDispatcher ;
BOOL _playbackRateObserverRegistered ;
BOOL _isExternalPlaybackActiveObserverRegistered ;
BOOL _videoLoadStarted ;
bool _pendingSeek ;
float _pendingSeekTime ;
float _lastSeekTime ;
/ * For sending videoProgress events * /
Float64 _progressUpdateInterval ;
BOOL _controls ;
id _timeObserver ;
/ * 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
2018-10-27 19:50:33 -06:00
BOOL _muted ;
BOOL _paused ;
BOOL _repeat ;
BOOL _allowsExternalPlayback ;
NSArray * _textTracks ;
NSDictionary * _selectedTextTrack ;
NSDictionary * _selectedAudioTrack ;
BOOL _playbackStalled ;
BOOL _playInBackground ;
BOOL _playWhenInactive ;
NSString * _ignoreSilentSwitch ;
NSString * _resizeMode ;
BOOL _fullscreen ;
2018-10-18 16:21:46 -06:00
BOOL _fullscreenAutorotate ;
2018-10-27 19:50:33 -06:00
NSString * _fullscreenOrientation ;
BOOL _fullscreenPlayerPresented ;
2018-11-06 07:38:28 -07:00
NSString * _filterName ;
2018-12-13 20:30:38 -07:00
BOOL _filterEnabled ;
2018-10-27 19:50:33 -06:00
UIViewController * _presentingViewController ;
2018-07-18 18:06:09 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
2018-10-27 19:50:33 -06:00
RCTVideoCache * _videoCache ;
2018-07-18 18:06:09 -06:00
# endif
2015-03-30 23:07:55 -06:00
}
2018-10-27 19:43:14 -06:00
- ( instancetype ) initWithEventDispatcher : ( RCTEventDispatcher * ) eventDispatcher
{
2018-10-27 19:50:33 -06:00
if ( ( self = [ super init ] ) ) {
_eventDispatcher = eventDispatcher ;
_playbackRateObserverRegistered = NO ;
_isExternalPlaybackActiveObserverRegistered = NO ;
_playbackStalled = NO ;
_rate = 1.0 ;
_volume = 1.0 ;
_resizeMode = @ "AVLayerVideoGravityResizeAspectFill" ;
2018-10-18 16:21:46 -06:00
_fullscreenAutorotate = YES ;
2018-10-27 19:50:33 -06:00
_fullscreenOrientation = @ "all" ;
_pendingSeek = false ;
_pendingSeekTime = 0.0 f ;
_lastSeekTime = 0.0 f ;
_progressUpdateInterval = 250 ;
_controls = NO ;
_playerBufferEmpty = YES ;
_playInBackground = false ;
_allowsExternalPlayback = YES ;
_playWhenInactive = false ;
_ignoreSilentSwitch = @ "inherit" ; // inherit , ignore , obey
2018-07-18 18:06:09 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
2018-10-27 19:50:33 -06:00
_videoCache = [ RCTVideoCache sharedInstance ] ;
2018-07-18 18:06:09 -06:00
# endif
2018-10-27 19:50:33 -06:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationWillResignActive : )
name : UIApplicationWillResignActiveNotification
object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationDidEnterBackground : )
name : UIApplicationDidEnterBackgroundNotification
object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationWillEnterForeground : )
name : UIApplicationWillEnterForegroundNotification
object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( audioRouteChanged : )
name : AVAudioSessionRouteChangeNotification
object : nil ] ;
}
return self ;
2015-03-30 23:07:55 -06:00
}
2018-10-27 19:43:14 -06:00
- ( RCTVideoPlayerViewController * ) createPlayerViewController : ( AVPlayer * ) player
withPlayerItem : ( AVPlayerItem * ) playerItem {
RCTVideoPlayerViewController * viewController = [ [ RCTVideoPlayerViewController alloc ] init ] ;
2018-10-07 21:24:50 -06:00
viewController . showsPlaybackControls = YES ;
viewController . rctDelegate = self ;
viewController . preferredOrientation = _fullscreenOrientation ;
2018-10-27 19:50:33 -06:00
2018-10-07 21:24:50 -06:00
viewController . view . frame = self . bounds ;
viewController . player = player ;
viewController . view . frame = self . bounds ;
return viewController ;
2015-12-22 16:39:04 -07:00
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Get the duration for a AVPlayerItem .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * /
2018-10-27 19:43:14 -06:00
- ( CMTime ) playerItemDuration
{
2018-10-27 19:50:33 -06:00
AVPlayerItem * playerItem = [ _player currentItem ] ;
if ( playerItem . status = = AVPlayerItemStatusReadyToPlay )
{
return ( [ playerItem duration ] ) ;
}
return ( kCMTimeInvalid ) ;
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:43:14 -06:00
- ( CMTimeRange ) playerItemSeekableTimeRange
{
2018-10-27 19:50:33 -06:00
AVPlayerItem * playerItem = [ _player currentItem ] ;
if ( playerItem . status = = AVPlayerItemStatusReadyToPlay )
{
return [ playerItem seekableTimeRanges ] . firstObject . CMTimeRangeValue ;
}
return ( kCMTimeRangeZero ) ;
2016-09-06 20:23:13 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) addPlayerTimeObserver
{
2018-10-27 19:50:33 -06:00
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000 ;
// @ see endScrubbing in AVPlayerDemoPlaybackViewController . m
// of https : // developer . apple . com / library / ios / samplecode / AVPlayerDemo / Introduction / Intro . html
__weak RCTVideo * weakSelf = self ;
_timeObserver = [ _player addPeriodicTimeObserverForInterval : CMTimeMakeWithSeconds ( progressUpdateIntervalMS , NSEC_PER _SEC )
queue : NULL
usingBlock : ^ ( CMTime time ) { [ weakSelf sendProgressUpdate ] ; }
] ;
2017-09-28 19:37:26 -06:00
}
2015-12-22 16:39:04 -07:00
/ * Cancels the previously registered time observer . * /
2018-10-27 19:43:14 -06:00
- ( void ) removePlayerTimeObserver
{
2018-10-27 19:50:33 -06:00
if ( _timeObserver )
{
[ _player removeTimeObserver : _timeObserver ] ;
_timeObserver = nil ;
}
2015-12-22 16:39:04 -07:00
}
# pragma mark - Progress
2018-10-27 19:43:14 -06:00
- ( void ) dealloc
{
2018-10-27 19:50:33 -06:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
[ self removePlayerLayer ] ;
[ self removePlayerItemObservers ] ;
[ _player removeObserver : self forKeyPath : playbackRate context : nil ] ;
2015-06-26 12:41:41 -06:00
}
2015-06-25 16:40:46 -06:00
# pragma mark - App lifecycle handlers
2018-10-27 19:43:14 -06:00
- ( void ) applicationWillResignActive : ( NSNotification * ) notification
{
2018-10-27 19:50:33 -06:00
if ( _playInBackground || _playWhenInactive || _paused ) return ;
[ _player pause ] ;
[ _player setRate : 0.0 ] ;
2015-06-25 16:40:46 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) applicationDidEnterBackground : ( NSNotification * ) notification
{
2018-10-27 19:50:33 -06:00
if ( _playInBackground ) {
// Needed to play sound in background . See https : // developer . apple . com / library / ios / qa / qa1668 / _index . html
[ _playerLayer setPlayer : nil ] ;
}
2016-04-29 01:46:28 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) applicationWillEnterForeground : ( NSNotification * ) notification
{
2018-10-27 19:50:33 -06:00
[ self applyModifiers ] ;
if ( _playInBackground ) {
[ _playerLayer setPlayer : _player ] ;
}
2015-06-25 16:40:46 -06:00
}
2018-07-12 22:48:58 -06:00
# pragma mark - Audio events
2018-10-27 19:43:14 -06:00
- ( void ) audioRouteChanged : ( NSNotification * ) notification
{
2018-09-04 16:44:19 -06:00
NSNumber * reason = [ [ notification userInfo ] objectForKey : AVAudioSessionRouteChangeReasonKey ] ;
NSNumber * previousRoute = [ [ notification userInfo ] objectForKey : AVAudioSessionRouteChangePreviousRouteKey ] ;
if ( reason . unsignedIntValue = = AVAudioSessionRouteChangeReasonOldDeviceUnavailable ) {
2018-10-27 19:50:33 -06:00
self . onVideoAudioBecomingNoisy ( @ { @ "target" : self . reactTag } ) ;
2018-09-04 16:44:19 -06:00
}
2018-07-12 22:48:58 -06:00
}
2015-04-08 09:46:13 -06:00
# pragma mark - Progress
2018-10-27 19:43:14 -06:00
- ( void ) sendProgressUpdate
{
2018-10-27 19:50:33 -06: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 ] } ] ;
if ( currentTimeSecs >= 0 && self . onVideoProgress ) {
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
* /
2018-10-27 19:43:14 -06:00
- ( NSNumber * ) calculatePlayableDuration
{
2018-10-27 19:50:33 -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 ;
}
} ] ;
Float64 playableDuration = CMTimeGetSeconds ( CMTimeRangeGetEnd ( effectiveTimeRange ) ) ;
if ( playableDuration > 0 ) {
return [ NSNumber numberWithFloat : playableDuration ] ;
2015-06-17 23:08:01 -06:00
}
2018-10-27 19:50:33 -06:00
}
return [ NSNumber numberWithInteger : 0 ] ;
2015-06-10 15:29:38 -06:00
}
2018-10-27 19:43:14 -06:00
- ( NSNumber * ) calculateSeekableDuration
{
2018-10-27 19:50:33 -06: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
}
2018-10-27 19:43:14 -06:00
- ( void ) addPlayerItemObservers
{
2018-10-27 19:50:33 -06:00
[ _playerItem addObserver : self forKeyPath : statusKeyPath options : 0 context : nil ] ;
[ _playerItem addObserver : self forKeyPath : playbackBufferEmptyKeyPath options : 0 context : nil ] ;
[ _playerItem addObserver : self forKeyPath : playbackLikelyToKeepUpKeyPath options : 0 context : nil ] ;
[ _playerItem addObserver : self forKeyPath : timedMetadata options : NSKeyValueObservingOptionNew context : nil ] ;
_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 * /
2018-10-27 19:43:14 -06:00
- ( void ) removePlayerItemObservers
{
2018-10-27 19:50:33 -06:00
if ( _playerItemObserversSet ) {
[ _playerItem removeObserver : self forKeyPath : statusKeyPath ] ;
[ _playerItem removeObserver : self forKeyPath : playbackBufferEmptyKeyPath ] ;
[ _playerItem removeObserver : self forKeyPath : playbackLikelyToKeepUpKeyPath ] ;
[ _playerItem removeObserver : self forKeyPath : timedMetadata ] ;
_playerItemObserversSet = NO ;
}
2015-05-30 13:47:14 -06:00
}
2015-04-08 09:46:13 -06:00
# pragma mark - Player and source
2018-10-27 19:43:14 -06:00
- ( void ) setSrc : ( NSDictionary * ) source
{
2018-12-13 20:30:38 -07:00
_source = source ;
2018-10-27 19:50:33 -06:00
[ self removePlayerLayer ] ;
[ self removePlayerTimeObserver ] ;
[ self removePlayerItemObservers ] ;
dispatch_after ( dispatch_time ( DISPATCH_TIME _NOW , ( int64_t ) 0 ) , dispatch_get _main _queue ( ) , ^ {
// perform on next run loop , otherwise other passed react - props may not be set
[ self playerItemForSource : source withCallback : ^ ( AVPlayerItem * playerItem ) {
_playerItem = playerItem ;
[ 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-10-29 10:53:52 -06:00
2018-10-27 19:50:33 -06:00
[ _player pause ] ;
[ _playerViewController . view removeFromSuperview ] ;
_playerViewController = nil ;
if ( _playbackRateObserverRegistered ) {
[ _player removeObserver : self forKeyPath : playbackRate context : nil ] ;
_playbackRateObserverRegistered = NO ;
}
if ( _isExternalPlaybackActiveObserverRegistered ) {
[ _player removeObserver : self forKeyPath : externalPlaybackActive context : nil ] ;
_isExternalPlaybackActiveObserverRegistered = NO ;
}
_player = [ AVPlayer playerWithPlayerItem : _playerItem ] ;
_player . actionAtItemEnd = AVPlayerActionAtItemEndNone ;
[ _player addObserver : self forKeyPath : playbackRate options : 0 context : nil ] ;
_playbackRateObserverRegistered = YES ;
[ _player addObserver : self forKeyPath : externalPlaybackActive options : 0 context : nil ] ;
_isExternalPlaybackActiveObserverRegistered = YES ;
[ self addPlayerTimeObserver ] ;
// Perform on next run loop , otherwise onVideoLoadStart is nil
if ( self . onVideoLoadStart ) {
id uri = [ source objectForKey : @ "uri" ] ;
id type = [ source objectForKey : @ "type" ] ;
self . onVideoLoadStart ( @ { @ "src" : @ {
@ "uri" : uri ? uri : [ NSNull null ] ,
@ "type" : type ? type : [ NSNull null ] ,
@ "isNetwork" : [ NSNumber numberWithBool : ( bool ) [ source objectForKey : @ "isNetwork" ] ] } ,
@ "target" : self . reactTag
} ) ;
}
} ] ;
} ) ;
_videoLoadStarted = YES ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( NSURL * ) urlFilePath : ( NSString * ) filepath {
2018-10-27 19:50:33 -06:00
if ( [ filepath containsString : @ "file://" ] ) {
return [ NSURL URLWithString : filepath ] ;
}
// if no file found , check if the file exists in the Document directory
NSArray * paths = NSSearchPathForDirectoriesInDomains ( NSDocumentDirectory , NSUserDomainMask , YES ) ;
NSString * relativeFilePath = [ filepath lastPathComponent ] ;
// the file may be multiple levels below the documents directory
NSArray * fileComponents = [ filepath componentsSeparatedByString : @ "Documents/" ] ;
if ( fileComponents . count > 1 ) {
relativeFilePath = [ fileComponents objectAtIndex : 1 ] ;
}
NSString * path = [ paths . firstObject stringByAppendingPathComponent : relativeFilePath ] ;
if ( [ [ NSFileManager defaultManager ] fileExistsAtPath : path ] ) {
return [ NSURL fileURLWithPath : path ] ;
}
return nil ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) playerItemPrepareText : ( AVAsset * ) asset assetOptions : ( NSDictionary * __nullable ) assetOptions withCallback : ( void ( ^ ) ( AVPlayerItem * ) ) handler
{
2018-10-18 16:21:46 -06:00
if ( ! _textTracks || _textTracks . count = = 0 ) {
2018-10-27 19:50:33 -06:00
handler ( [ AVPlayerItem playerItemWithAsset : asset ] ) ;
return ;
}
2018-10-18 16:21:46 -06:00
// AVPlayer can ' t airplay AVMutableCompositions
_allowsExternalPlayback = NO ;
2018-10-27 19:50:33 -06:00
// sideload text tracks
AVMutableComposition * mixComposition = [ [ AVMutableComposition alloc ] init ] ;
AVAssetTrack * videoAsset = [ asset tracksWithMediaType : AVMediaTypeVideo ] . firstObject ;
AVMutableCompositionTrack * videoCompTrack = [ mixComposition addMutableTrackWithMediaType : AVMediaTypeVideo preferredTrackID : kCMPersistentTrackID_Invalid ] ;
[ videoCompTrack insertTimeRange : CMTimeRangeMake ( kCMTimeZero , videoAsset . timeRange . duration )
ofTrack : videoAsset
atTime : kCMTimeZero
error : nil ] ;
AVAssetTrack * audioAsset = [ asset tracksWithMediaType : AVMediaTypeAudio ] . firstObject ;
AVMutableCompositionTrack * audioCompTrack = [ mixComposition addMutableTrackWithMediaType : AVMediaTypeAudio preferredTrackID : kCMPersistentTrackID_Invalid ] ;
[ audioCompTrack insertTimeRange : CMTimeRangeMake ( kCMTimeZero , videoAsset . timeRange . duration )
ofTrack : audioAsset
atTime : kCMTimeZero
error : nil ] ;
NSMutableArray * validTextTracks = [ NSMutableArray array ] ;
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 ] ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:50:33 -06:00
AVAssetTrack * textTrackAsset = [ textURLAsset tracksWithMediaType : AVMediaTypeText ] . firstObject ;
if ( ! textTrackAsset ) continue ; // fix when there ' s no textTrackAsset
[ validTextTracks addObject : [ _textTracks objectAtIndex : i ] ] ;
AVMutableCompositionTrack * textCompTrack = [ mixComposition
addMutableTrackWithMediaType : AVMediaTypeText
preferredTrackID : kCMPersistentTrackID_Invalid ] ;
[ textCompTrack insertTimeRange : CMTimeRangeMake ( kCMTimeZero , videoAsset . timeRange . duration )
ofTrack : textTrackAsset
atTime : kCMTimeZero
error : nil ] ;
}
if ( validTextTracks . count ! = _textTracks . count ) {
[ self setTextTracks : validTextTracks ] ;
}
handler ( [ AVPlayerItem playerItemWithAsset : mixComposition ] ) ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) playerItemForSource : ( NSDictionary * ) source withCallback : ( void ( ^ ) ( AVPlayerItem * ) ) handler
{
2018-10-27 19:50:33 -06:00
bool isNetwork = [ RCTConvert BOOL : [ source objectForKey : @ "isNetwork" ] ] ;
bool isAsset = [ RCTConvert BOOL : [ source objectForKey : @ "isAsset" ] ] ;
NSString * uri = [ source objectForKey : @ "uri" ] ;
NSString * type = [ source objectForKey : @ "type" ] ;
NSURL * url = isNetwork || isAsset
? [ NSURL URLWithString : uri ]
: [ [ NSURL alloc ] initFileURLWithPath : [ [ NSBundle mainBundle ] pathForResource : uri ofType : type ] ] ;
NSMutableDictionary * assetOptions = [ [ NSMutableDictionary alloc ] init ] ;
if ( isNetwork ) {
/ * 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-10-26 07:21:41 -06:00
# if __has _include ( < react - native - video / RCTVideoCache . h > )
2018-10-27 19:50:33 -06:00
if ( ! _textTracks ) {
2018-10-27 19:43:14 -06:00
/ * The DVURLAsset created by cache doesn ' t have a tracksWithMediaType property , so trying
* to bring in the text track code will crash . I suspect this is because the asset hasn ' t fully loaded .
* Until this is fixed , we need to bypass caching when text tracks are specified .
* /
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 ) ;
[ 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
2018-10-27 19:50:33 -06:00
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : url options : assetOptions ] ;
2018-10-26 07:21:41 -06:00
[ self playerItemPrepareText : asset assetOptions : assetOptions withCallback : handler ] ;
2018-10-27 19:50:33 -06:00
return ;
} else if ( isAsset ) {
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : url options : nil ] ;
[ self playerItemPrepareText : asset assetOptions : assetOptions withCallback : handler ] ;
return ;
}
AVURLAsset * asset = [ AVURLAsset URLAssetWithURL : [ [ NSURL alloc ] initFileURLWithPath : [ [ NSBundle mainBundle ] pathForResource : uri ofType : type ] ] options : nil ] ;
[ 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-10-27 19:50:33 -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 🎉" ) ;
} ] ;
}
# endif
2018-10-27 19:43:14 -06:00
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary * ) change context : ( void * ) context
{
2018-10-27 19:50:33 -06:00
if ( object = = _playerItem ) {
// When timeMetadata is read the event onTimedMetadata is triggered
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 ) {
NSString * value = ( NSString * ) item . value ;
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-10-27 19:50:33 -06:00
self . onTimedMetadata ( @ {
@ "target" : self . reactTag ,
@ "metadata" : array
} ) ;
}
}
if ( [ keyPath isEqualToString : statusKeyPath ] ) {
// Handle player item status change .
if ( _playerItem . status = = AVPlayerItemStatusReadyToPlay ) {
float duration = CMTimeGetSeconds ( _playerItem . asset . duration ) ;
if ( isnan ( duration ) ) {
duration = 0.0 ;
2016-04-08 03:10:22 -06:00
}
2018-10-27 19:50:33 -06:00
NSObject * width = @ "undefined" ;
NSObject * height = @ "undefined" ;
NSString * orientation = @ "undefined" ;
if ( [ _playerItem . asset tracksWithMediaType : AVMediaTypeVideo ] . count > 0 ) {
AVAssetTrack * videoTrack = [ [ _playerItem . asset tracksWithMediaType : AVMediaTypeVideo ] objectAtIndex : 0 ] ;
width = [ NSNumber numberWithFloat : videoTrack . naturalSize . width ] ;
height = [ NSNumber numberWithFloat : videoTrack . naturalSize . height ] ;
CGAffineTransform preferredTransform = [ videoTrack preferredTransform ] ;
if ( ( videoTrack . naturalSize . width = = preferredTransform . tx
&& videoTrack . naturalSize . height = = preferredTransform . ty )
|| ( preferredTransform . tx = = 0 && preferredTransform . ty = = 0 ) )
{
orientation = @ "landscape" ;
} else {
orientation = @ "portrait" ;
}
2018-02-28 09:42:49 -07:00
}
2018-10-27 19:50:33 -06:00
if ( self . onVideoLoad && _videoLoadStarted ) {
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" : @ {
@ "width" : width ,
@ "height" : height ,
@ "orientation" : orientation
} ,
@ "audioTracks" : [ self getAudioTrackInfo ] ,
@ "textTracks" : [ self getTextTrackInfo ] ,
@ "target" : self . reactTag } ) ;
}
_videoLoadStarted = NO ;
[ self attachListeners ] ;
[ self applyModifiers ] ;
} else if ( _playerItem . status = = AVPlayerItemStatusFailed && self . onVideoError ) {
self . onVideoError ( @ { @ "error" : @ { @ "code" : [ NSNumber numberWithInteger : _playerItem . error . code ] ,
@ "domain" : _playerItem . error . domain } ,
2018-02-28 09:42:49 -07:00
@ "target" : self . reactTag } ) ;
2018-10-27 19:50:33 -06:00
}
} else if ( [ keyPath isEqualToString : playbackBufferEmptyKeyPath ] ) {
_playerBufferEmpty = YES ;
self . onVideoBuffer ( @ { @ "isBuffering" : @ ( YES ) , @ "target" : self . reactTag } ) ;
} else if ( [ keyPath isEqualToString : playbackLikelyToKeepUpKeyPath ] ) {
// Continue playing ( or not if paused ) after being paused due to hitting an unbuffered zone .
if ( ( ! ( _controls || _fullscreenPlayerPresented ) || _playerBufferEmpty ) && _playerItem . playbackLikelyToKeepUp ) {
[ self setPaused : _paused ] ;
}
_playerBufferEmpty = NO ;
self . onVideoBuffer ( @ { @ "isBuffering" : @ ( NO ) , @ "target" : self . reactTag } ) ;
}
} else if ( object = = _playerLayer ) {
if ( [ keyPath isEqualToString : readyForDisplayKeyPath ] && [ change objectForKey : NSKeyValueChangeNewKey ] ) {
if ( [ change objectForKey : NSKeyValueChangeNewKey ] && self . onReadyForDisplay ) {
self . onReadyForDisplay ( @ { @ "target" : self . reactTag } ) ;
}
}
} else if ( object = = _player ) {
if ( [ keyPath isEqualToString : playbackRate ] ) {
if ( self . onPlaybackRateChange ) {
self . onPlaybackRateChange ( @ { @ "playbackRate" : [ NSNumber numberWithFloat : _player . rate ] ,
@ "target" : self . reactTag } ) ;
}
if ( _playbackStalled && _player . rate > 0 ) {
if ( self . onPlaybackResume ) {
self . onPlaybackResume ( @ { @ "playbackRate" : [ NSNumber numberWithFloat : _player . rate ] ,
@ "target" : self . reactTag } ) ;
2018-10-27 19:43:14 -06:00
}
2018-10-27 19:50:33 -06:00
_playbackStalled = NO ;
}
}
else if ( [ keyPath isEqualToString : externalPlaybackActive ] ) {
if ( self . onVideoExternalPlaybackChange ) {
self . onVideoExternalPlaybackChange ( @ { @ "isExternalPlaybackActive" : [ NSNumber numberWithBool : _player . isExternalPlaybackActive ] ,
@ "target" : self . reactTag } ) ;
2018-10-26 07:21:41 -06:00
}
2015-04-08 00:49:14 -06:00
}
2018-10-27 19:50:33 -06:00
} else {
[ super observeValueForKeyPath : keyPath ofObject : object change : change context : context ] ;
}
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) attachListeners
{
2018-10-27 19:50:33 -06:00
// listen for end of file
[ [ NSNotificationCenter defaultCenter ] removeObserver : self
name : AVPlayerItemDidPlayToEndTimeNotification
object : [ _player currentItem ] ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( playerItemDidReachEnd : )
name : AVPlayerItemDidPlayToEndTimeNotification
object : [ _player currentItem ] ] ;
[ [ NSNotificationCenter defaultCenter ] removeObserver : self
name : AVPlayerItemPlaybackStalledNotification
object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( playbackStalled : )
name : AVPlayerItemPlaybackStalledNotification
object : nil ] ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) playbackStalled : ( NSNotification * ) notification
{
2018-10-27 19:50:33 -06:00
if ( self . onPlaybackStalled ) {
self . onPlaybackStalled ( @ { @ "target" : self . reactTag } ) ;
}
_playbackStalled = YES ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) playerItemDidReachEnd : ( NSNotification * ) notification
{
2018-10-27 19:50:33 -06:00
if ( self . onVideoEnd ) {
self . onVideoEnd ( @ { @ "target" : self . reactTag } ) ;
}
if ( _repeat ) {
AVPlayerItem * item = [ notification object ] ;
[ item seekToTime : kCMTimeZero ] ;
[ self applyModifiers ] ;
} else {
[ self removePlayerTimeObserver ] ;
}
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
2018-10-27 19:43:14 -06:00
- ( void ) setResizeMode : ( NSString * ) mode
{
2018-10-27 19:50:33 -06:00
if ( _controls )
{
_playerViewController . videoGravity = mode ;
}
else
{
_playerLayer . videoGravity = mode ;
}
_resizeMode = mode ;
2015-04-04 18:55:37 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setPlayInBackground : ( BOOL ) playInBackground
{
2018-10-27 19:50:33 -06:00
_playInBackground = playInBackground ;
2015-10-30 03:34:54 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setAllowsExternalPlayback : ( BOOL ) allowsExternalPlayback
{
2018-09-04 16:44:19 -06:00
_allowsExternalPlayback = allowsExternalPlayback ;
_player . allowsExternalPlayback = _allowsExternalPlayback ;
2018-06-05 19:40:12 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setPlayWhenInactive : ( BOOL ) playWhenInactive
{
2018-10-27 19:50:33 -06:00
_playWhenInactive = playWhenInactive ;
2016-04-29 05:55:34 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setIgnoreSilentSwitch : ( NSString * ) ignoreSilentSwitch
{
2018-10-27 19:50:33 -06:00
_ignoreSilentSwitch = ignoreSilentSwitch ;
[ self applyModifiers ] ;
2017-04-20 12:10:06 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setPaused : ( BOOL ) paused
{
2018-10-27 19:50:33 -06:00
if ( paused ) {
[ _player pause ] ;
[ _player setRate : 0.0 ] ;
} else {
if ( [ _ignoreSilentSwitch isEqualToString : @ "ignore" ] ) {
[ [ AVAudioSession sharedInstance ] setCategory : AVAudioSessionCategoryPlayback error : nil ] ;
} else if ( [ _ignoreSilentSwitch isEqualToString : @ "obey" ] ) {
[ [ AVAudioSession sharedInstance ] setCategory : AVAudioSessionCategoryAmbient error : nil ] ;
2017-04-20 12:10:06 -06:00
}
2018-10-27 19:50:33 -06:00
[ _player play ] ;
[ _player setRate : _rate ] ;
}
_paused = paused ;
2015-04-05 11:17:03 -06:00
}
2018-10-27 19:43:14 -06:00
- ( float ) getCurrentTime
{
2018-10-27 19:50:33 -06:00
return _playerItem ! = NULL ? CMTimeGetSeconds ( _playerItem . currentTime ) : 0 ;
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setCurrentTime : ( float ) currentTime
{
2018-10-27 19:50:33 -06:00
NSDictionary * info = @ {
@ "time" : [ NSNumber numberWithFloat : currentTime ] ,
@ "tolerance" : [ NSNumber numberWithInt : 100 ]
} ;
[ self setSeek : info ] ;
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setSeek : ( NSDictionary * ) info
{
2018-10-27 19:50:33 -06:00
NSNumber * seekTime = info [ @ "time" ] ;
NSNumber * seekTolerance = info [ @ "tolerance" ] ;
int timeScale = 1000 ;
AVPlayerItem * item = _player . currentItem ;
if ( item && item . status = = AVPlayerItemStatusReadyToPlay ) {
// TODO check loadedTimeRanges
CMTime cmSeekTime = CMTimeMakeWithSeconds ( [ seekTime floatValue ] , timeScale ) ;
CMTime current = item . currentTime ;
// TODO figure out a good tolerance level
CMTime tolerance = CMTimeMake ( [ seekTolerance floatValue ] , timeScale ) ;
BOOL wasPaused = _paused ;
if ( CMTimeCompare ( current , cmSeekTime ) ! = 0 ) {
if ( ! wasPaused ) [ _player pause ] ;
[ _player seekToTime : cmSeekTime toleranceBefore : tolerance toleranceAfter : tolerance completionHandler : ^ ( BOOL finished ) {
if ( ! _timeObserver ) {
[ self addPlayerTimeObserver ] ;
2017-01-16 09:27:08 -07:00
}
2018-10-27 19:50:33 -06:00
if ( ! wasPaused ) {
[ self setPaused : false ] ;
}
if ( self . onVideoSeek ) {
self . onVideoSeek ( @ { @ "currentTime" : [ NSNumber numberWithFloat : CMTimeGetSeconds ( item . currentTime ) ] ,
@ "seekTime" : seekTime ,
@ "target" : self . reactTag } ) ;
}
} ] ;
_pendingSeek = false ;
2015-04-08 16:15:57 -06:00
}
2018-10-27 19:50:33 -06:00
} else {
// TODO : See if this makes sense and if so , actually implement it
_pendingSeek = true ;
_pendingSeekTime = [ seekTime floatValue ] ;
}
2015-04-08 16:15:57 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setRate : ( float ) rate
{
2018-10-27 19:50:33 -06:00
_rate = rate ;
[ self applyModifiers ] ;
2015-04-07 21:38:16 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setMuted : ( BOOL ) muted
{
2018-10-27 19:50:33 -06:00
_muted = muted ;
[ self applyModifiers ] ;
2015-04-07 21:38:16 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setVolume : ( float ) volume
{
2018-10-27 19:50:33 -06:00
_volume = volume ;
[ self applyModifiers ] ;
2015-04-07 21:38:16 -06:00
}
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
}
2018-10-27 19:43:14 -06:00
- ( void ) applyModifiers
{
2018-10-27 19:50:33 -06:00
if ( _muted ) {
[ _player setVolume : 0 ] ;
[ _player setMuted : YES ] ;
} else {
[ _player setVolume : _volume ] ;
[ _player setMuted : NO ] ;
}
2018-12-22 16:33:43 -07:00
[ self setMaxBitRate : _maxBitRate ] ;
2018-10-27 19:50:33 -06:00
[ self setSelectedAudioTrack : _selectedAudioTrack ] ;
[ self setSelectedTextTrack : _selectedTextTrack ] ;
[ self setResizeMode : _resizeMode ] ;
[ self setRepeat : _repeat ] ;
[ self setPaused : _paused ] ;
[ self setControls : _controls ] ;
[ 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 {
2018-10-27 19:50:33 -06:00
_repeat = repeat ;
2015-03-31 00:29:15 -06:00
}
2018-07-17 15:14:21 -06:00
- ( void ) setMediaSelectionTrackForCharacteristic : ( AVMediaCharacteristic ) characteristic
2018-10-27 19:43:14 -06:00
withCriteria : ( NSDictionary * ) criteria
{
2018-09-04 16:44:19 -06:00
NSString * type = criteria [ @ "type" ] ;
AVMediaSelectionGroup * group = [ _player . currentItem . asset
2018-10-27 19:50:33 -06:00
mediaSelectionGroupForMediaCharacteristic : characteristic ] ;
2018-09-04 16:44:19 -06:00
AVMediaSelectionOption * mediaOption ;
2018-10-27 19:50:33 -06:00
2018-09-04 16:44:19 -06:00
if ( [ type isEqualToString : @ "disabled" ] ) {
2018-10-27 19:50:33 -06:00
// Do nothing . We want to ensure option is nil
2018-09-04 16:44:19 -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-10-27 19:50:33 -06:00
optionValue = [ currentOption extendedLanguageTag ] ;
2018-09-04 16:44:19 -06:00
} else {
2018-10-27 19:50:33 -06:00
optionValue = [ [ [ currentOption commonMetadata ]
valueForKey : @ "value" ]
objectAtIndex : 0 ] ;
2018-09-04 16:44:19 -06:00
}
if ( [ value isEqualToString : optionValue ] ) {
2018-10-27 19:50:33 -06:00
mediaOption = currentOption ;
break ;
2018-09-04 16:44:19 -06:00
}
2018-10-27 19:50:33 -06:00
}
// } else if ( [ type isEqualToString : @ "default" ] ) {
// option = group . defaultOption ; * /
2018-09-04 16:44:19 -06:00
} else if ( [ type isEqualToString : @ "index" ] ) {
if ( [ criteria [ @ "value" ] isKindOfClass : [ NSNumber class ] ] ) {
2018-10-27 19:50:33 -06:00
int index = [ criteria [ @ "value" ] intValue ] ;
if ( group . options . count > index ) {
mediaOption = [ group . options objectAtIndex : index ] ;
}
2018-09-04 16:44:19 -06:00
}
} else { // default . invalid type or "system"
2018-10-27 19:50:33 -06:00
[ _player . currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup : group ] ;
return ;
2018-08-09 10:58:03 -06:00
}
2018-10-27 19:50:33 -06:00
2018-09-04 16:44:19 -06:00
// If a match isn ' t found , option will be nil and text tracks will be disabled
[ _player . currentItem selectMediaOption : mediaOption inMediaSelectionGroup : group ] ;
2018-07-17 15:14:21 -06:00
}
- ( void ) setSelectedAudioTrack : ( NSDictionary * ) selectedAudioTrack {
2018-09-04 16:44:19 -06:00
_selectedAudioTrack = selectedAudioTrack ;
[ self setMediaSelectionTrackForCharacteristic : AVMediaCharacteristicAudible
withCriteria : _selectedAudioTrack ] ;
2018-07-17 15:14:21 -06:00
}
2018-07-06 17:38:24 -06:00
- ( void ) setSelectedTextTrack : ( NSDictionary * ) selectedTextTrack {
2018-10-27 19:50:33 -06:00
_selectedTextTrack = selectedTextTrack ;
if ( _textTracks ) { // sideloaded text tracks
[ self setSideloadedText ] ;
} else { // text tracks included in the HLS playlist
[ self setMediaSelectionTrackForCharacteristic : AVMediaCharacteristicLegible
withCriteria : _selectedTextTrack ] ;
}
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setSideloadedText {
2018-10-27 19:50:33 -06:00
NSString * type = _selectedTextTrack [ @ "type" ] ;
NSArray * textTracks = [ self getTextTrackInfo ] ;
// 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-06-21 10:08:37 -06:00
}
2018-10-27 19:50:33 -06:00
}
int selectedTrackIndex = RCTVideoUnset ;
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 ) {
NSDictionary * currentTextTrack = [ textTracks objectAtIndex : i ] ;
if ( [ selectedValue isEqualToString : currentTextTrack [ @ "language" ] ] ) {
selectedTrackIndex = i ;
break ;
}
2018-06-21 10:08:37 -06:00
}
2018-10-27 19:50:33 -06:00
} else if ( [ type isEqualToString : @ "title" ] ) {
NSString * selectedValue = _selectedTextTrack [ @ "value" ] ;
for ( int i = 0 ; i < textTracks . count ; + + i ) {
NSDictionary * currentTextTrack = [ textTracks objectAtIndex : i ] ;
if ( [ selectedValue isEqualToString : currentTextTrack [ @ "title" ] ] ) {
selectedTrackIndex = i ;
break ;
}
2018-07-29 18:42:09 -06:00
}
2018-10-27 19:50:33 -06:00
} else if ( [ type isEqualToString : @ "index" ] ) {
if ( [ _selectedTextTrack [ @ "value" ] isKindOfClass : [ NSNumber class ] ] ) {
int index = [ _selectedTextTrack [ @ "value" ] intValue ] ;
if ( textTracks . count > index ) {
selectedTrackIndex = index ;
}
}
}
// in the situation that a selected text track is not available ( eg . specifies a textTrack not available )
if ( ! [ type isEqualToString : @ "disabled" ] && selectedTrackIndex = = RCTVideoUnset ) {
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-10-26 07:21:41 -06:00
}
2018-10-27 19:50:33 -06:00
}
}
}
for ( int i = firstTextIndex ; i < _player . currentItem . tracks . count ; + + i ) {
BOOL isEnabled = NO ;
if ( selectedTrackIndex ! = RCTVideoUnset ) {
isEnabled = i = = selectedTrackIndex + firstTextIndex ;
2018-07-29 18:42:09 -06:00
}
2018-10-27 19:50:33 -06:00
[ _player . currentItem . tracks [ i ] setEnabled : isEnabled ] ;
}
2018-06-21 10:08:37 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setStreamingText {
2018-10-27 19:50:33 -06:00
NSString * type = _selectedTextTrack [ @ "type" ] ;
AVMediaSelectionGroup * group = [ _player . currentItem . asset
mediaSelectionGroupForMediaCharacteristic : AVMediaCharacteristicLegible ] ;
AVMediaSelectionOption * mediaOption ;
if ( [ type isEqualToString : @ "disabled" ] ) {
// Do nothing . We want to ensure option is nil
} else if ( [ type isEqualToString : @ "language" ] || [ type isEqualToString : @ "title" ] ) {
NSString * value = _selectedTextTrack [ @ "value" ] ;
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 ]
2018-06-02 03:24:13 -06:00
valueForKey : @ "value" ]
2018-10-27 19:50:33 -06:00
objectAtIndex : 0 ] ;
}
if ( [ value isEqualToString : optionValue ] ) {
mediaOption = currentOption ;
break ;
}
2018-06-02 03:24:13 -06:00
}
2018-10-27 19:50:33 -06:00
// } else if ( [ type isEqualToString : @ "default" ] ) {
// option = group . defaultOption ; * /
} else if ( [ type isEqualToString : @ "index" ] ) {
if ( [ _selectedTextTrack [ @ "value" ] isKindOfClass : [ NSNumber class ] ] ) {
int index = [ _selectedTextTrack [ @ "value" ] intValue ] ;
if ( group . options . count > index ) {
mediaOption = [ group . options objectAtIndex : index ] ;
}
}
} else { // default . invalid type or "system"
[ _player . currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup : group ] ;
return ;
}
// If a match isn ' t found , option will be nil and text tracks will be disabled
[ _player . currentItem selectMediaOption : mediaOption inMediaSelectionGroup : group ] ;
2018-06-02 03:24:13 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setTextTracks : ( NSArray * ) textTracks ;
{
2018-10-27 19:50:33 -06:00
_textTracks = textTracks ;
// in case textTracks was set after selectedTextTrack
if ( _selectedTextTrack ) [ self setSelectedTextTrack : _selectedTextTrack ] ;
2018-06-21 10:08:37 -06:00
}
2018-10-27 19:43:14 -06:00
- ( NSArray * ) getAudioTrackInfo
{
2018-09-04 16:44:19 -06:00
NSMutableArray * audioTracks = [ [ NSMutableArray alloc ] init ] ;
AVMediaSelectionGroup * group = [ _player . currentItem . asset
2018-10-27 19:50:33 -06:00
mediaSelectionGroupForMediaCharacteristic : AVMediaCharacteristicAudible ] ;
2018-09-04 16:44:19 -06:00
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-10-27 19:50:33 -06:00
title = [ values objectAtIndex : 0 ] ;
2018-09-04 16:44:19 -06:00
}
NSString * language = [ currentOption extendedLanguageTag ] ? [ currentOption extendedLanguageTag ] : @ "" ;
NSDictionary * audioTrack = @ {
2018-10-27 19:50:33 -06:00
@ "index" : [ NSNumber numberWithInt : i ] ,
@ "title" : title ,
@ "language" : language
} ;
2018-09-04 16:44:19 -06:00
[ audioTracks addObject : audioTrack ] ;
2018-07-17 15:14:21 -06:00
}
2018-09-04 16:44:19 -06:00
return audioTracks ;
2018-07-17 15:14:21 -06:00
}
2018-10-27 19:43:14 -06:00
- ( NSArray * ) getTextTrackInfo
{
2018-10-27 19:50:33 -06:00
// if sideloaded , textTracks will already be set
if ( _textTracks ) return _textTracks ;
// if streaming video , we extract the text tracks
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 ] ;
NSString * title = @ "" ;
NSArray * values = [ [ currentOption commonMetadata ] valueForKey : @ "value" ] ;
if ( values . count > 0 ) {
title = [ values objectAtIndex : 0 ] ;
2016-03-31 12:34:22 -06:00
}
2018-10-27 19:50:33 -06:00
NSString * language = [ currentOption extendedLanguageTag ] ? [ currentOption extendedLanguageTag ] : @ "" ;
NSDictionary * textTrack = @ {
@ "index" : [ NSNumber numberWithInt : i ] ,
@ "title" : title ,
@ "language" : language
} ;
[ textTracks addObject : textTrack ] ;
}
return textTracks ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( BOOL ) getFullscreen
{
2018-10-27 19:50:33 -06:00
return _fullscreenPlayerPresented ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setFullscreen : ( BOOL ) fullscreen {
2018-10-27 19:50:33 -06:00
if ( fullscreen && ! _fullscreenPlayerPresented && _player )
{
// Ensure player view controller is not null
if ( ! _playerViewController )
2018-10-27 19:43:14 -06:00
{
2018-10-27 19:50:33 -06:00
[ self usePlayerViewController ] ;
2018-10-27 19:43:14 -06:00
}
2018-10-27 19:50:33 -06:00
// Set presentation style to fullscreen
[ _playerViewController setModalPresentationStyle : UIModalPresentationFullScreen ] ;
// Find the nearest view controller
UIViewController * viewController = [ self firstAvailableUIViewController ] ;
if ( ! viewController )
2018-10-27 19:43:14 -06:00
{
2018-10-27 19:50:33 -06:00
UIWindow * keyWindow = [ [ UIApplication sharedApplication ] keyWindow ] ;
viewController = keyWindow . rootViewController ;
if ( viewController . childViewControllers . count > 0 )
{
viewController = viewController . childViewControllers . lastObject ;
}
}
if ( viewController )
{
_presentingViewController = viewController ;
if ( self . onVideoFullscreenPlayerWillPresent ) {
self . onVideoFullscreenPlayerWillPresent ( @ { @ "target" : self . reactTag } ) ;
}
[ viewController presentViewController : _playerViewController animated : true completion : ^ {
_playerViewController . showsPlaybackControls = YES ;
_fullscreenPlayerPresented = fullscreen ;
2018-10-18 16:21:46 -06:00
_playerViewController . autorotate = _fullscreenAutorotate ;
2018-10-27 19:50:33 -06:00
if ( self . onVideoFullscreenPlayerDidPresent ) {
self . onVideoFullscreenPlayerDidPresent ( @ { @ "target" : self . reactTag } ) ;
}
} ] ;
2018-02-28 09:42:49 -07:00
}
2018-10-27 19:50:33 -06:00
}
else if ( ! fullscreen && _fullscreenPlayerPresented )
{
[ 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 {
2018-10-27 19:50:33 -06:00
_fullscreenOrientation = orientation ;
if ( _fullscreenPlayerPresented ) {
_playerViewController . preferredOrientation = orientation ;
}
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:43:14 -06:00
- ( void ) usePlayerViewController
{
2018-10-27 19:50:33 -06:00
if ( _player )
{
_playerViewController = [ self createPlayerViewController : _player withPlayerItem : _playerItem ] ;
// to prevent video from being animated when resizeMode is ' cover '
// resize mode must be set before subview is added
[ self setResizeMode : _resizeMode ] ;
[ self addSubview : _playerViewController . view ] ;
}
2018-10-26 07:21:41 -06:00
}
2018-10-25 07:56:20 -06:00
2018-10-27 19:43:14 -06:00
- ( void ) usePlayerLayer
{
2018-10-27 19:50:33 -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 ;
}
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setControls : ( BOOL ) controls
{
2018-10-27 19:50:33 -06:00
if ( _controls ! = controls || ( ! _playerLayer && ! _playerViewController ) )
{
_controls = controls ;
if ( _controls )
2018-10-27 19:43:14 -06:00
{
2018-10-27 19:50:33 -06:00
[ self removePlayerLayer ] ;
[ self usePlayerViewController ] ;
}
else
{
[ _playerViewController . view removeFromSuperview ] ;
_playerViewController = nil ;
[ self usePlayerLayer ] ;
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:50:33 -06:00
}
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:43:14 -06:00
- ( void ) setProgressUpdateInterval : ( float ) progressUpdateInterval
{
2018-10-27 19:50:33 -06:00
_progressUpdateInterval = progressUpdateInterval ;
if ( _timeObserver ) {
[ self removePlayerTimeObserver ] ;
[ self addPlayerTimeObserver ] ;
}
2016-10-01 12:23:50 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) removePlayerLayer
{
2018-10-27 19:50:33 -06:00
[ _playerLayer removeFromSuperlayer ] ;
if ( _playerLayerObserverSet ) {
[ _playerLayer removeObserver : self forKeyPath : readyForDisplayKeyPath ] ;
_playerLayerObserverSet = NO ;
}
_playerLayer = nil ;
}
# pragma mark - RCTVideoPlayerViewControllerDelegate
- ( void ) videoPlayerViewControllerWillDismiss : ( AVPlayerViewController * ) playerViewController
{
if ( _playerViewController = = playerViewController && _fullscreenPlayerPresented && self . onVideoFullscreenPlayerWillDismiss )
{
self . onVideoFullscreenPlayerWillDismiss ( @ { @ "target" : self . reactTag } ) ;
}
}
- ( void ) videoPlayerViewControllerDidDismiss : ( AVPlayerViewController * ) playerViewController
{
if ( _playerViewController = = playerViewController && _fullscreenPlayerPresented )
{
_fullscreenPlayerPresented = false ;
_presentingViewController = nil ;
_playerViewController = nil ;
[ self applyModifiers ] ;
if ( self . onVideoFullscreenPlayerDidDismiss ) {
self . onVideoFullscreenPlayerDidDismiss ( @ { @ "target" : self . reactTag } ) ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:50:33 -06:00
}
2016-04-28 06:25:45 -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 ;
}
2015-04-08 11:34:27 -06:00
# pragma mark - React View Management
2015-04-08 09:46:13 -06:00
2018-10-27 19:43:14 -06:00
- ( void ) insertReactSubview : ( UIView * ) view atIndex : ( NSInteger ) atIndex
{
2018-10-27 19:50:33 -06: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 ] ;
}
if ( _controls )
{
view . frame = self . bounds ;
[ _playerViewController . contentOverlayView insertSubview : view atIndex : atIndex ] ;
}
else
{
RCTLogError ( @ "video cannot have any subviews" ) ;
}
return ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) removeReactSubview : ( UIView * ) subview
{
2018-10-27 19:50:33 -06:00
if ( _controls )
{
[ subview removeFromSuperview ] ;
}
else
{
RCTLogError ( @ "video cannot have any subviews" ) ;
}
return ;
2018-10-26 07:21:41 -06:00
}
2018-10-27 19:43:14 -06:00
- ( void ) layoutSubviews
{
2018-10-27 19:50:33 -06:00
[ super layoutSubviews ] ;
if ( _controls )
{
_playerViewController . view . frame = self . bounds ;
// also adjust all subviews of contentOverlayView
for ( UIView * subview in _playerViewController . contentOverlayView . subviews ) {
subview . frame = self . bounds ;
2015-12-22 16:39:04 -07:00
}
2018-10-27 19:50:33 -06:00
}
else
{
[ CATransaction begin ] ;
[ CATransaction setAnimationDuration : 0 ] ;
_playerLayer . frame = self . bounds ;
[ CATransaction commit ] ;
}
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
2018-10-27 19:43:14 -06:00
- ( void ) removeFromSuperview
{
2018-10-27 19:50:33 -06:00
[ _player pause ] ;
if ( _playbackRateObserverRegistered ) {
[ _player removeObserver : self forKeyPath : playbackRate context : nil ] ;
_playbackRateObserverRegistered = NO ;
}
if ( _isExternalPlaybackActiveObserverRegistered ) {
[ _player removeObserver : self forKeyPath : externalPlaybackActive context : nil ] ;
_isExternalPlaybackActiveObserverRegistered = NO ;
}
_player = nil ;
[ self removePlayerLayer ] ;
[ _playerViewController . view removeFromSuperview ] ;
_playerViewController = nil ;
[ self removePlayerTimeObserver ] ;
[ self removePlayerItemObservers ] ;
_eventDispatcher = nil ;
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
[ super removeFromSuperview ] ;
2018-10-26 07:21:41 -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-11-26 15:50:31 -07:00
@ end