Support loading multiple sideloaded text tracks

This commit is contained in:
Hampton Maxwell 2018-07-09 16:28:38 -07:00
parent 572c11a1dc
commit 93ce4d6b9f

View File

@ -47,7 +47,6 @@ static NSString *const timedMetadata = @"timedMetadata";
BOOL _allowsExternalPlayback; BOOL _allowsExternalPlayback;
NSArray * _textTracks; NSArray * _textTracks;
NSDictionary * _selectedTextTrack; NSDictionary * _selectedTextTrack;
NSDictionary * _selectedText;
BOOL _playbackStalled; BOOL _playbackStalled;
BOOL _playInBackground; BOOL _playInBackground;
BOOL _playWhenInactive; BOOL _playWhenInactive;
@ -352,66 +351,66 @@ static NSString *const timedMetadata = @"timedMetadata";
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 *subtitleUri = _selectedText[@"uri"];
NSString *type = [source objectForKey:@"type"]; NSString *type = [source objectForKey:@"type"];
NSDictionary *headers = [source objectForKey:@"requestHeaders"];
AVURLAsset *asset; AVURLAsset *asset;
AVURLAsset *subAsset; NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init];
if (isNetwork) { if (isNetwork) {
NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc]init];
/* Per #1091, this is not a public API. We need to either get approval from Apple to use this /* Per #1091, this is not a public API. We need to either get approval from Apple to use this
* or use a different approach. * or use a different approach.
NSDictionary *headers = [source objectForKey:@"requestHeaders"];
if ([headers count] > 0) { if ([headers count] > 0) {
[assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"];
} }
*/ */
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
[assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey]; [assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey];
asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions]; asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions];
subAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:subtitleUri] options:assetOptions]; } else if (isAsset) { // assets on iOS have to be in the Documents folder
}
else if (isAsset) // assets on iOS have to be in the Documents folder
{
asset = [AVURLAsset URLAssetWithURL:[self urlFilePath:uri] options:nil]; asset = [AVURLAsset URLAssetWithURL:[self urlFilePath:uri] options:nil];
subAsset = [AVURLAsset URLAssetWithURL:[self urlFilePath:subtitleUri] options:nil]; } else { // file passed in through JS, or an asset in the Xcode project
}
else // file passed in through JS, or an asset in the Xcode project
{
asset = [AVURLAsset URLAssetWithURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]] options:nil]; asset = [AVURLAsset URLAssetWithURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]] options:nil];
} }
if (!_textTracks || !_selectedText) return [AVPlayerItem playerItemWithAsset:asset]; if (!_textTracks) {
return [AVPlayerItem playerItemWithAsset:asset];
// otherwise sideload text tracks }
AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
AVAssetTrack *textAsset = [subAsset tracksWithMediaType:AVMediaTypeText].firstObject;
NSLog(@"assets: %@,%@,%@", videoAsset, audioAsset, textAsset);
// sideload text tracks
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init]; AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
AVMutableCompositionTrack *videoCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *subtitleCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeText preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
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;
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];
[subtitleCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) for (int i = 0; i < _textTracks.count; ++i) {
ofTrack:textAsset 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 atTime:kCMTimeZero
error:nil]; error:nil];
}
return [AVPlayerItem playerItemWithAsset:mixComposition]; return [AVPlayerItem playerItemWithAsset:mixComposition];
} }
@ -425,7 +424,7 @@ static NSString *const timedMetadata = @"timedMetadata";
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 = (NSString *)item.value;
NSString *identifier = item.identifier; NSString *identifier = item.identifier;
if (![value isEqual: [NSNull null]]) { if (![value isEqual: [NSNull null]]) {
@ -730,29 +729,35 @@ static NSString *const timedMetadata = @"timedMetadata";
- (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack {
_selectedTextTrack = selectedTextTrack; _selectedTextTrack = selectedTextTrack;
if (_textTracks) { if (_textTracks) {
[self setSideloadedText]; [self setSideloadedText];
} else {
[self setStreamingText];
} }
else [self setStreamingText];
} }
- (void) setSideloadedText { - (void) setSideloadedText {
NSString *type = _selectedTextTrack[@"type"]; NSString *type = _selectedTextTrack[@"type"];
NSArray* textTracks = [self getTextTrackInfo]; NSArray* textTracks = [self getTextTrackInfo];
_selectedText = nil; // 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"]) { 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"]) {
NSString *selectedValue = _selectedTextTrack[@"value"]; NSString *selectedValue = _selectedTextTrack[@"value"];
for (int i = 0; i < textTracks.count; ++i) { for (int i = 0; i < textTracks.count; ++i) {
NSDictionary *currentTextTrack = [textTracks objectAtIndex:i]; NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];
if ([selectedValue isEqualToString:currentTextTrack[@"language"]]) { if ([selectedValue isEqualToString:currentTextTrack[@"language"]]) {
_selectedText = currentTextTrack; selectedTrackIndex = i;
break; break;
} }
} }
@ -760,9 +765,8 @@ static NSString *const timedMetadata = @"timedMetadata";
NSString *selectedValue = _selectedTextTrack[@"value"]; NSString *selectedValue = _selectedTextTrack[@"value"];
for (int i = 0; i < textTracks.count; ++i) { for (int i = 0; i < textTracks.count; ++i) {
NSDictionary *currentTextTrack = [textTracks objectAtIndex:i]; NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];
if ([selectedValue isEqualToString:currentTextTrack[@"title"]]) { if ([selectedValue isEqualToString:currentTextTrack[@"title"]]) {
_selectedText = currentTextTrack; selectedTrackIndex = i;
break; break;
} }
} }
@ -770,30 +774,34 @@ static NSString *const timedMetadata = @"timedMetadata";
if ([_selectedTextTrack[@"value"] isKindOfClass:[NSNumber class]]) { if ([_selectedTextTrack[@"value"] isKindOfClass:[NSNumber class]]) {
int index = [_selectedTextTrack[@"value"] intValue]; int index = [_selectedTextTrack[@"value"] intValue];
if (textTracks.count > index) { if (textTracks.count > index) {
_selectedText = [textTracks objectAtIndex:index]; selectedTrackIndex = index;
} }
} }
} }
// user's selected language might not be available, or system defaults have captions enabled // user's selected language might not be available, or system defaults have captions enabled
if (_selectedText==nil || [type isEqualToString:@"default"]) { if (selectedTrackIndex == -1 || [type isEqualToString:@"default"]) {
CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser); CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser);
NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics; NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics;
if ([captionSettings containsObject: AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) { if ([captionSettings containsObject: AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) {
// iterate through the textTracks to find a matching option, or default to the first object. // iterate through the textTracks to find a matching option, or default to the first object.
_selectedText = textTracks.firstObject; selectedTrackIndex = firstTextIndex;
NSString * systemLanguage = [[NSLocale preferredLanguages] firstObject]; NSString * systemLanguage = [[NSLocale preferredLanguages] firstObject];
for (int i = 0; i < textTracks.count; ++i) { for (int i = 0; i < textTracks.count; ++i) {
NSDictionary *currentTextTrack = [textTracks objectAtIndex:i]; NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];
if ([systemLanguage isEqualToString:currentTextTrack[@"language"]]) { if ([systemLanguage isEqualToString:currentTextTrack[@"language"]]) {
_selectedText = currentTextTrack; selectedTrackIndex = i;
break; break;
} }
} }
} }
} }
for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) {
BOOL isEnabled = i == selectedTrackIndex + firstTextIndex;
[_player.currentItem.tracks[i] setEnabled:isEnabled];
}
} }
-(void) setStreamingText { -(void) setStreamingText {