Skip to content

Commit

Permalink
Merge pull request #2679 from nbennink/fix/disable-sideloaded-texttracks
Browse files Browse the repository at this point in the history
fix(texttracks): unable to disable sideloaded texttracks in the AVPlayer
  • Loading branch information
hueniverse authored Jun 23, 2022
2 parents 52eb556 + 1597d7f commit 41731a8
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 13 deletions.
19 changes: 11 additions & 8 deletions ios/Video/Features/RCTPlayerOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ enum RCTPlayerOperations {
static func setSideloadedText(player:AVPlayer?, textTracks:[TextTrack]?, criteria:SelectedTrackCriteria?) {
let type = criteria?.type
let textTracks:[TextTrack]! = textTracks ?? RCTVideoUtils.getTextTrackInfo(player)

let trackCount:Int! = player?.currentItem?.tracks.count ?? 0

// The first few tracks will be audio & video track
let firstTextIndex:Int = 0
for firstTextIndex in 0..<(player?.currentItem?.tracks.count ?? 0) {
if player?.currentItem?.tracks[firstTextIndex].assetTrack?.hasMediaCharacteristic(.legible) ?? false {
var firstTextIndex:Int = 0
for i in 0..<(trackCount) {
if player?.currentItem?.tracks[i].assetTrack?.hasMediaCharacteristic(.legible) ?? false {
firstTextIndex = i
break
}
}

var selectedTrackIndex:Int = RCTVideoUnset

if (type == "disabled") {
// Do nothing. We want to ensure option is nil
// Select the last text index which is the disabled text track
selectedTrackIndex = trackCount - firstTextIndex
} else if (type == "language") {
let selectedValue = criteria?.value as? String
for i in 0..<textTracks.count {
Expand Down Expand Up @@ -53,7 +56,7 @@ enum RCTPlayerOperations {

// in the situation that a selected text track is not available (eg. specifies a textTrack not available)
if (type != "disabled") && selectedTrackIndex == RCTVideoUnset {
let captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(.user) as! CFArray
let captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(.user)
let captionSettings = captioningMediaCharacteristics as? [AnyHashable]
if ((captionSettings?.contains(AVMediaCharacteristic.transcribesSpokenDialogForAccessibility)) != nil) {
selectedTrackIndex = 0 // If we can't find a match, use the first available track
Expand All @@ -67,8 +70,8 @@ enum RCTPlayerOperations {
}
}
}

for i in firstTextIndex..<(player?.currentItem?.tracks.count ?? 0) {
for i in firstTextIndex..<(trackCount) {
var isEnabled = false
if selectedTrackIndex != RCTVideoUnset {
isEnabled = i == selectedTrackIndex + firstTextIndex
Expand Down
46 changes: 42 additions & 4 deletions ios/Video/Features/RCTVideoUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,17 @@ enum RCTVideoUtils {
return 0
}

static func urlFilePath(filepath:NSString!) -> NSURL! {
static func urlFilePath(filepath:NSString!, searchPath:FileManager.SearchPathDirectory) -> NSURL! {
if filepath.contains("file://") {
return NSURL(string: filepath as String)
}

// if no file found, check if the file exists in the Document directory
let paths:[String]! = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let paths:[String]! = NSSearchPathForDirectoriesInDomains(searchPath, .userDomainMask, true)
var relativeFilePath:String! = filepath.lastPathComponent
// the file may be multiple levels below the documents directory
let fileComponents:[String]! = filepath.components(separatedBy: "Documents/")
let directoryString:String! = searchPath == .cachesDirectory ? "Library/Caches/" : "Documents";
let fileComponents:[String]! = filepath.components(separatedBy: directoryString)
if fileComponents.count > 1 {
relativeFilePath = fileComponents[1]
}
Expand Down Expand Up @@ -192,14 +193,17 @@ enum RCTVideoUtils {
static func getValidTextTracks(asset:AVAsset, assetOptions:NSDictionary?, mixComposition:AVMutableComposition, textTracks:[TextTrack]?) -> [TextTrack] {
let videoAsset:AVAssetTrack! = asset.tracks(withMediaType: AVMediaType.video).first
var validTextTracks:[TextTrack] = []

if let textTracks = textTracks, textTracks.count > 0 {
for i in 0..<textTracks.count {
var textURLAsset:AVURLAsset!
let textUri:String = textTracks[i].uri
if textUri.lowercased().hasPrefix("http") {
textURLAsset = AVURLAsset(url: NSURL(string: textUri)! as URL, options:(assetOptions as! [String : Any]))
} else {
textURLAsset = AVURLAsset(url: RCTVideoUtils.urlFilePath(filepath: textUri as NSString?) as URL, options:nil)
let isDisabledTrack:Bool! = textTracks[i].type == "disabled"
let searchPath:FileManager.SearchPathDirectory = isDisabledTrack ? .cachesDirectory : .documentDirectory;
textURLAsset = AVURLAsset(url: RCTVideoUtils.urlFilePath(filepath: textUri as NSString?, searchPath: searchPath) as URL, options:nil)
}
let textTrackAsset:AVAssetTrack! = textURLAsset.tracks(withMediaType: AVMediaType.text).first
if (textTrackAsset == nil) {continue} // fix when there's no textTrackAsset
Expand All @@ -215,9 +219,43 @@ enum RCTVideoUtils {
}
}
}

let emptyVttFile:TextTrack? = self.createEmptyVttFile()
if (emptyVttFile != nil) {
validTextTracks.append(emptyVttFile!)
}

return validTextTracks
}

/*
* 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
*/
static func createEmptyVttFile() -> TextTrack? {
let fileManager = FileManager.default
let cachesDirectoryUrl = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let filePath = cachesDirectoryUrl.appendingPathComponent("empty.vtt").path

if !fileManager.fileExists(atPath: filePath) {
let stringToWrite = "WEBVTT\n\n1\n99:59:59.000 --> 99:59:59.001\n."

do {
try stringToWrite.write(to: URL(fileURLWithPath: filePath), atomically: true, encoding: String.Encoding.utf8)
} catch {
return nil
}
}

return TextTrack([
"language": "disabled",
"title": "EmptyVttFile",
"type": "text/vtt",
"uri": filePath,
])
}

static func delay(seconds: Int = 0) -> Promise<Void> {
return Promise<Void>(on: .global()) { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds)) / Double(NSEC_PER_SEC), execute: {
Expand Down
2 changes: 1 addition & 1 deletion ios/Video/RCTVideoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React
class RCTVideoManager: RCTViewManager {

override func view() -> UIView {
return RCTVideo(eventDispatcher: bridge.eventDispatcher())
return RCTVideo(eventDispatcher: bridge.eventDispatcher() as! RCTEventDispatcher)
}

func methodQueue() -> DispatchQueue {
Expand Down

0 comments on commit 41731a8

Please sign in to comment.