Merge pull request #2679 from nbennink/fix/disable-sideloaded-texttracks
fix(texttracks): unable to disable sideloaded texttracks in the AVPlayer
This commit is contained in:
commit
41731a8117
@ -12,11 +12,13 @@ enum RCTPlayerOperations {
|
|||||||
static func setSideloadedText(player:AVPlayer?, textTracks:[TextTrack]?, criteria:SelectedTrackCriteria?) {
|
static func setSideloadedText(player:AVPlayer?, textTracks:[TextTrack]?, criteria:SelectedTrackCriteria?) {
|
||||||
let type = criteria?.type
|
let type = criteria?.type
|
||||||
let textTracks:[TextTrack]! = textTracks ?? RCTVideoUtils.getTextTrackInfo(player)
|
let textTracks:[TextTrack]! = textTracks ?? RCTVideoUtils.getTextTrackInfo(player)
|
||||||
|
let trackCount:Int! = player?.currentItem?.tracks.count ?? 0
|
||||||
|
|
||||||
// The first few tracks will be audio & video track
|
// The first few tracks will be audio & video track
|
||||||
let firstTextIndex:Int = 0
|
var firstTextIndex:Int = 0
|
||||||
for firstTextIndex in 0..<(player?.currentItem?.tracks.count ?? 0) {
|
for i in 0..<(trackCount) {
|
||||||
if player?.currentItem?.tracks[firstTextIndex].assetTrack?.hasMediaCharacteristic(.legible) ?? false {
|
if player?.currentItem?.tracks[i].assetTrack?.hasMediaCharacteristic(.legible) ?? false {
|
||||||
|
firstTextIndex = i
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,7 +26,8 @@ enum RCTPlayerOperations {
|
|||||||
var selectedTrackIndex:Int = RCTVideoUnset
|
var selectedTrackIndex:Int = RCTVideoUnset
|
||||||
|
|
||||||
if (type == "disabled") {
|
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") {
|
} else if (type == "language") {
|
||||||
let selectedValue = criteria?.value as? String
|
let selectedValue = criteria?.value as? String
|
||||||
for i in 0..<textTracks.count {
|
for i in 0..<textTracks.count {
|
||||||
@ -53,7 +56,7 @@ enum RCTPlayerOperations {
|
|||||||
|
|
||||||
// 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 != "disabled") && selectedTrackIndex == RCTVideoUnset {
|
if (type != "disabled") && selectedTrackIndex == RCTVideoUnset {
|
||||||
let captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(.user) as! CFArray
|
let captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(.user)
|
||||||
let captionSettings = captioningMediaCharacteristics as? [AnyHashable]
|
let captionSettings = captioningMediaCharacteristics as? [AnyHashable]
|
||||||
if ((captionSettings?.contains(AVMediaCharacteristic.transcribesSpokenDialogForAccessibility)) != nil) {
|
if ((captionSettings?.contains(AVMediaCharacteristic.transcribesSpokenDialogForAccessibility)) != nil) {
|
||||||
selectedTrackIndex = 0 // If we can't find a match, use the first available track
|
selectedTrackIndex = 0 // If we can't find a match, use the first available track
|
||||||
@ -67,8 +70,8 @@ enum RCTPlayerOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in firstTextIndex..<(player?.currentItem?.tracks.count ?? 0) {
|
for i in firstTextIndex..<(trackCount) {
|
||||||
var isEnabled = false
|
var isEnabled = false
|
||||||
if selectedTrackIndex != RCTVideoUnset {
|
if selectedTrackIndex != RCTVideoUnset {
|
||||||
isEnabled = i == selectedTrackIndex + firstTextIndex
|
isEnabled = i == selectedTrackIndex + firstTextIndex
|
||||||
|
@ -37,16 +37,17 @@ enum RCTVideoUtils {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
static func urlFilePath(filepath:NSString!) -> NSURL! {
|
static func urlFilePath(filepath:NSString!, searchPath:FileManager.SearchPathDirectory) -> NSURL! {
|
||||||
if filepath.contains("file://") {
|
if filepath.contains("file://") {
|
||||||
return NSURL(string: filepath as String)
|
return NSURL(string: filepath as String)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
let paths:[String]! = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
let paths:[String]! = NSSearchPathForDirectoriesInDomains(searchPath, .userDomainMask, true)
|
||||||
var relativeFilePath:String! = filepath.lastPathComponent
|
var relativeFilePath:String! = filepath.lastPathComponent
|
||||||
// the file may be multiple levels below the documents directory
|
// 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 {
|
if fileComponents.count > 1 {
|
||||||
relativeFilePath = fileComponents[1]
|
relativeFilePath = fileComponents[1]
|
||||||
}
|
}
|
||||||
@ -192,6 +193,7 @@ enum RCTVideoUtils {
|
|||||||
static func getValidTextTracks(asset:AVAsset, assetOptions:NSDictionary?, mixComposition:AVMutableComposition, textTracks:[TextTrack]?) -> [TextTrack] {
|
static func getValidTextTracks(asset:AVAsset, assetOptions:NSDictionary?, mixComposition:AVMutableComposition, textTracks:[TextTrack]?) -> [TextTrack] {
|
||||||
let videoAsset:AVAssetTrack! = asset.tracks(withMediaType: AVMediaType.video).first
|
let videoAsset:AVAssetTrack! = asset.tracks(withMediaType: AVMediaType.video).first
|
||||||
var validTextTracks:[TextTrack] = []
|
var validTextTracks:[TextTrack] = []
|
||||||
|
|
||||||
if let textTracks = textTracks, textTracks.count > 0 {
|
if let textTracks = textTracks, textTracks.count > 0 {
|
||||||
for i in 0..<textTracks.count {
|
for i in 0..<textTracks.count {
|
||||||
var textURLAsset:AVURLAsset!
|
var textURLAsset:AVURLAsset!
|
||||||
@ -199,7 +201,9 @@ enum RCTVideoUtils {
|
|||||||
if textUri.lowercased().hasPrefix("http") {
|
if textUri.lowercased().hasPrefix("http") {
|
||||||
textURLAsset = AVURLAsset(url: NSURL(string: textUri)! as URL, options:(assetOptions as! [String : Any]))
|
textURLAsset = AVURLAsset(url: NSURL(string: textUri)! as URL, options:(assetOptions as! [String : Any]))
|
||||||
} else {
|
} 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
|
let textTrackAsset:AVAssetTrack! = textURLAsset.tracks(withMediaType: AVMediaType.text).first
|
||||||
if (textTrackAsset == nil) {continue} // fix when there's no textTrackAsset
|
if (textTrackAsset == nil) {continue} // fix when there's no textTrackAsset
|
||||||
@ -215,9 +219,43 @@ enum RCTVideoUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let emptyVttFile:TextTrack? = self.createEmptyVttFile()
|
||||||
|
if (emptyVttFile != nil) {
|
||||||
|
validTextTracks.append(emptyVttFile!)
|
||||||
|
}
|
||||||
|
|
||||||
return validTextTracks
|
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> {
|
static func delay(seconds: Int = 0) -> Promise<Void> {
|
||||||
return Promise<Void>(on: .global()) { fulfill, reject in
|
return Promise<Void>(on: .global()) { fulfill, reject in
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds)) / Double(NSEC_PER_SEC), execute: {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds)) / Double(NSEC_PER_SEC), execute: {
|
||||||
|
@ -5,7 +5,7 @@ import React
|
|||||||
class RCTVideoManager: RCTViewManager {
|
class RCTVideoManager: RCTViewManager {
|
||||||
|
|
||||||
override func view() -> UIView {
|
override func view() -> UIView {
|
||||||
return RCTVideo(eventDispatcher: bridge.eventDispatcher())
|
return RCTVideo(eventDispatcher: bridge.eventDispatcher() as! RCTEventDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
func methodQueue() -> DispatchQueue {
|
func methodQueue() -> DispatchQueue {
|
||||||
|
Loading…
Reference in New Issue
Block a user