I have an app that plays Vimeo videos in AVPlayer
. Previously I played them in AVPlayerLayer
and handled switching caption language (or hiding captions) with UI I implemented. Now I've been asked to switch to AVPlayerViewController
, but when it plays the same videos its caption language switching UI (basically a little text bubble icon for the languages menu) never appears, and the video always plays with English captions, regardless of language selected in iOS Settings:General.
To create the AVAsset
for AVPlayer
, I fetch the captions as .vtt files, and create a combined AVMutableComposition
with both the video, audio and text tracks using the following code. The videos can be cached locally or streamed so the URL path can be to a remote server or local, but I always download the text tracks so the textTrackPath
is always local.
func createCaptionedVideoFrom(_ localVideoAsset: AVAsset, url: URL) -> AVMutableComposition? {
do {
let fileName = url.lastPathComponent
guard let tracksRecord = TextTracks.tracksForGroupLesson(self) else {
return nil // no langauge tracks
}
// Create resource for combined tracks
let videoPlusSubtitles = AVMutableComposition()
// Add video track
guard let existingVideo = localVideoAsset.tracks(withMediaType: .video)[safe: 0] else {
Logger.error("No video track in local video asset named: (name) fileName: (fileName)")
return nil
}
guard let videoTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
Logger.error("Could not create video track in new video named: (name) fileName: (fileName)")
return nil
}
try videoTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: localVideoAsset.duration), of: existingVideo, at: CMTime.zero)
// Add audio track
guard let existingAudio = localVideoAsset.tracks(withMediaType: .audio)[safe: 0] else {
Logger.error("No audio track in local video asset named: (name) fileName: (fileName)")
return nil
}
guard let audioTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
Logger.error("Can't create audio track in new video named: (name) fileName: (fileName)")
return nil
}
try audioTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: localVideoAsset.duration), of: existingAudio, at: CMTime.zero)
for textTrack in tracksRecord.tracks {
// add text track
guard let textTrackPath = textTrack.cachedLocalPath else {
Logger.error("Text track: (textTrack) for group lesson: (id) not cached for offline use.")
return nil
}
let subtitleAsset = AVURLAsset(url: URL(fileURLWithPath: textTrackPath))
guard let existingTrack = subtitleAsset.tracks(withMediaType: .text).first else {
Logger.error("No text track in local video asset named: (name) fileName: (fileName)")
return nil
}
guard let subtitleTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid) else {
Logger.error("Could not create text track in new video named: (name) fileName: (fileName)")
return nil
}
subtitleTrack.languageCode = textTrack.language
subtitleTrack.extendedLanguageTag = textTrack.language
try subtitleTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: localVideoAsset.duration), of: existingTrack, at: CMTime.zero)
if subtitleTrack.hasMediaCharacteristic(AVMediaCharacteristic.legible) {
print("New (textTrack.language) is legible")
}
}
return videoPlusSubtitles
} catch {
Logger.error("Could not write text track for video lesson: (name), error: (error)")
}
return nil
}
func playVideo(_ video: AVAsset) {
let assetKeys = [
"playable"
]
let playerItem = AVPlayerItem(asset: video,
automaticallyLoadedAssetKeys: assetKeys)
let videoPlayer = AVPlayer(playerItem: playerItem)
videoPlayer.allowsExternalPlayback = true
let avPVC = AVPlayerViewController()
avPVC.player = videoPlayer
avPVC.showsPlaybackControls = true
self.present(avPVC, animated: true) {
self.playerController = avPVC
videoPlayer.play()
}
}
The video, audio and text tracks are always created properly, and the text tracks always have the mediaCharacteristic
of legible
, and videos always have Spanish and English tracks. But weirdly the completed AVMutableComposition
has no mediaCharacteristics
.
a) videoPlusSubtitles.availableMediaCharacteristicsWithMediaSelectionOptions
always returns nil,
b) videoPlusSubtitles.hasMediaCharacteristic(.legible)
always returns nil.
So why does AVPlayerViewController
not display caption controls for both the English and Spanish tracks if it can see the English captions track?
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…