Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(texttracks): unable to disable sideloaded texttracks in the AVPlayer #2679

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 60 additions & 13 deletions ios/Video/RCTVideo.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ @implementation RCTVideo
BOOL _paused;
BOOL _repeat;
BOOL _allowsExternalPlayback;
NSArray * _textTracks;
NSMutableArray * _textTracks;
NSDictionary * _selectedTextTrack;
NSDictionary * _selectedAudioTrack;
BOOL _playbackStalled;
Expand Down Expand Up @@ -97,7 +97,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_eventDispatcher = eventDispatcher;
_automaticallyWaitsToMinimizeStalling = YES;
_automaticallyWaitsToMinimizeStalling = YES;
_playbackRateObserverRegistered = NO;
_isExternalPlaybackActiveObserverRegistered = NO;
_playbackStalled = NO;
Expand Down Expand Up @@ -418,16 +418,18 @@ - (void)setDrm:(NSDictionary *)drm {
_drm = drm;
}

- (NSURL*) urlFilePath:(NSString*) filepath {
- (NSURL*) urlFilePath:(NSString*) filepath searchPath:(enum NSSearchPathDirectory) searchPath {
if ([filepath containsString:@"file://"]) {
return [NSURL URLWithString:filepath];
}

// if no file found, check if the file exists in the Document directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// if no file found, check if the file exists in the given searchPath
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
NSString* relativeFilePath = [filepath lastPathComponent];
// the file may be multiple levels below the documents directory
NSArray* fileComponents = [filepath componentsSeparatedByString:@"Documents/"];

// the file may be multiple levels below the in the given searchPath
NSString *directoryString = searchPath == NSCachesDirectory ? @"Library/Caches/" : @"Documents/";
NSArray *fileComponents = [filepath componentsSeparatedByString:directoryString];
if (fileComponents.count > 1) {
relativeFilePath = [fileComponents objectAtIndex:1];
}
Expand Down Expand Up @@ -467,13 +469,22 @@ - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nu
error:nil];

NSMutableArray* validTextTracks = [NSMutableArray array];

NSMutableDictionary *emptyVttTrack = [self createDisabledVttFile];
if (emptyVttTrack) [_textTracks addObject:emptyVttTrack];

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];
BOOL isDisabledTrack = [[_textTracks objectAtIndex:i][@"language"] isEqualToString:@"disabled"];

// Search the track to disabled subtitles in the Caches directory, search every other local file in the Documents directory
NSSearchPathDirectory searchPath = isDisabledTrack ? NSCachesDirectory : NSDocumentDirectory;
NSURL *assetUrl = [self urlFilePath:textUri searchPath:searchPath];
textURLAsset = [AVURLAsset URLAssetWithURL:assetUrl options:nil];
}
AVAssetTrack *textTrackAsset = [textURLAsset tracksWithMediaType:AVMediaTypeText].firstObject;
if (!textTrackAsset) continue; // fix when there's no textTrackAsset
Expand All @@ -493,6 +504,36 @@ - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nu
handler([AVPlayerItem playerItemWithAsset:mixComposition]);
}

/*
* Create an useless / almost empty VTT file in the list with available tracks. This track gets selected when you give type: "disabled" as the selectedTextTrack
* This is needed because there is a bug where sideloaded texttracks cannot be disabled in the AVPlayer. Loading this VTT file instead solves that problem.
* For more info see: https://github.com/react-native-community/react-native-video/issues/1144
*/
- (NSMutableDictionary*)createDisabledVttFile {
NSError *error;
NSMutableDictionary *emptyVTTDictionary = nil;

// Write the file into the Caches directory because that directory is available on iOS and tvOS
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"empty.vtt"];

if (![[NSFileManager defaultManager] isReadableFileAtPath:filePath]){
// WebVTT should have at least 1 cue to be valid. That's why a small dot is visible for 1 ms at the 99:59:59.000 timestamp
NSString *stringToWrite = @"WEBVTT\n\n1\n99:59:59.000 --> 99:59:59.001\n.";

[stringToWrite writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
}

if (!error){
emptyVTTDictionary = [[NSMutableDictionary alloc] init];
emptyVTTDictionary[@"language"] = @"disabled";
emptyVTTDictionary[@"title"] = @"EmptyVttFile";
emptyVTTDictionary[@"uri"] = filePath;
emptyVTTDictionary[@"type"] = @"text/vtt";
}

return emptyVTTDictionary;
}

- (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler
{
bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]];
Expand Down Expand Up @@ -1037,8 +1078,8 @@ - (void)setPreferredForwardBufferDuration:(float) preferredForwardBufferDuration

- (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits
{
_automaticallyWaitsToMinimizeStalling = waits;
_player.automaticallyWaitsToMinimizeStalling = waits;
_automaticallyWaitsToMinimizeStalling = waits;
_player.automaticallyWaitsToMinimizeStalling = waits;
}


Expand Down Expand Up @@ -1183,9 +1224,10 @@ - (void) setSideloadedText {
}

int selectedTrackIndex = RCTVideoUnset;

if ([type isEqualToString:@"disabled"]) {
// Do nothing. We want to ensure option is nil
// Enable the empty texttrack
[_player.currentItem.tracks[_player.currentItem.tracks.count - 1] setEnabled:YES];
} else if ([type isEqualToString:@"language"]) {
NSString *selectedValue = _selectedTextTrack[@"value"];
for (int i = 0; i < textTracks.count; ++i) {
Expand Down Expand Up @@ -1324,17 +1366,22 @@ - (NSArray *)getTextTrackInfo
mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
for (int i = 0; i < group.options.count; ++i) {
AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];
NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @"";

// Ignore the texttrack with language disabled. This is not a real texttrack and can be ignored
if ([language isEqualToString:@"disabled"]) continue;

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;
Expand Down