Merge branch 'master' into implement-ios-caching
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoAudioBecomingNoisy;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillPresent;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidPresent;
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss;
|
||||
|
401
ios/RCTVideo.m
401
ios/RCTVideo.m
@@ -4,6 +4,8 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/UIView+React.h>
|
||||
#include <MediaAccessibility/MediaAccessibility.h>
|
||||
#include <AVFoundation/AVFoundation.h>
|
||||
|
||||
static NSString *const statusKeyPath = @"status";
|
||||
static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp";
|
||||
@@ -19,13 +21,15 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
BOOL _playerItemObserversSet;
|
||||
BOOL _playerBufferEmpty;
|
||||
AVPlayerLayer *_playerLayer;
|
||||
BOOL _playerLayerObserverSet;
|
||||
AVPlayerViewController *_playerViewController;
|
||||
NSURL *_videoURL;
|
||||
|
||||
/* Required to publish events */
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
BOOL _playbackRateObserverRegistered;
|
||||
|
||||
BOOL _videoLoadStarted;
|
||||
|
||||
bool _pendingSeek;
|
||||
float _pendingSeekTime;
|
||||
float _lastSeekTime;
|
||||
@@ -41,6 +45,9 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
BOOL _muted;
|
||||
BOOL _paused;
|
||||
BOOL _repeat;
|
||||
BOOL _allowsExternalPlayback;
|
||||
NSArray * _textTracks;
|
||||
NSDictionary * _selectedTextTrack;
|
||||
BOOL _playbackStalled;
|
||||
BOOL _playInBackground;
|
||||
BOOL _playWhenInactive;
|
||||
@@ -68,6 +75,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
_controls = NO;
|
||||
_playerBufferEmpty = YES;
|
||||
_playInBackground = false;
|
||||
_allowsExternalPlayback = YES;
|
||||
_playWhenInactive = false;
|
||||
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
|
||||
_videoCache = [RCTVideoCache sharedInstance];
|
||||
@@ -86,19 +94,24 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
selector:@selector(applicationWillEnterForeground:)
|
||||
name:UIApplicationWillEnterForegroundNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(audioRouteChanged:)
|
||||
name:AVAudioSessionRouteChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem {
|
||||
RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init];
|
||||
playerLayer.showsPlaybackControls = NO;
|
||||
playerLayer.rctDelegate = self;
|
||||
playerLayer.view.frame = self.bounds;
|
||||
playerLayer.player = _player;
|
||||
playerLayer.view.frame = self.bounds;
|
||||
return playerLayer;
|
||||
RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init];
|
||||
playerLayer.showsPlaybackControls = YES;
|
||||
playerLayer.rctDelegate = self;
|
||||
playerLayer.view.frame = self.bounds;
|
||||
playerLayer.player = player;
|
||||
playerLayer.view.frame = self.bounds;
|
||||
return playerLayer;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
@@ -127,6 +140,17 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
return (kCMTimeRangeZero);
|
||||
}
|
||||
|
||||
-(void)addPlayerTimeObserver
|
||||
{
|
||||
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]; }
|
||||
];
|
||||
}
|
||||
|
||||
/* Cancels the previously registered time observer. */
|
||||
-(void)removePlayerTimeObserver
|
||||
@@ -143,8 +167,8 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[self removePlayerItemObservers];
|
||||
[self removePlayerLayer];
|
||||
[self removePlayerItemObservers];
|
||||
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
||||
}
|
||||
|
||||
@@ -174,6 +198,17 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Audio events
|
||||
|
||||
- (void)audioRouteChanged:(NSNotification *)notification
|
||||
{
|
||||
NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey];
|
||||
NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
|
||||
if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
|
||||
self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Progress
|
||||
|
||||
- (void)sendProgressUpdate
|
||||
@@ -255,9 +290,6 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
* observer set */
|
||||
- (void)removePlayerItemObservers
|
||||
{
|
||||
if (_playerLayer) {
|
||||
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
|
||||
}
|
||||
if (_playerItemObserversSet) {
|
||||
[_playerItem removeObserver:self forKeyPath:statusKeyPath];
|
||||
[_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath];
|
||||
@@ -276,6 +308,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
|
||||
- (void)setSrc:(NSDictionary *)source
|
||||
{
|
||||
[self removePlayerLayer];
|
||||
[self removePlayerTimeObserver];
|
||||
[self removePlayerItemObservers];
|
||||
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
|
||||
@@ -313,6 +346,28 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
||||
// perform on next run loop, otherwise other passed react-props may not be set
|
||||
_playerItem = [self playerItemForSource:source];
|
||||
[self addPlayerItemObservers];
|
||||
|
||||
[_player pause];
|
||||
[_playerViewController.view removeFromSuperview];
|
||||
_playerViewController = nil;
|
||||
|
||||
if (_playbackRateObserverRegistered) {
|
||||
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
||||
_playbackRateObserverRegistered = NO;
|
||||
}
|
||||
|
||||
_player = [AVPlayer playerWithPlayerItem:_playerItem];
|
||||
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
|
||||
|
||||
[_player addObserver:self forKeyPath:playbackRate options:0 context:nil];
|
||||
_playbackRateObserverRegistered = YES;
|
||||
|
||||
[self addPlayerTimeObserver];
|
||||
|
||||
//Perform on next run loop, otherwise onVideoLoadStart is nil
|
||||
if(self.onVideoLoadStart) {
|
||||
id uri = [source objectForKey:@"uri"];
|
||||
@@ -324,7 +379,70 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
@"target": self.reactTag
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
_videoLoadStarted = YES;
|
||||
}
|
||||
|
||||
- (NSURL*) urlFilePath:(NSString*) filepath {
|
||||
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;
|
||||
}
|
||||
|
||||
- (void)playerItemPrepareText:(AVAsset *)asset withCallback:(void(^)(AVPlayerItem *))handler
|
||||
{
|
||||
if (!_textTracks) {
|
||||
handler([AVPlayerItem playerItemWithAsset:asset]);
|
||||
return;
|
||||
}
|
||||
// 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];
|
||||
|
||||
for (int i = 0; i < _textTracks.count; ++i) {
|
||||
AVURLAsset *textURLAsset;
|
||||
NSString *textUri = [_textTracks objectAtIndex:i][@"uri"];
|
||||
if ([[textUri lowercaseString] hasPrefix:@"http"]) {
|
||||
textURLAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:textUri] options:assetOptions];
|
||||
} else {
|
||||
textURLAsset = [AVURLAsset URLAssetWithURL:[self urlFilePath:textUri] options:nil];
|
||||
}
|
||||
AVAssetTrack *textTrackAsset = [textURLAsset tracksWithMediaType:AVMediaTypeText].firstObject;
|
||||
AVMutableCompositionTrack *textCompTrack = [mixComposition
|
||||
addMutableTrackWithMediaType:AVMediaTypeText
|
||||
preferredTrackID:kCMPersistentTrackID_Invalid];
|
||||
[textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)
|
||||
ofTrack:textTrackAsset
|
||||
atTime:kCMTimeZero
|
||||
error:nil];
|
||||
}
|
||||
|
||||
handler([AVPlayerItem playerItemWithAsset:mixComposition]);
|
||||
}
|
||||
|
||||
- (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler
|
||||
@@ -333,7 +451,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
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]];
|
||||
@@ -341,41 +459,36 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
if (isNetwork) {
|
||||
[_videoCache getItemForUri:uri withCallback:^(AVAsset * _Nullable asset) {
|
||||
if (asset) {
|
||||
handler([AVPlayerItem playerItemWithAsset:asset]);
|
||||
[self playerItemPrepareText:asset withCallback:handler];
|
||||
return;
|
||||
}
|
||||
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
|
||||
DVURLAsset * dvAsset = [[DVURLAsset alloc] initWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies} networkTimeout: 10000];
|
||||
dvAsset.loaderDelegate = self;
|
||||
handler([AVPlayerItem playerItemWithAsset:dvAsset]);
|
||||
[self playerItemPrepareText:dvAsset withCallback:handler];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
else if (isAsset) {
|
||||
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
|
||||
handler([AVPlayerItem playerItemWithAsset:asset]);
|
||||
[self playerItemPrepareText:asset withCallback:handler];
|
||||
return;
|
||||
}
|
||||
handler([AVPlayerItem playerItemWithURL:url]);
|
||||
return;
|
||||
|
||||
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]] options:nil];
|
||||
[self playerItemPrepareText:asset withCallback:handler];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
if (object == _playerItem) {
|
||||
|
||||
// When timeMetadata is read the event onTimedMetadata is triggered
|
||||
if ([keyPath isEqualToString: timedMetadata])
|
||||
{
|
||||
|
||||
|
||||
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 = item.value;
|
||||
NSString *value = (NSString *)item.value;
|
||||
NSString *identifier = item.identifier;
|
||||
|
||||
if (![value isEqual: [NSNull null]]) {
|
||||
@@ -416,11 +529,12 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
|| (preferredTransform.tx == 0 && preferredTransform.ty == 0))
|
||||
{
|
||||
orientation = @"landscape";
|
||||
} else
|
||||
} else {
|
||||
orientation = @"portrait";
|
||||
}
|
||||
}
|
||||
|
||||
if(self.onVideoLoad) {
|
||||
if (self.onVideoLoad && _videoLoadStarted) {
|
||||
self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration],
|
||||
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],
|
||||
@"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse],
|
||||
@@ -434,13 +548,14 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
@"height": height,
|
||||
@"orientation": orientation
|
||||
},
|
||||
@"textTracks": [self getTextTrackInfo],
|
||||
@"target": self.reactTag});
|
||||
}
|
||||
|
||||
_videoLoadStarted = NO;
|
||||
|
||||
[self attachListeners];
|
||||
[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],
|
||||
@"domain": _playerItem.error.domain},
|
||||
@"target": self.reactTag});
|
||||
@@ -484,10 +599,17 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
- (void)attachListeners
|
||||
{
|
||||
// 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
|
||||
@@ -512,6 +634,8 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
AVPlayerItem *item = [notification object];
|
||||
[item seekToTime:kCMTimeZero];
|
||||
[self applyModifiers];
|
||||
} else {
|
||||
[self removePlayerTimeObserver];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +659,12 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
_playInBackground = playInBackground;
|
||||
}
|
||||
|
||||
- (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback
|
||||
{
|
||||
_allowsExternalPlayback = allowsExternalPlayback;
|
||||
_player.allowsExternalPlayback = _allowsExternalPlayback;
|
||||
}
|
||||
|
||||
- (void)setPlayWhenInactive:(BOOL)playWhenInactive
|
||||
{
|
||||
_playWhenInactive = playWhenInactive;
|
||||
@@ -571,30 +701,42 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
|
||||
- (void)setCurrentTime:(float)currentTime
|
||||
{
|
||||
[self setSeek: currentTime];
|
||||
NSDictionary *info = @{
|
||||
@"time": [NSNumber numberWithFloat:currentTime],
|
||||
@"tolerance": [NSNumber numberWithInt:100]
|
||||
};
|
||||
[self setSeek:info];
|
||||
}
|
||||
|
||||
- (void)setSeek:(float)seekTime
|
||||
- (void)setSeek:(NSDictionary *)info
|
||||
{
|
||||
int timeScale = 10000;
|
||||
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, timeScale);
|
||||
CMTime cmSeekTime = CMTimeMakeWithSeconds([seekTime floatValue], timeScale);
|
||||
CMTime current = item.currentTime;
|
||||
// TODO figure out a good tolerance level
|
||||
CMTime tolerance = CMTimeMake(1000, timeScale);
|
||||
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 (!wasPaused) [_player play];
|
||||
if (!_timeObserver) {
|
||||
[self addPlayerTimeObserver];
|
||||
}
|
||||
if (!wasPaused) {
|
||||
[self setPaused:false];
|
||||
}
|
||||
if(self.onVideoSeek) {
|
||||
self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)],
|
||||
@"seekTime": [NSNumber numberWithFloat:seekTime],
|
||||
@"seekTime": seekTime,
|
||||
@"target": self.reactTag});
|
||||
}
|
||||
}];
|
||||
@@ -605,7 +747,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
} else {
|
||||
// TODO: See if this makes sense and if so, actually implement it
|
||||
_pendingSeek = true;
|
||||
_pendingSeekTime = seekTime;
|
||||
_pendingSeekTime = [seekTime floatValue];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,17 +778,175 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
[_player setVolume:_volume];
|
||||
[_player setMuted:NO];
|
||||
}
|
||||
|
||||
|
||||
[self setSelectedTextTrack:_selectedTextTrack];
|
||||
[self setResizeMode:_resizeMode];
|
||||
[self setRepeat:_repeat];
|
||||
[self setPaused:_paused];
|
||||
[self setControls:_controls];
|
||||
[self setAllowsExternalPlayback:_allowsExternalPlayback];
|
||||
}
|
||||
|
||||
- (void)setRepeat:(BOOL)repeat {
|
||||
_repeat = repeat;
|
||||
}
|
||||
|
||||
- (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack {
|
||||
_selectedTextTrack = selectedTextTrack;
|
||||
if (_textTracks) {
|
||||
[self setSideloadedText];
|
||||
} else {
|
||||
[self setStreamingText];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setSideloadedText {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int selectedTrackIndex = -1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
} else if ([type isEqualToString:@"index"]) {
|
||||
if ([_selectedTextTrack[@"value"] isKindOfClass:[NSNumber class]]) {
|
||||
int index = [_selectedTextTrack[@"value"] intValue];
|
||||
if (textTracks.count > index) {
|
||||
selectedTrackIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// user's selected language might not be available, or system defaults have captions enabled
|
||||
if (selectedTrackIndex == -1 || [type isEqualToString:@"default"]) {
|
||||
CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser);
|
||||
NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics;
|
||||
if ([captionSettings containsObject: AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) {
|
||||
// iterate through the textTracks to find a matching option, or default to the first object.
|
||||
selectedTrackIndex = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) {
|
||||
BOOL isEnabled = i == selectedTrackIndex + firstTextIndex;
|
||||
[_player.currentItem.tracks[i] setEnabled:isEnabled];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) setStreamingText {
|
||||
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]
|
||||
valueForKey:@"value"]
|
||||
objectAtIndex:0];
|
||||
}
|
||||
if ([value isEqualToString:optionValue]) {
|
||||
mediaOption = currentOption;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//} 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];
|
||||
}
|
||||
|
||||
- (void)setTextTracks:(NSArray*) textTracks;
|
||||
{
|
||||
_textTracks = textTracks;
|
||||
|
||||
// in case textTracks was set after selectedTextTrack
|
||||
if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack];
|
||||
}
|
||||
|
||||
- (NSArray *)getTextTrackInfo
|
||||
{
|
||||
|
||||
// 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];
|
||||
}
|
||||
NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @"";
|
||||
NSDictionary *textTrack = @{
|
||||
@"index": [NSNumber numberWithInt:i],
|
||||
@"title": title,
|
||||
@"language": language
|
||||
};
|
||||
[textTracks addObject:textTrack];
|
||||
}
|
||||
return textTracks;
|
||||
}
|
||||
|
||||
- (BOOL)getFullscreen
|
||||
{
|
||||
return _fullscreenPlayerPresented;
|
||||
@@ -723,6 +1023,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
// 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;
|
||||
@@ -751,27 +1052,23 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
- (void)setProgressUpdateInterval:(float)progressUpdateInterval
|
||||
{
|
||||
_progressUpdateInterval = progressUpdateInterval;
|
||||
|
||||
if (_timeObserver) {
|
||||
[self removePlayerTimeObserver];
|
||||
[self addPlayerTimeObserver];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removePlayerLayer
|
||||
{
|
||||
[_playerLayer removeFromSuperlayer];
|
||||
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
|
||||
if (_playerLayerObserverSet) {
|
||||
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
|
||||
_playerLayerObserverSet = NO;
|
||||
}
|
||||
_playerLayer = nil;
|
||||
}
|
||||
|
||||
#pragma mark - DVAssetLoaderDelegate
|
||||
|
||||
- (void)dvAssetLoaderDelegate:(DVAssetLoaderDelegate *)loaderDelegate
|
||||
didLoadData:(NSData *)data
|
||||
forURL:(NSURL *)url {
|
||||
[_videoCache storeItem:data forUri:[url absoluteString] withCallback:^(BOOL success) {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"data stored succesfully 🎉");
|
||||
#endif
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - RCTVideoPlayerViewControllerDelegate
|
||||
|
||||
- (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController
|
||||
|
@@ -23,6 +23,9 @@ RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(cache, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
|
||||
@@ -31,7 +34,7 @@ RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(rate, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(seek, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(currentTime, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
|
||||
@@ -44,6 +47,7 @@ RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTBubblingEventBlock);
|
||||
@@ -63,4 +67,9 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock);
|
||||
};
|
||||
}
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -8,13 +8,9 @@
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewDidDisappear:animated];
|
||||
[_rctDelegate videoPlayerViewControllerDidDismiss:self];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[_rctDelegate videoPlayerViewControllerWillDismiss:self];
|
||||
[super viewWillDisappear:animated];
|
||||
[super viewDidDisappear:animated];
|
||||
[_rctDelegate videoPlayerViewControllerWillDismiss:self];
|
||||
[_rctDelegate videoPlayerViewControllerDidDismiss:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
Reference in New Issue
Block a user