added video filter

This commit is contained in:
Nicolas Gonzalez 2018-10-25 08:56:20 -05:00
parent 2c391f5807
commit 18e8895712
3 changed files with 125 additions and 71 deletions

View File

@ -274,7 +274,10 @@ export default class Video extends Component {
} }
} }
Video.filterTypes = ['Normal', 'Country', 'Winter', 'Black N White', 'Sunrise', 'Artistic'];
Video.propTypes = { Video.propTypes = {
filter: PropTypes.oneOf(Video.filterTypes),
/* Native only */ /* Native only */
src: PropTypes.object, src: PropTypes.object,
seek: PropTypes.oneOfType([ seek: PropTypes.oneOfType([

View File

@ -13,6 +13,7 @@ static NSString *const readyForDisplayKeyPath = @"readyForDisplay";
static NSString *const playbackRate = @"rate"; static NSString *const playbackRate = @"rate";
static NSString *const timedMetadata = @"timedMetadata"; static NSString *const timedMetadata = @"timedMetadata";
static NSString *const externalPlaybackActive = @"externalPlaybackActive"; static NSString *const externalPlaybackActive = @"externalPlaybackActive";
static NSDictionary* filters = nil;
static int const RCTVideoUnset = -1; static int const RCTVideoUnset = -1;
@ -32,22 +33,22 @@ static int const RCTVideoUnset = -1;
BOOL _playerLayerObserverSet; BOOL _playerLayerObserverSet;
RCTVideoPlayerViewController *_playerViewController; RCTVideoPlayerViewController *_playerViewController;
NSURL *_videoURL; NSURL *_videoURL;
/* Required to publish events */ /* Required to publish events */
RCTEventDispatcher *_eventDispatcher; RCTEventDispatcher *_eventDispatcher;
BOOL _playbackRateObserverRegistered; BOOL _playbackRateObserverRegistered;
BOOL _isExternalPlaybackActiveObserverRegistered; BOOL _isExternalPlaybackActiveObserverRegistered;
BOOL _videoLoadStarted; BOOL _videoLoadStarted;
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;
@ -63,6 +64,7 @@ static int const RCTVideoUnset = -1;
BOOL _playWhenInactive; BOOL _playWhenInactive;
NSString * _ignoreSilentSwitch; NSString * _ignoreSilentSwitch;
NSString * _resizeMode; NSString * _resizeMode;
NSString * _filter;
BOOL _fullscreen; BOOL _fullscreen;
NSString * _fullscreenOrientation; NSString * _fullscreenOrientation;
BOOL _fullscreenPlayerPresented; BOOL _fullscreenPlayerPresented;
@ -75,8 +77,18 @@ static int const RCTVideoUnset = -1;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{ {
if ((self = [super init])) { if ((self = [super init])) {
filters = @{
@"Normal": @"",
@"Country": @"CISepiaTone",
@"Winter": @"CIPhotoEffectProcess",
@"Black N White": @"CIPhotoEffectNoir",
@"Sunrise": @"CIPhotoEffectTransfer",
@"Artistic": @"CIColorPosterize",
};
_eventDispatcher = eventDispatcher; _eventDispatcher = eventDispatcher;
_playbackRateObserverRegistered = NO; _playbackRateObserverRegistered = NO;
_isExternalPlaybackActiveObserverRegistered = NO; _isExternalPlaybackActiveObserverRegistered = NO;
_playbackStalled = NO; _playbackStalled = NO;
@ -101,23 +113,23 @@ static int const RCTVideoUnset = -1;
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];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(audioRouteChanged:) selector:@selector(audioRouteChanged:)
name:AVAudioSessionRouteChangeNotification name:AVAudioSessionRouteChangeNotification
object:nil]; object:nil];
} }
return self; return self;
} }
@ -127,7 +139,7 @@ static int const RCTVideoUnset = -1;
viewController.showsPlaybackControls = YES; viewController.showsPlaybackControls = YES;
viewController.rctDelegate = self; viewController.rctDelegate = self;
viewController.preferredOrientation = _fullscreenOrientation; viewController.preferredOrientation = _fullscreenOrientation;
viewController.view.frame = self.bounds; viewController.view.frame = self.bounds;
viewController.player = player; viewController.player = player;
viewController.view.frame = self.bounds; viewController.view.frame = self.bounds;
@ -145,7 +157,7 @@ static int const RCTVideoUnset = -1;
{ {
return([playerItem duration]); return([playerItem duration]);
} }
return(kCMTimeInvalid); return(kCMTimeInvalid);
} }
@ -156,7 +168,7 @@ static int const RCTVideoUnset = -1;
{ {
return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue; return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue;
} }
return (kCMTimeRangeZero); return (kCMTimeRangeZero);
} }
@ -197,7 +209,7 @@ static int const RCTVideoUnset = -1;
- (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];
} }
@ -237,18 +249,18 @@ static int const RCTVideoUnset = -1;
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)],
@ -333,11 +345,11 @@ static int const RCTVideoUnset = -1;
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) { [self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
_playerItem = playerItem; _playerItem = playerItem;
[self addPlayerItemObservers]; [self addPlayerItemObservers];
[_player pause]; [_player pause];
[_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;
@ -346,16 +358,16 @@ static int const RCTVideoUnset = -1;
[_player removeObserver:self forKeyPath:externalPlaybackActive context:nil]; [_player removeObserver:self forKeyPath:externalPlaybackActive context:nil];
_isExternalPlaybackActiveObserverRegistered = NO; _isExternalPlaybackActiveObserverRegistered = 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;
[_player addObserver:self forKeyPath:externalPlaybackActive options:0 context:nil]; [_player addObserver:self forKeyPath:externalPlaybackActive options:0 context:nil];
_isExternalPlaybackActiveObserverRegistered = YES; _isExternalPlaybackActiveObserverRegistered = YES;
[self addPlayerTimeObserver]; [self addPlayerTimeObserver];
//Perform on next run loop, otherwise onVideoLoadStart is nil //Perform on next run loop, otherwise onVideoLoadStart is nil
@ -378,7 +390,7 @@ static int const RCTVideoUnset = -1;
if ([filepath containsString:@"file://"]) { if ([filepath containsString:@"file://"]) {
return [NSURL URLWithString:filepath]; return [NSURL URLWithString:filepath];
} }
// if no file found, check if the file exists in the Document directory // if no file found, check if the file exists in the Document directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* relativeFilePath = [filepath lastPathComponent]; NSString* relativeFilePath = [filepath lastPathComponent];
@ -387,7 +399,7 @@ static int const RCTVideoUnset = -1;
if (fileComponents.count > 1) { if (fileComponents.count > 1) {
relativeFilePath = [fileComponents objectAtIndex:1]; relativeFilePath = [fileComponents objectAtIndex:1];
} }
NSString *path = [paths.firstObject stringByAppendingPathComponent:relativeFilePath]; NSString *path = [paths.firstObject stringByAppendingPathComponent:relativeFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
return [NSURL fileURLWithPath:path]; return [NSURL fileURLWithPath:path];
@ -404,21 +416,21 @@ static int const RCTVideoUnset = -1;
// sideload text tracks // sideload text tracks
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init]; AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject; AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
AVMutableCompositionTrack *videoCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; AVMutableCompositionTrack *videoCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[videoCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) [videoCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)
ofTrack:videoAsset ofTrack:videoAsset
atTime:kCMTimeZero atTime:kCMTimeZero
error:nil]; error:nil];
AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject; AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[audioCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) [audioCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)
ofTrack:audioAsset ofTrack:audioAsset
atTime:kCMTimeZero atTime:kCMTimeZero
error:nil]; error:nil];
NSMutableArray* validTextTracks = [NSMutableArray array]; NSMutableArray* validTextTracks = [NSMutableArray array];
for (int i = 0; i < _textTracks.count; ++i) { for (int i = 0; i < _textTracks.count; ++i) {
AVURLAsset *textURLAsset; AVURLAsset *textURLAsset;
@ -457,7 +469,7 @@ static int const RCTVideoUnset = -1;
? [NSURL URLWithString:uri] ? [NSURL URLWithString:uri]
: [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; : [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]];
NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init]; NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init];
if (isNetwork) { if (isNetwork) {
/* Per #1091, this is not a public API. /* Per #1091, this is not a public API.
* We need to either get approval from Apple to use this or use a different approach. * We need to either get approval from Apple to use this or use a different approach.
@ -560,40 +572,40 @@ static int const RCTVideoUnset = -1;
for (AVMetadataItem *item in items) { for (AVMetadataItem *item in items) {
NSString *value = (NSString *)item.value; NSString *value = (NSString *)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(@{ self.onTimedMetadata(@{
@"target": self.reactTag, @"target": self.reactTag,
@"metadata": array @"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))
@ -603,7 +615,7 @@ static int const RCTVideoUnset = -1;
orientation = @"portrait"; orientation = @"portrait";
} }
} }
if (self.onVideoLoad && _videoLoadStarted) { if (self.onVideoLoad && _videoLoadStarted) {
self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration], self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration],
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)], @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],
@ -623,7 +635,7 @@ static int const RCTVideoUnset = -1;
@"target": self.reactTag}); @"target": self.reactTag});
} }
_videoLoadStarted = NO; _videoLoadStarted = NO;
[self attachListeners]; [self attachListeners];
[self applyModifiers]; [self applyModifiers];
} else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) { } else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
@ -683,7 +695,7 @@ static int const RCTVideoUnset = -1;
selector:@selector(playerItemDidReachEnd:) selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification name:AVPlayerItemDidPlayToEndTimeNotification
object:[_player currentItem]]; object:[_player currentItem]];
[[NSNotificationCenter defaultCenter] removeObserver:self [[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemPlaybackStalledNotification name:AVPlayerItemPlaybackStalledNotification
object:nil]; object:nil];
@ -706,7 +718,7 @@ static int const RCTVideoUnset = -1;
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];
@ -767,7 +779,7 @@ static int const RCTVideoUnset = -1;
[_player play]; [_player play];
[_player setRate:_rate]; [_player setRate:_rate];
} }
_paused = paused; _paused = paused;
} }
@ -789,19 +801,19 @@ static int const RCTVideoUnset = -1;
{ {
NSNumber *seekTime = info[@"time"]; NSNumber *seekTime = info[@"time"];
NSNumber *seekTolerance = info[@"tolerance"]; NSNumber *seekTolerance = info[@"tolerance"];
int timeScale = 1000; int timeScale = 1000;
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 floatValue], timeScale); CMTime cmSeekTime = CMTimeMakeWithSeconds([seekTime floatValue], 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([seekTolerance floatValue], timeScale); CMTime tolerance = CMTimeMake([seekTolerance floatValue], 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) {
@ -817,10 +829,10 @@ static int const RCTVideoUnset = -1;
@"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;
@ -855,12 +867,13 @@ static int const RCTVideoUnset = -1;
[_player setVolume:_volume]; [_player setVolume:_volume];
[_player setMuted:NO]; [_player setMuted:NO];
} }
[self setSelectedAudioTrack:_selectedAudioTrack]; [self setSelectedAudioTrack:_selectedAudioTrack];
[self setSelectedTextTrack:_selectedTextTrack]; [self setSelectedTextTrack:_selectedTextTrack];
[self setResizeMode:_resizeMode]; [self setResizeMode:_resizeMode];
[self setRepeat:_repeat]; [self setRepeat:_repeat];
[self setPaused:_paused]; [self setPaused:_paused];
[self setFilter:_filter];
[self setControls:_controls]; [self setControls:_controls];
[self setAllowsExternalPlayback:_allowsExternalPlayback]; [self setAllowsExternalPlayback:_allowsExternalPlayback];
} }
@ -876,7 +889,7 @@ static int const RCTVideoUnset = -1;
AVMediaSelectionGroup *group = [_player.currentItem.asset AVMediaSelectionGroup *group = [_player.currentItem.asset
mediaSelectionGroupForMediaCharacteristic:characteristic]; mediaSelectionGroupForMediaCharacteristic:characteristic];
AVMediaSelectionOption *mediaOption; AVMediaSelectionOption *mediaOption;
if ([type isEqualToString:@"disabled"]) { if ([type isEqualToString:@"disabled"]) {
// Do nothing. We want to ensure option is nil // Do nothing. We want to ensure option is nil
} else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) {
@ -909,7 +922,7 @@ static int const RCTVideoUnset = -1;
[_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group];
return; return;
} }
// If a match isn't found, option will be nil and text tracks will be disabled // If a match isn't found, option will be nil and text tracks will be disabled
[_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];
} }
@ -933,7 +946,7 @@ static int const RCTVideoUnset = -1;
- (void) setSideloadedText { - (void) setSideloadedText {
NSString *type = _selectedTextTrack[@"type"]; NSString *type = _selectedTextTrack[@"type"];
NSArray *textTracks = [self getTextTrackInfo]; NSArray *textTracks = [self getTextTrackInfo];
// The first few tracks will be audio & video track // The first few tracks will be audio & video track
int firstTextIndex = 0; int firstTextIndex = 0;
for (firstTextIndex = 0; firstTextIndex < _player.currentItem.tracks.count; ++firstTextIndex) { for (firstTextIndex = 0; firstTextIndex < _player.currentItem.tracks.count; ++firstTextIndex) {
@ -941,9 +954,9 @@ static int const RCTVideoUnset = -1;
break; break;
} }
} }
int selectedTrackIndex = RCTVideoUnset; int selectedTrackIndex = RCTVideoUnset;
if ([type isEqualToString:@"disabled"]) { if ([type isEqualToString:@"disabled"]) {
// Do nothing. We want to ensure option is nil // Do nothing. We want to ensure option is nil
} else if ([type isEqualToString:@"language"]) { } else if ([type isEqualToString:@"language"]) {
@ -972,7 +985,7 @@ static int const RCTVideoUnset = -1;
} }
} }
} }
// in the situation that a selected text track is not available (eg. specifies a textTrack not available) // in the situation that a selected text track is not available (eg. specifies a textTrack not available)
if (![type isEqualToString:@"disabled"] && selectedTrackIndex == RCTVideoUnset) { if (![type isEqualToString:@"disabled"] && selectedTrackIndex == RCTVideoUnset) {
CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser); CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser);
@ -989,7 +1002,7 @@ static int const RCTVideoUnset = -1;
} }
} }
} }
for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) { for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) {
BOOL isEnabled = NO; BOOL isEnabled = NO;
if (selectedTrackIndex != RCTVideoUnset) { if (selectedTrackIndex != RCTVideoUnset) {
@ -1004,7 +1017,7 @@ static int const RCTVideoUnset = -1;
AVMediaSelectionGroup *group = [_player.currentItem.asset AVMediaSelectionGroup *group = [_player.currentItem.asset
mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
AVMediaSelectionOption *mediaOption; AVMediaSelectionOption *mediaOption;
if ([type isEqualToString:@"disabled"]) { if ([type isEqualToString:@"disabled"]) {
// Do nothing. We want to ensure option is nil // Do nothing. We want to ensure option is nil
} else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) {
@ -1037,7 +1050,7 @@ static int const RCTVideoUnset = -1;
[_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group];
return; return;
} }
// If a match isn't found, option will be nil and text tracks will be disabled // If a match isn't found, option will be nil and text tracks will be disabled
[_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];
} }
@ -1045,7 +1058,7 @@ static int const RCTVideoUnset = -1;
- (void)setTextTracks:(NSArray*) textTracks; - (void)setTextTracks:(NSArray*) textTracks;
{ {
_textTracks = textTracks; _textTracks = textTracks;
// in case textTracks was set after selectedTextTrack // in case textTracks was set after selectedTextTrack
if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack]; if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack];
} }
@ -1077,7 +1090,7 @@ static int const RCTVideoUnset = -1;
{ {
// if sideloaded, textTracks will already be set // if sideloaded, textTracks will already be set
if (_textTracks) return _textTracks; if (_textTracks) return _textTracks;
// if streaming video, we extract the text tracks // if streaming video, we extract the text tracks
NSMutableArray *textTracks = [[NSMutableArray alloc] init]; NSMutableArray *textTracks = [[NSMutableArray alloc] init];
AVMediaSelectionGroup *group = [_player.currentItem.asset AVMediaSelectionGroup *group = [_player.currentItem.asset
@ -1115,7 +1128,7 @@ static int const RCTVideoUnset = -1;
} }
// Set presentation style to fullscreen // Set presentation style to fullscreen
[_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen]; [_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen];
// Find the nearest view controller // Find the nearest view controller
UIViewController *viewController = [self firstAvailableUIViewController]; UIViewController *viewController = [self firstAvailableUIViewController];
if( !viewController ) if( !viewController )
@ -1151,6 +1164,43 @@ static int const RCTVideoUnset = -1;
} }
} }
- (void)setFilter:(NSString *)filter {
_filter = filter;
AVAsset *asset = _playerItem.asset;
if (asset != nil) {
NSString *filterName = filters[filter];
CIFilter *filter = [CIFilter filterWithName:filterName];
_playerItem.videoComposition = [AVVideoComposition
videoCompositionWithAsset:asset
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];
}
}];
}
}
- (void)setFullscreenOrientation:(NSString *)orientation { - (void)setFullscreenOrientation:(NSString *)orientation {
_fullscreenOrientation = orientation; _fullscreenOrientation = orientation;
if (_fullscreenPlayerPresented) { if (_fullscreenPlayerPresented) {
@ -1177,13 +1227,13 @@ static int const RCTVideoUnset = -1;
_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];
_playerLayerObserverSet = YES; _playerLayerObserverSet = YES;
[self.layer addSublayer:_playerLayer]; [self.layer addSublayer:_playerLayer];
self.layer.needsDisplayOnBoundsChange = YES; self.layer.needsDisplayOnBoundsChange = YES;
} }
@ -1211,7 +1261,7 @@ static int const RCTVideoUnset = -1;
- (void)setProgressUpdateInterval:(float)progressUpdateInterval - (void)setProgressUpdateInterval:(float)progressUpdateInterval
{ {
_progressUpdateInterval = progressUpdateInterval; _progressUpdateInterval = progressUpdateInterval;
if (_timeObserver) { if (_timeObserver) {
[self removePlayerTimeObserver]; [self removePlayerTimeObserver];
[self addPlayerTimeObserver]; [self addPlayerTimeObserver];
@ -1262,7 +1312,7 @@ static int const RCTVideoUnset = -1;
{ {
[self setControls:true]; [self setControls:true];
} }
if( _controls ) if( _controls )
{ {
view.frame = self.bounds; view.frame = self.bounds;
@ -1294,7 +1344,7 @@ static int const RCTVideoUnset = -1;
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;
@ -1323,18 +1373,18 @@ static int const RCTVideoUnset = -1;
_isExternalPlaybackActiveObserverRegistered = NO; _isExternalPlaybackActiveObserverRegistered = 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

@ -38,6 +38,7 @@ RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(currentTime, float); RCT_EXPORT_VIEW_PROPERTY(currentTime, float);
RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL);
RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString); RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString);
RCT_EXPORT_VIEW_PROPERTY(filter, NSString);
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock);