mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-06-04 11:35:13 +00:00
fix: external subtitle asset composition (#4830)
* fix: external subtitle asset composition * fix: filter for supported subtitles before adding them
This commit is contained in:
+8
-8
@@ -15,18 +15,18 @@ extension AVPlayerItem {
|
|||||||
if config.externalSubtitles?.isEmpty != false {
|
if config.externalSubtitles?.isEmpty != false {
|
||||||
return AVPlayerItem(asset: asset)
|
return AVPlayerItem(asset: asset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let supportedExternalSubtitles = config.externalSubtitles?.filter { subtitle in
|
||||||
|
ExternalSubtitlesUtils.isSubtitleTypeSupported(subtitle: subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if supportedExternalSubtitles?.isEmpty == true {
|
||||||
|
return AVPlayerItem(asset: asset)
|
||||||
|
}
|
||||||
|
|
||||||
if asset.url.pathExtension == "m3u8" {
|
if asset.url.pathExtension == "m3u8" {
|
||||||
let supportedExternalSubtitles = config.externalSubtitles?.filter { subtitle in
|
|
||||||
ExternalSubtitlesUtils.isSubtitleTypeSupported(subtitle: subtitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if supportedExternalSubtitles?.isEmpty == true {
|
|
||||||
return AVPlayerItem(asset: asset)
|
|
||||||
} else {
|
|
||||||
return try await ExternalSubtitlesUtils.modifyStreamManifestWithExternalSubtitles(
|
return try await ExternalSubtitlesUtils.modifyStreamManifestWithExternalSubtitles(
|
||||||
for: asset, config: config)
|
for: asset, config: config)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return try await ExternalSubtitlesUtils.createCompositionWithExternalSubtitles(
|
return try await ExternalSubtitlesUtils.createCompositionWithExternalSubtitles(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum ExternalSubtitlesUtils {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("[ReactNativeVideo] Unsupported external subtitle. Expected VTT. uri: \(subtitle.uri)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,73 +21,67 @@ enum ExternalSubtitlesUtils {
|
|||||||
for asset: AVURLAsset,
|
for asset: AVURLAsset,
|
||||||
config: NativeVideoConfig
|
config: NativeVideoConfig
|
||||||
) async throws -> AVPlayerItem {
|
) async throws -> AVPlayerItem {
|
||||||
let subtitlesAssets = try config.externalSubtitles?.map { subtitle in
|
let supportedSubtitles = (config.externalSubtitles ?? []).filter { subtitle in
|
||||||
|
isSubtitleTypeSupported(subtitle: subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtitleAssets: [AVURLAsset] = try supportedSubtitles.map { subtitle in
|
||||||
guard let url = URL(string: subtitle.uri) else {
|
guard let url = URL(string: subtitle.uri) else {
|
||||||
throw PlayerError.invalidTrackUrl(url: subtitle.uri).error()
|
throw PlayerError.invalidTrackUrl(url: subtitle.uri).error()
|
||||||
}
|
}
|
||||||
|
|
||||||
return AVURLAsset(url: url)
|
return AVURLAsset(url: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
let mainDuration = try await asset.load(.duration)
|
||||||
let mainVideoTracks = asset.tracks(withMediaType: .video)
|
|
||||||
let mainAudioTracks = asset.tracks(withMediaType: .audio)
|
|
||||||
let textTracks =
|
|
||||||
subtitlesAssets?.flatMap { $0.tracks(withMediaType: .text) } ?? []
|
|
||||||
|
|
||||||
let composition = AVMutableComposition()
|
let composition = AVMutableComposition()
|
||||||
|
|
||||||
if let videoTrack = mainVideoTracks.first(where: { $0.mediaType == .video }){
|
let tracks = try await asset.load(.tracks)
|
||||||
if let compositionVideoTrack = composition.addMutableTrack(
|
for track in tracks {
|
||||||
withMediaType: .video,
|
guard let compTrack = composition.addMutableTrack(
|
||||||
preferredTrackID: kCMPersistentTrackID_Invalid
|
withMediaType: track.mediaType,
|
||||||
) {
|
preferredTrackID: kCMPersistentTrackID_Invalid
|
||||||
try compositionVideoTrack.insertTimeRange(
|
) else { continue }
|
||||||
CMTimeRange(start: .zero, duration: videoTrack.timeRange.duration),
|
|
||||||
of: videoTrack,
|
do {
|
||||||
at: .zero
|
try compTrack.insertTimeRange(
|
||||||
)
|
CMTimeRange(start: .zero, duration: mainDuration),
|
||||||
}
|
of: track,
|
||||||
|
at: .zero
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
print("[ReactNativeVideo] Error inserting main track \(track.mediaType.rawValue): \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let audioTrack = mainAudioTracks.first(where: { $0.mediaType == .audio }) {
|
|
||||||
if let compositionAudioTrack = composition.addMutableTrack(
|
|
||||||
withMediaType: .audio,
|
|
||||||
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
||||||
) {
|
|
||||||
try compositionAudioTrack.insertTimeRange(
|
|
||||||
CMTimeRange(start: .zero, duration: audioTrack.timeRange.duration),
|
|
||||||
of: audioTrack,
|
|
||||||
at: .zero
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for textTrack in textTracks {
|
|
||||||
if let compositionTextTrack = composition.addMutableTrack(
|
|
||||||
withMediaType: .text,
|
|
||||||
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
||||||
) {
|
|
||||||
do {
|
|
||||||
try compositionTextTrack.insertTimeRange(
|
|
||||||
CMTimeRange(start: .zero, duration: textTrack.timeRange.duration),
|
|
||||||
of: textTrack,
|
|
||||||
at: .zero
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
print(
|
|
||||||
"[ReactNativeVideo] Failed to insert text track into composition: \(error.localizedDescription). Language: \(textTrack.languageCode ?? "unknown"). Continuing without this subtitle track."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
compositionTextTrack.languageCode = textTrack.languageCode
|
|
||||||
compositionTextTrack.isEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await AVPlayerItem(asset: composition)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for subtitleAsset in subtitleAssets {
|
||||||
|
let track: AVAssetTrack? = try await subtitleAsset.loadTracks(withMediaType: .text).first
|
||||||
|
|
||||||
|
guard let track else { continue }
|
||||||
|
|
||||||
|
guard let compSubtitleTrack = composition.addMutableTrack(
|
||||||
|
withMediaType: track.mediaType,
|
||||||
|
preferredTrackID: kCMPersistentTrackID_Invalid
|
||||||
|
) else { continue }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let trackRange = try await track.load(.timeRange)
|
||||||
|
let effectiveDuration = CMTimeMinimum(trackRange.duration, mainDuration)
|
||||||
|
try compSubtitleTrack.insertTimeRange(
|
||||||
|
CMTimeRange(start: .zero, duration: effectiveDuration),
|
||||||
|
of: track,
|
||||||
|
at: .zero
|
||||||
|
)
|
||||||
|
|
||||||
|
compSubtitleTrack.languageCode = try await track.load(.languageCode)
|
||||||
|
compSubtitleTrack.isEnabled = true
|
||||||
|
} catch {
|
||||||
|
print("[ReactNativeVideo] Error inserting subtitle track: \(error.localizedDescription)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await AVPlayerItem(asset: composition)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func modifyStreamManifestWithExternalSubtitles(
|
static func modifyStreamManifestWithExternalSubtitles(
|
||||||
|
|||||||
Reference in New Issue
Block a user