Add cache property; Make playerItemForSource in 'RCTVideoManager.m' async

This commit is contained in:
Laurin Quast 2018-02-28 17:42:49 +01:00
parent 6b2c1046dd
commit 438aa79494
3 changed files with 283 additions and 266 deletions

View File

@ -247,6 +247,7 @@ export default class Video extends Component {
Video.propTypes = { Video.propTypes = {
/* Native only */ /* Native only */
src: PropTypes.object, src: PropTypes.object,
cache: PropTypes.bool,
seek: PropTypes.number, seek: PropTypes.number,
fullscreen: PropTypes.bool, fullscreen: PropTypes.bool,
onVideoLoadStart: PropTypes.func, onVideoLoadStart: PropTypes.func,

View File

@ -20,20 +20,20 @@ static NSString *const timedMetadata = @"timedMetadata";
AVPlayerLayer *_playerLayer; AVPlayerLayer *_playerLayer;
AVPlayerViewController *_playerViewController; AVPlayerViewController *_playerViewController;
NSURL *_videoURL; NSURL *_videoURL;
/* Required to publish events */ /* Required to publish events */
RCTEventDispatcher *_eventDispatcher; RCTEventDispatcher *_eventDispatcher;
BOOL _playbackRateObserverRegistered; BOOL _playbackRateObserverRegistered;
bool _pendingSeek; bool _pendingSeek;
float _pendingSeekTime; float _pendingSeekTime;
float _lastSeekTime; float _lastSeekTime;
/* For sending videoProgress events */ /* For sending videoProgress events */
Float64 _progressUpdateInterval; Float64 _progressUpdateInterval;
BOOL _controls; BOOL _controls;
id _timeObserver; id _timeObserver;
/* Keep track of any modifiers, need to be applied after each play */ /* Keep track of any modifiers, need to be applied after each play */
float _volume; float _volume;
float _rate; float _rate;
@ -53,7 +53,7 @@ static NSString *const timedMetadata = @"timedMetadata";
{ {
if ((self = [super init])) { if ((self = [super init])) {
_eventDispatcher = eventDispatcher; _eventDispatcher = eventDispatcher;
_playbackRateObserverRegistered = NO; _playbackRateObserverRegistered = NO;
_playbackStalled = NO; _playbackStalled = NO;
_rate = 1.0; _rate = 1.0;
@ -68,34 +68,34 @@ static NSString *const timedMetadata = @"timedMetadata";
_playInBackground = false; _playInBackground = false;
_playWhenInactive = false; _playWhenInactive = false;
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey _ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:) selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification name:UIApplicationWillResignActiveNotification
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:) selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification name:UIApplicationDidEnterBackgroundNotification
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:) selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification name:UIApplicationWillEnterForegroundNotification
object:nil]; object:nil];
} }
return self; return self;
} }
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem {
RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init];
playerLayer.showsPlaybackControls = NO; playerLayer.showsPlaybackControls = NO;
playerLayer.rctDelegate = self; playerLayer.rctDelegate = self;
playerLayer.view.frame = self.bounds; playerLayer.view.frame = self.bounds;
playerLayer.player = _player; playerLayer.player = _player;
playerLayer.view.frame = self.bounds; playerLayer.view.frame = self.bounds;
return playerLayer; return playerLayer;
} }
/* --------------------------------------------------------- /* ---------------------------------------------------------
@ -104,35 +104,35 @@ static NSString *const timedMetadata = @"timedMetadata";
- (CMTime)playerItemDuration - (CMTime)playerItemDuration
{ {
AVPlayerItem *playerItem = [_player currentItem]; AVPlayerItem *playerItem = [_player currentItem];
if (playerItem.status == AVPlayerItemStatusReadyToPlay) if (playerItem.status == AVPlayerItemStatusReadyToPlay)
{ {
return([playerItem duration]); return([playerItem duration]);
} }
return(kCMTimeInvalid); return(kCMTimeInvalid);
} }
- (CMTimeRange)playerItemSeekableTimeRange - (CMTimeRange)playerItemSeekableTimeRange
{ {
AVPlayerItem *playerItem = [_player currentItem]; AVPlayerItem *playerItem = [_player currentItem];
if (playerItem.status == AVPlayerItemStatusReadyToPlay) if (playerItem.status == AVPlayerItemStatusReadyToPlay)
{ {
return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue; return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue;
} }
return (kCMTimeRangeZero); return (kCMTimeRangeZero);
} }
/* Cancels the previously registered time observer. */ /* Cancels the previously registered time observer. */
-(void)removePlayerTimeObserver -(void)removePlayerTimeObserver
{ {
if (_timeObserver) if (_timeObserver)
{ {
[_player removeTimeObserver:_timeObserver]; [_player removeTimeObserver:_timeObserver];
_timeObserver = nil; _timeObserver = nil;
} }
} }
#pragma mark - Progress #pragma mark - Progress
@ -150,7 +150,7 @@ static NSString *const timedMetadata = @"timedMetadata";
- (void)applicationWillResignActive:(NSNotification *)notification - (void)applicationWillResignActive:(NSNotification *)notification
{ {
if (_playInBackground || _playWhenInactive || _paused) return; if (_playInBackground || _playWhenInactive || _paused) return;
[_player pause]; [_player pause];
[_player setRate:0.0]; [_player setRate:0.0];
} }
@ -175,32 +175,32 @@ static NSString *const timedMetadata = @"timedMetadata";
- (void)sendProgressUpdate - (void)sendProgressUpdate
{ {
AVPlayerItem *video = [_player currentItem]; AVPlayerItem *video = [_player currentItem];
if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) {
return; return;
} }
CMTime playerDuration = [self playerItemDuration]; CMTime playerDuration = [self playerItemDuration];
if (CMTIME_IS_INVALID(playerDuration)) { if (CMTIME_IS_INVALID(playerDuration)) {
return; return;
} }
CMTime currentTime = _player.currentTime; CMTime currentTime = _player.currentTime;
const Float64 duration = CMTimeGetSeconds(playerDuration); const Float64 duration = CMTimeGetSeconds(playerDuration);
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}]; [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];
if( currentTimeSecs >= 0 && self.onVideoProgress) { if( currentTimeSecs >= 0 && self.onVideoProgress) {
self.onVideoProgress(@{ self.onVideoProgress(@{
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)], @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
@"playableDuration": [self calculatePlayableDuration], @"playableDuration": [self calculatePlayableDuration],
@"atValue": [NSNumber numberWithLongLong:currentTime.value], @"atValue": [NSNumber numberWithLongLong:currentTime.value],
@"atTimescale": [NSNumber numberWithInt:currentTime.timescale], @"atTimescale": [NSNumber numberWithInt:currentTime.timescale],
@"target": self.reactTag, @"target": self.reactTag,
@"seekableDuration": [self calculateSeekableDuration], @"seekableDuration": [self calculateSeekableDuration],
}); });
} }
} }
/*! /*!
@ -230,12 +230,12 @@ static NSString *const timedMetadata = @"timedMetadata";
- (NSNumber *)calculateSeekableDuration - (NSNumber *)calculateSeekableDuration
{ {
CMTimeRange timeRange = [self playerItemSeekableTimeRange]; CMTimeRange timeRange = [self playerItemSeekableTimeRange];
if (CMTIME_IS_NUMERIC(timeRange.duration)) if (CMTIME_IS_NUMERIC(timeRange.duration))
{ {
return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)]; return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)];
} }
return [NSNumber numberWithInteger:0]; return [NSNumber numberWithInteger:0];
} }
- (void)addPlayerItemObservers - (void)addPlayerItemObservers
@ -266,29 +266,41 @@ static NSString *const timedMetadata = @"timedMetadata";
#pragma mark - Player and source #pragma mark - Player and source
- (void)setCache:(BOOL *)cache
{
// @TODO: Implement
}
- (void)setSrc:(NSDictionary *)source - (void)setSrc:(NSDictionary *)source
{ {
[self removePlayerTimeObserver]; [self removePlayerTimeObserver];
[self removePlayerItemObservers]; [self removePlayerItemObservers];
_playerItem = [self playerItemForSource:source]; [self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
[self addPlayerItemObservers]; [self didSetPlayerItemWithSource:source playerItem:playerItem];
}];
}
- (void) didSetPlayerItemWithSource:(NSDictionary *)source playerItem:(AVPlayerItem *) playerItem
{
_playerItem = playerItem;
[self addPlayerItemObservers];
[_player pause]; [_player pause];
[self removePlayerLayer]; [self removePlayerLayer];
[_playerViewController.view removeFromSuperview]; [_playerViewController.view removeFromSuperview];
_playerViewController = nil; _playerViewController = nil;
if (_playbackRateObserverRegistered) { if (_playbackRateObserverRegistered) {
[_player removeObserver:self forKeyPath:playbackRate context:nil]; [_player removeObserver:self forKeyPath:playbackRate context:nil];
_playbackRateObserverRegistered = NO; _playbackRateObserverRegistered = NO;
} }
_player = [AVPlayer playerWithPlayerItem:_playerItem]; _player = [AVPlayer playerWithPlayerItem:_playerItem];
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[_player addObserver:self forKeyPath:playbackRate options:0 context:nil]; [_player addObserver:self forKeyPath:playbackRate options:0 context:nil];
_playbackRateObserverRegistered = YES; _playbackRateObserverRegistered = YES;
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000;
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html
__weak RCTVideo *weakSelf = self; __weak RCTVideo *weakSelf = self;
@ -296,107 +308,110 @@ static NSString *const timedMetadata = @"timedMetadata";
queue:NULL queue:NULL
usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; }
]; ];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//Perform on next run loop, otherwise onVideoLoadStart is nil //Perform on next run loop, otherwise onVideoLoadStart is nil
if(self.onVideoLoadStart) { if(self.onVideoLoadStart) {
id uri = [source objectForKey:@"uri"]; id uri = [source objectForKey:@"uri"];
id type = [source objectForKey:@"type"]; id type = [source objectForKey:@"type"];
self.onVideoLoadStart(@{@"src": @{ self.onVideoLoadStart(@{@"src": @{
@"uri": uri ? uri : [NSNull null], @"uri": uri ? uri : [NSNull null],
@"type": type ? type : [NSNull null], @"type": type ? type : [NSNull null],
@"isNetwork": [NSNumber numberWithBool:(bool)[source objectForKey:@"isNetwork"]]}, @"isNetwork": [NSNumber numberWithBool:(bool)[source objectForKey:@"isNetwork"]]},
@"target": self.reactTag @"target": self.reactTag
}); });
} }
}); });
} }
- (AVPlayerItem*)playerItemForSource:(NSDictionary *)source - (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler
{ {
bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]]; bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]];
bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]]; bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]];
NSString *uri = [source objectForKey:@"uri"]; NSString *uri = [source objectForKey:@"uri"];
NSString *type = [source objectForKey:@"type"]; NSString *type = [source objectForKey:@"type"];
NSURL *url = (isNetwork || isAsset) ? NSURL *url = (isNetwork || isAsset) ?
[NSURL URLWithString:uri] : [NSURL URLWithString:uri] :
[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]];
if (isNetwork) { if (isNetwork) {
// @TODO: Check if item is cached an if so use the cached asset
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies}]; AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies}];
return [AVPlayerItem playerItemWithAsset:asset]; handler([AVPlayerItem playerItemWithAsset:asset]);
return;
} }
else if (isAsset) { else if (isAsset) {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
return [AVPlayerItem playerItemWithAsset:asset]; handler([AVPlayerItem playerItemWithAsset:asset]);
return;
} }
handler([AVPlayerItem playerItemWithURL:url]);
return [AVPlayerItem playerItemWithURL:url]; return;
} }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{ {
if (object == _playerItem) { if (object == _playerItem) {
// When timeMetadata is read the event onTimedMetadata is triggered // When timeMetadata is read the event onTimedMetadata is triggered
if ([keyPath isEqualToString: timedMetadata]) if ([keyPath isEqualToString: timedMetadata])
{ {
NSArray<AVMetadataItem *> *items = [change objectForKey:@"new"]; NSArray<AVMetadataItem *> *items = [change objectForKey:@"new"];
if (items && ![items isEqual:[NSNull null]] && items.count > 0) { if (items && ![items isEqual:[NSNull null]] && items.count > 0) {
NSMutableArray *array = [NSMutableArray new]; NSMutableArray *array = [NSMutableArray new];
for (AVMetadataItem *item in items) { for (AVMetadataItem *item in items) {
NSString *value = item.value; NSString *value = item.value;
NSString *identifier = item.identifier; NSString *identifier = item.identifier;
if (![value isEqual: [NSNull null]]) { if (![value isEqual: [NSNull null]]) {
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]]; NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]];
[array addObject:dictionary]; [array addObject:dictionary];
} }
}
self.onTimedMetadata(@{
@"target": self.reactTag,
@"metadata": array
});
} }
self.onTimedMetadata(@{
@"target": self.reactTag,
@"metadata": array
});
}
} }
if ([keyPath isEqualToString:statusKeyPath]) { if ([keyPath isEqualToString:statusKeyPath]) {
// Handle player item status change. // Handle player item status change.
if (_playerItem.status == AVPlayerItemStatusReadyToPlay) { if (_playerItem.status == AVPlayerItemStatusReadyToPlay) {
float duration = CMTimeGetSeconds(_playerItem.asset.duration); float duration = CMTimeGetSeconds(_playerItem.asset.duration);
if (isnan(duration)) { if (isnan(duration)) {
duration = 0.0; duration = 0.0;
} }
NSObject *width = @"undefined"; NSObject *width = @"undefined";
NSObject *height = @"undefined"; NSObject *height = @"undefined";
NSString *orientation = @"undefined"; NSString *orientation = @"undefined";
if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) { if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) {
AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
width = [NSNumber numberWithFloat:videoTrack.naturalSize.width]; width = [NSNumber numberWithFloat:videoTrack.naturalSize.width];
height = [NSNumber numberWithFloat:videoTrack.naturalSize.height]; height = [NSNumber numberWithFloat:videoTrack.naturalSize.height];
CGAffineTransform preferredTransform = [videoTrack preferredTransform]; CGAffineTransform preferredTransform = [videoTrack preferredTransform];
if ((videoTrack.naturalSize.width == preferredTransform.tx if ((videoTrack.naturalSize.width == preferredTransform.tx
&& videoTrack.naturalSize.height == preferredTransform.ty) && videoTrack.naturalSize.height == preferredTransform.ty)
|| (preferredTransform.tx == 0 && preferredTransform.ty == 0)) || (preferredTransform.tx == 0 && preferredTransform.ty == 0))
{ {
orientation = @"landscape"; orientation = @"landscape";
} else } else
orientation = @"portrait"; orientation = @"portrait";
} }
if(self.onVideoLoad) { if(self.onVideoLoad) {
self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration], self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration],
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)], @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],
@"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse], @"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse],
@ -406,20 +421,20 @@ static NSString *const timedMetadata = @"timedMetadata";
@"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward], @"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward],
@"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward], @"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward],
@"naturalSize": @{ @"naturalSize": @{
@"width": width, @"width": width,
@"height": height, @"height": height,
@"orientation": orientation @"orientation": orientation
}, },
@"target": self.reactTag}); @"target": self.reactTag});
} }
[self attachListeners]; [self attachListeners];
[self applyModifiers]; [self applyModifiers];
} else if(_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) { } else if(_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
self.onVideoError(@{@"error": @{@"code": [NSNumber numberWithInteger: _playerItem.error.code], self.onVideoError(@{@"error": @{@"code": [NSNumber numberWithInteger: _playerItem.error.code],
@"domain": _playerItem.error.domain}, @"domain": _playerItem.error.domain},
@"target": self.reactTag}); @"target": self.reactTag});
} }
} else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) { } else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) {
_playerBufferEmpty = YES; _playerBufferEmpty = YES;
@ -432,28 +447,28 @@ static NSString *const timedMetadata = @"timedMetadata";
_playerBufferEmpty = NO; _playerBufferEmpty = NO;
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag}); self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
} }
} else if (object == _playerLayer) { } else if (object == _playerLayer) {
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) { if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) { if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
self.onReadyForDisplay(@{@"target": self.reactTag}); self.onReadyForDisplay(@{@"target": self.reactTag});
} }
} }
} else if (object == _player) { } else if (object == _player) {
if([keyPath isEqualToString:playbackRate]) { if([keyPath isEqualToString:playbackRate]) {
if(self.onPlaybackRateChange) { if(self.onPlaybackRateChange) {
self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
@"target": self.reactTag}); @"target": self.reactTag});
}
if(_playbackStalled && _player.rate > 0) {
if(self.onPlaybackResume) {
self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
@"target": self.reactTag});
}
_playbackStalled = NO;
}
} }
if(_playbackStalled && _player.rate > 0) {
if(self.onPlaybackResume) {
self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
@"target": self.reactTag});
}
_playbackStalled = NO;
}
}
} else { } else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
} }
} }
@ -481,9 +496,9 @@ static NSString *const timedMetadata = @"timedMetadata";
- (void)playerItemDidReachEnd:(NSNotification *)notification - (void)playerItemDidReachEnd:(NSNotification *)notification
{ {
if(self.onVideoEnd) { if(self.onVideoEnd) {
self.onVideoEnd(@{@"target": self.reactTag}); self.onVideoEnd(@{@"target": self.reactTag});
} }
if (_repeat) { if (_repeat) {
AVPlayerItem *item = [notification object]; AVPlayerItem *item = [notification object];
[item seekToTime:kCMTimeZero]; [item seekToTime:kCMTimeZero];
@ -536,7 +551,7 @@ static NSString *const timedMetadata = @"timedMetadata";
[_player play]; [_player play];
[_player setRate:_rate]; [_player setRate:_rate];
} }
_paused = paused; _paused = paused;
} }
@ -553,31 +568,31 @@ static NSString *const timedMetadata = @"timedMetadata";
- (void)setSeek:(float)seekTime - (void)setSeek:(float)seekTime
{ {
int timeScale = 10000; int timeScale = 10000;
AVPlayerItem *item = _player.currentItem; AVPlayerItem *item = _player.currentItem;
if (item && item.status == AVPlayerItemStatusReadyToPlay) { if (item && item.status == AVPlayerItemStatusReadyToPlay) {
// TODO check loadedTimeRanges // TODO check loadedTimeRanges
CMTime cmSeekTime = CMTimeMakeWithSeconds(seekTime, timeScale); CMTime cmSeekTime = CMTimeMakeWithSeconds(seekTime, timeScale);
CMTime current = item.currentTime; CMTime current = item.currentTime;
// TODO figure out a good tolerance level // TODO figure out a good tolerance level
CMTime tolerance = CMTimeMake(1000, timeScale); CMTime tolerance = CMTimeMake(1000, timeScale);
BOOL wasPaused = _paused; BOOL wasPaused = _paused;
if (CMTimeCompare(current, cmSeekTime) != 0) { if (CMTimeCompare(current, cmSeekTime) != 0) {
if (!wasPaused) [_player pause]; if (!wasPaused) [_player pause];
[_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) {
if (!wasPaused) [_player play]; if (!wasPaused) [_player play];
if(self.onVideoSeek) { if(self.onVideoSeek) {
self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)],
@"seekTime": [NSNumber numberWithFloat:seekTime], @"seekTime": [NSNumber numberWithFloat:seekTime],
@"target": self.reactTag}); @"target": self.reactTag});
} }
}]; }];
_pendingSeek = false; _pendingSeek = false;
} }
} else { } else {
// TODO: See if this makes sense and if so, actually implement it // TODO: See if this makes sense and if so, actually implement it
_pendingSeek = true; _pendingSeek = true;
@ -612,7 +627,7 @@ static NSString *const timedMetadata = @"timedMetadata";
[_player setVolume:_volume]; [_player setVolume:_volume];
[_player setMuted:NO]; [_player setMuted:NO];
} }
[self setResizeMode:_resizeMode]; [self setResizeMode:_resizeMode];
[self setRepeat:_repeat]; [self setRepeat:_repeat];
[self setPaused:_paused]; [self setPaused:_paused];
@ -625,103 +640,103 @@ static NSString *const timedMetadata = @"timedMetadata";
- (BOOL)getFullscreen - (BOOL)getFullscreen
{ {
return _fullscreenPlayerPresented; return _fullscreenPlayerPresented;
} }
- (void)setFullscreen:(BOOL)fullscreen - (void)setFullscreen:(BOOL)fullscreen
{ {
if( fullscreen && !_fullscreenPlayerPresented ) if( fullscreen && !_fullscreenPlayerPresented )
{
// Ensure player view controller is not null
if( !_playerViewController )
{ {
// Ensure player view controller is not null [self usePlayerViewController];
if( !_playerViewController )
{
[self usePlayerViewController];
}
// Set presentation style to fullscreen
[_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen];
// Find the nearest view controller
UIViewController *viewController = [self firstAvailableUIViewController];
if( !viewController )
{
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;
if(self.onVideoFullscreenPlayerDidPresent) {
self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag});
}
}];
}
} }
else if ( !fullscreen && _fullscreenPlayerPresented ) // Set presentation style to fullscreen
[_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen];
// Find the nearest view controller
UIViewController *viewController = [self firstAvailableUIViewController];
if( !viewController )
{ {
[self videoPlayerViewControllerWillDismiss:_playerViewController]; UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
[_presentingViewController dismissViewControllerAnimated:true completion:^{ viewController = keyWindow.rootViewController;
[self videoPlayerViewControllerDidDismiss:_playerViewController]; 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;
if(self.onVideoFullscreenPlayerDidPresent) {
self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag});
}
}];
}
}
else if ( !fullscreen && _fullscreenPlayerPresented )
{
[self videoPlayerViewControllerWillDismiss:_playerViewController];
[_presentingViewController dismissViewControllerAnimated:true completion:^{
[self videoPlayerViewControllerDidDismiss:_playerViewController];
}];
}
} }
- (void)usePlayerViewController - (void)usePlayerViewController
{ {
if( _player ) if( _player )
{ {
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem]; _playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
// to prevent video from being animated when resizeMode is 'cover' // to prevent video from being animated when resizeMode is 'cover'
// resize mode must be set before subview is added // resize mode must be set before subview is added
[self setResizeMode:_resizeMode]; [self setResizeMode:_resizeMode];
[self addSubview:_playerViewController.view]; [self addSubview:_playerViewController.view];
} }
} }
- (void)usePlayerLayer - (void)usePlayerLayer
{ {
if( _player ) if( _player )
{ {
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
_playerLayer.frame = self.bounds; _playerLayer.frame = self.bounds;
_playerLayer.needsDisplayOnBoundsChange = YES; _playerLayer.needsDisplayOnBoundsChange = YES;
// to prevent video from being animated when resizeMode is 'cover' // to prevent video from being animated when resizeMode is 'cover'
// resize mode must be set before layer is added // resize mode must be set before layer is added
[self setResizeMode:_resizeMode]; [self setResizeMode:_resizeMode];
[_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil]; [_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];
[self.layer addSublayer:_playerLayer]; [self.layer addSublayer:_playerLayer];
self.layer.needsDisplayOnBoundsChange = YES; self.layer.needsDisplayOnBoundsChange = YES;
} }
} }
- (void)setControls:(BOOL)controls - (void)setControls:(BOOL)controls
{ {
if( _controls != controls || (!_playerLayer && !_playerViewController) ) if( _controls != controls || (!_playerLayer && !_playerViewController) )
{
_controls = controls;
if( _controls )
{ {
_controls = controls; [self removePlayerLayer];
if( _controls ) [self usePlayerViewController];
{
[self removePlayerLayer];
[self usePlayerViewController];
}
else
{
[_playerViewController.view removeFromSuperview];
_playerViewController = nil;
[self usePlayerLayer];
}
} }
else
{
[_playerViewController.view removeFromSuperview];
_playerViewController = nil;
[self usePlayerLayer];
}
}
} }
- (void)setProgressUpdateInterval:(float)progressUpdateInterval - (void)setProgressUpdateInterval:(float)progressUpdateInterval
@ -731,33 +746,33 @@ static NSString *const timedMetadata = @"timedMetadata";
- (void)removePlayerLayer - (void)removePlayerLayer
{ {
[_playerLayer removeFromSuperlayer]; [_playerLayer removeFromSuperlayer];
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
_playerLayer = nil; _playerLayer = nil;
} }
#pragma mark - RCTVideoPlayerViewControllerDelegate #pragma mark - RCTVideoPlayerViewControllerDelegate
- (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController
{ {
if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss)
{ {
self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag}); self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag});
} }
} }
- (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController - (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController
{ {
if (_playerViewController == playerViewController && _fullscreenPlayerPresented) if (_playerViewController == playerViewController && _fullscreenPlayerPresented)
{ {
_fullscreenPlayerPresented = false; _fullscreenPlayerPresented = false;
_presentingViewController = nil; _presentingViewController = nil;
_playerViewController = nil; _playerViewController = nil;
[self applyModifiers]; [self applyModifiers];
if(self.onVideoFullscreenPlayerDidDismiss) { if(self.onVideoFullscreenPlayerDidDismiss) {
self.onVideoFullscreenPlayerDidDismiss(@{@"target": self.reactTag}); self.onVideoFullscreenPlayerDidDismiss(@{@"target": self.reactTag});
}
} }
}
} }
#pragma mark - React View Management #pragma mark - React View Management
@ -770,15 +785,15 @@ static NSString *const timedMetadata = @"timedMetadata";
{ {
[self setControls:true]; [self setControls:true];
} }
if( _controls ) if( _controls )
{ {
view.frame = self.bounds; view.frame = self.bounds;
[_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex];
} }
else else
{ {
RCTLogError(@"video cannot have any subviews"); RCTLogError(@"video cannot have any subviews");
} }
return; return;
} }
@ -787,7 +802,7 @@ static NSString *const timedMetadata = @"timedMetadata";
{ {
if( _controls ) if( _controls )
{ {
[subview removeFromSuperview]; [subview removeFromSuperview];
} }
else else
{ {
@ -802,7 +817,7 @@ static NSString *const timedMetadata = @"timedMetadata";
if( _controls ) if( _controls )
{ {
_playerViewController.view.frame = self.bounds; _playerViewController.view.frame = self.bounds;
// also adjust all subviews of contentOverlayView // also adjust all subviews of contentOverlayView
for (UIView* subview in _playerViewController.contentOverlayView.subviews) { for (UIView* subview in _playerViewController.contentOverlayView.subviews) {
subview.frame = self.bounds; subview.frame = self.bounds;
@ -810,10 +825,10 @@ static NSString *const timedMetadata = @"timedMetadata";
} }
else else
{ {
[CATransaction begin]; [CATransaction begin];
[CATransaction setAnimationDuration:0]; [CATransaction setAnimationDuration:0];
_playerLayer.frame = self.bounds; _playerLayer.frame = self.bounds;
[CATransaction commit]; [CATransaction commit];
} }
} }
@ -827,18 +842,18 @@ static NSString *const timedMetadata = @"timedMetadata";
_playbackRateObserverRegistered = NO; _playbackRateObserverRegistered = NO;
} }
_player = nil; _player = nil;
[self removePlayerLayer]; [self removePlayerLayer];
[_playerViewController.view removeFromSuperview]; [_playerViewController.view removeFromSuperview];
_playerViewController = nil; _playerViewController = nil;
[self removePlayerTimeObserver]; [self removePlayerTimeObserver];
[self removePlayerItemObservers]; [self removePlayerItemObservers];
_eventDispatcher = nil; _eventDispatcher = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
[super removeFromSuperview]; [super removeFromSuperview];
} }

View File

@ -20,6 +20,7 @@ RCT_EXPORT_MODULE();
} }
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(cache, BOOL);
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);