@capgo/capacitor-video-player 8.1.21 → 8.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +1 -1
- package/ios/Sources/VideoPlayerPlugin/FullscreenVideoPlayer.swift +24 -94
- package/ios/Sources/VideoPlayerPlugin/HLSSubtitleResourceLoader.swift +185 -0
- package/ios/Sources/VideoPlayerPlugin/ProgressiveVideoPlayerItemFactory.swift +105 -0
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +1 -1
- package/package.json +1 -1
|
@@ -39,7 +39,7 @@ import java.util.Map;
|
|
|
39
39
|
)
|
|
40
40
|
public class VideoPlayerPlugin extends Plugin {
|
|
41
41
|
|
|
42
|
-
private final String pluginVersion = "8.1.
|
|
42
|
+
private final String pluginVersion = "8.1.22";
|
|
43
43
|
|
|
44
44
|
// Permission alias constants
|
|
45
45
|
private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
|
|
@@ -31,6 +31,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
31
31
|
private weak var presentingViewController: UIViewController?
|
|
32
32
|
private var subtitleUrl: String?
|
|
33
33
|
private var subtitleLanguage: String?
|
|
34
|
+
private var hlsResourceLoader: HLSSubtitleResourceLoader?
|
|
34
35
|
|
|
35
36
|
init(
|
|
36
37
|
playerId: String,
|
|
@@ -73,18 +74,29 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
73
74
|
return
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
let asset = makeVideoAsset(url: url)
|
|
77
|
-
|
|
78
77
|
guard let subtitleUrlString = subtitleUrl,
|
|
79
78
|
!subtitleUrlString.isEmpty,
|
|
80
79
|
let subtitleURL = URL(string: subtitleUrlString) else {
|
|
80
|
+
let asset = makeVideoAsset(url: url, subtitleURL: nil)
|
|
81
|
+
configurePlayer(with: AVPlayerItem(asset: asset))
|
|
82
|
+
completion()
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if HLSVideoAssetFactory.isHLSStream(url) {
|
|
87
|
+
let asset = makeVideoAsset(url: url, subtitleURL: subtitleURL)
|
|
81
88
|
configurePlayer(with: AVPlayerItem(asset: asset))
|
|
82
89
|
completion()
|
|
83
90
|
return
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
Task {
|
|
87
|
-
let
|
|
94
|
+
let asset = makeVideoAsset(url: url, subtitleURL: nil)
|
|
95
|
+
let item = await ProgressiveVideoPlayerItemFactory.createPlayerItem(
|
|
96
|
+
videoAsset: asset,
|
|
97
|
+
subtitleURL: subtitleURL,
|
|
98
|
+
subtitleLanguage: subtitleLanguage
|
|
99
|
+
)
|
|
88
100
|
await MainActor.run {
|
|
89
101
|
self.configurePlayer(with: item)
|
|
90
102
|
completion()
|
|
@@ -92,8 +104,14 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
92
104
|
}
|
|
93
105
|
}
|
|
94
106
|
|
|
95
|
-
private func makeVideoAsset(url: URL) -> AVURLAsset {
|
|
96
|
-
let
|
|
107
|
+
private func makeVideoAsset(url: URL, subtitleURL: URL?) -> AVURLAsset {
|
|
108
|
+
let result = HLSVideoAssetFactory.makeAsset(
|
|
109
|
+
videoURL: url,
|
|
110
|
+
subtitleURL: subtitleURL,
|
|
111
|
+
language: subtitleLanguage ?? "en"
|
|
112
|
+
)
|
|
113
|
+
hlsResourceLoader = result.resourceLoader
|
|
114
|
+
let asset = result.asset
|
|
97
115
|
|
|
98
116
|
if let certUrl = fairplayCertificateUrl, !certUrl.isEmpty {
|
|
99
117
|
let session = AVContentKeySession(keySystem: .fairPlayStreaming)
|
|
@@ -105,95 +123,6 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
105
123
|
return asset
|
|
106
124
|
}
|
|
107
125
|
|
|
108
|
-
private func createPlayerItem(videoAsset: AVURLAsset, subtitleURL: URL) async -> AVPlayerItem {
|
|
109
|
-
let subtitleAsset = AVURLAsset(url: subtitleURL)
|
|
110
|
-
|
|
111
|
-
do {
|
|
112
|
-
let videoDuration = try await videoAsset.load(.duration)
|
|
113
|
-
let composition = AVMutableComposition()
|
|
114
|
-
|
|
115
|
-
try await insertTracks(
|
|
116
|
-
from: videoAsset,
|
|
117
|
-
mediaTypes: [.video, .audio],
|
|
118
|
-
duration: videoDuration,
|
|
119
|
-
into: composition
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
var subtitleAdded = false
|
|
123
|
-
let subtitleTracks = try await subtitleAsset.loadTracks(withMediaType: .text)
|
|
124
|
-
if let subtitleTrack = subtitleTracks.first {
|
|
125
|
-
let compositionTrack = composition.addMutableTrack(
|
|
126
|
-
withMediaType: .text,
|
|
127
|
-
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
128
|
-
)
|
|
129
|
-
let subtitleDuration = try await subtitleAsset.load(.duration)
|
|
130
|
-
let duration = CMTimeCompare(subtitleDuration, videoDuration) < 0 ? subtitleDuration : videoDuration
|
|
131
|
-
try compositionTrack?.insertTimeRange(
|
|
132
|
-
CMTimeRange(start: .zero, duration: duration),
|
|
133
|
-
of: subtitleTrack,
|
|
134
|
-
at: CMTime.zero
|
|
135
|
-
)
|
|
136
|
-
subtitleAdded = true
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
let playerItem = AVPlayerItem(asset: composition)
|
|
140
|
-
|
|
141
|
-
if subtitleAdded {
|
|
142
|
-
selectSubtitle(in: playerItem, language: subtitleLanguage)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return playerItem
|
|
146
|
-
} catch {
|
|
147
|
-
return AVPlayerItem(asset: videoAsset)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private func selectSubtitle(in playerItem: AVPlayerItem, language: String?) {
|
|
152
|
-
guard let group = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else {
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
let selectedOption = selectSubtitleOption(in: group, language: language)
|
|
157
|
-
?? group.defaultOption
|
|
158
|
-
?? group.options.first
|
|
159
|
-
|
|
160
|
-
if let selectedOption {
|
|
161
|
-
playerItem.select(selectedOption, in: group)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private func insertTracks(
|
|
166
|
-
from asset: AVURLAsset,
|
|
167
|
-
mediaTypes: [AVMediaType],
|
|
168
|
-
duration: CMTime,
|
|
169
|
-
into composition: AVMutableComposition
|
|
170
|
-
) async throws {
|
|
171
|
-
for mediaType in mediaTypes {
|
|
172
|
-
let tracks = try await asset.loadTracks(withMediaType: mediaType)
|
|
173
|
-
guard let sourceTrack = tracks.first else { continue }
|
|
174
|
-
|
|
175
|
-
let compositionTrack = composition.addMutableTrack(
|
|
176
|
-
withMediaType: mediaType,
|
|
177
|
-
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
178
|
-
)
|
|
179
|
-
try compositionTrack?.insertTimeRange(
|
|
180
|
-
CMTimeRange(start: .zero, duration: duration),
|
|
181
|
-
of: sourceTrack,
|
|
182
|
-
at: .zero
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private func selectSubtitleOption(in group: AVMediaSelectionGroup, language: String?) -> AVMediaSelectionOption? {
|
|
188
|
-
guard let language, !language.isEmpty else { return nil }
|
|
189
|
-
|
|
190
|
-
return group.options.first { option in
|
|
191
|
-
option.extendedLanguageTag == language
|
|
192
|
-
|| option.locale?.identifier == language
|
|
193
|
-
|| option.locale?.languageCode == language
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
126
|
private func configurePlayer(with item: AVPlayerItem) {
|
|
198
127
|
playerItem = item
|
|
199
128
|
player = AVPlayer(playerItem: playerItem)
|
|
@@ -323,6 +252,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
323
252
|
private func cleanup() {
|
|
324
253
|
castController?.detach(stopRemoteMedia: false)
|
|
325
254
|
castController = nil
|
|
255
|
+
hlsResourceLoader = nil
|
|
326
256
|
if let observer = timeObserver {
|
|
327
257
|
player?.removeObserver(self, forKeyPath: "rate")
|
|
328
258
|
player?.removeTimeObserver(observer)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum HLSVideoAssetFactory {
|
|
5
|
+
static func isHLSStream(_ url: URL) -> Bool {
|
|
6
|
+
let urlString = url.absoluteString.lowercased()
|
|
7
|
+
let path = url.path.lowercased()
|
|
8
|
+
|
|
9
|
+
return path.hasSuffix(".m3u8")
|
|
10
|
+
|| urlString.contains(".m3u8")
|
|
11
|
+
|| urlString.contains("mpegurl")
|
|
12
|
+
|| urlString.contains("hls_playlist")
|
|
13
|
+
|| urlString.contains("hls_manifest")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static func makeAsset(
|
|
17
|
+
videoURL: URL,
|
|
18
|
+
subtitleURL: URL?,
|
|
19
|
+
language: String
|
|
20
|
+
) -> (asset: AVURLAsset, resourceLoader: HLSSubtitleResourceLoader?) {
|
|
21
|
+
guard let subtitleURL, isHLSStream(videoURL) else {
|
|
22
|
+
return (AVURLAsset(url: videoURL), nil)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let resourceLoader = HLSSubtitleResourceLoader(
|
|
26
|
+
videoURL: videoURL,
|
|
27
|
+
subtitleURL: subtitleURL,
|
|
28
|
+
language: language
|
|
29
|
+
)
|
|
30
|
+
guard let assetURL = HLSSubtitleResourceLoader.assetURL(for: videoURL) else {
|
|
31
|
+
return (AVURLAsset(url: videoURL), nil)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let asset = AVURLAsset(url: assetURL)
|
|
35
|
+
asset.resourceLoader.setDelegate(resourceLoader, queue: DispatchQueue.global(qos: .userInitiated))
|
|
36
|
+
return (asset, resourceLoader)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Injects external WebVTT subtitles into HLS master playlists via AVAssetResourceLoaderDelegate.
|
|
41
|
+
/// AVMutableComposition cannot demux HLS tracks, so sidecar subtitles must be wired through the manifest.
|
|
42
|
+
final class HLSSubtitleResourceLoader: NSObject, AVAssetResourceLoaderDelegate {
|
|
43
|
+
static let videoScheme = "capgohls"
|
|
44
|
+
static let subtitlePlaylistScheme = "capgohls-sub"
|
|
45
|
+
private static let subtitleGroupID = "capgosubs"
|
|
46
|
+
|
|
47
|
+
private let originalVideoURL: URL
|
|
48
|
+
private let subtitleURL: URL
|
|
49
|
+
private let language: String
|
|
50
|
+
|
|
51
|
+
init(videoURL: URL, subtitleURL: URL, language: String) {
|
|
52
|
+
self.originalVideoURL = videoURL
|
|
53
|
+
self.subtitleURL = subtitleURL
|
|
54
|
+
self.language = language
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static func assetURL(for videoURL: URL) -> URL? {
|
|
58
|
+
guard var components = URLComponents(url: videoURL, resolvingAgainstBaseURL: false) else {
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
components.scheme = videoScheme
|
|
62
|
+
return components.url
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static func subtitlePlaylistURL(for videoURL: URL) -> URL? {
|
|
66
|
+
guard var components = URLComponents(url: videoURL, resolvingAgainstBaseURL: false) else {
|
|
67
|
+
return nil
|
|
68
|
+
}
|
|
69
|
+
components.scheme = subtitlePlaylistScheme
|
|
70
|
+
return components.url
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func resourceLoader(
|
|
74
|
+
_ resourceLoader: AVAssetResourceLoader,
|
|
75
|
+
shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest
|
|
76
|
+
) -> Bool {
|
|
77
|
+
guard let requestURL = loadingRequest.request.url else {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Task {
|
|
82
|
+
do {
|
|
83
|
+
let data = try await loadData(for: requestURL)
|
|
84
|
+
fulfill(loadingRequest, with: data)
|
|
85
|
+
} catch {
|
|
86
|
+
loadingRequest.finishLoading(with: error)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private func loadData(for requestURL: URL) async throws -> Data {
|
|
94
|
+
if requestURL.scheme == Self.subtitlePlaylistScheme {
|
|
95
|
+
return Data(subtitleMediaPlaylist().utf8)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let fetchURL = originalURL(for: requestURL)
|
|
99
|
+
let (data, _) = try await URLSession.shared.data(from: fetchURL)
|
|
100
|
+
let manifest = String(data: data, encoding: .utf8) ?? ""
|
|
101
|
+
|
|
102
|
+
guard hasStreamInfTags(manifest) else {
|
|
103
|
+
return data
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
guard let subtitlePlaylistURI = Self.subtitlePlaylistURL(for: originalVideoURL)?.absoluteString else {
|
|
107
|
+
return data
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let modified = injectSubtitle(into: manifest, subtitlePlaylistURI: subtitlePlaylistURI)
|
|
111
|
+
return Data(modified.utf8)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private func originalURL(for requestURL: URL) -> URL {
|
|
115
|
+
guard var components = URLComponents(url: requestURL, resolvingAgainstBaseURL: false),
|
|
116
|
+
components.scheme == Self.videoScheme else {
|
|
117
|
+
return requestURL
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
components.scheme = originalVideoURL.scheme ?? "https"
|
|
121
|
+
return components.url ?? originalVideoURL
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private func hasStreamInfTags(_ manifest: String) -> Bool {
|
|
125
|
+
manifest.contains("#EXT-X-STREAM-INF")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private func injectSubtitle(into manifest: String, subtitlePlaylistURI: String) -> String {
|
|
129
|
+
let mediaTag = """
|
|
130
|
+
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="\(Self.subtitleGroupID)",NAME="Subtitles",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="\(language)",URI="\(subtitlePlaylistURI)"
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
var lines = manifest.components(separatedBy: .newlines)
|
|
134
|
+
var result: [String] = []
|
|
135
|
+
var insertedMediaTag = false
|
|
136
|
+
|
|
137
|
+
for line in lines {
|
|
138
|
+
if line.hasPrefix("#EXT-X-STREAM-INF") {
|
|
139
|
+
if !insertedMediaTag {
|
|
140
|
+
result.append(mediaTag)
|
|
141
|
+
insertedMediaTag = true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if line.contains("SUBTITLES=") {
|
|
145
|
+
result.append(line)
|
|
146
|
+
} else {
|
|
147
|
+
result.append("\(line),SUBTITLES=\"\(Self.subtitleGroupID)\"")
|
|
148
|
+
}
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
result.append(line)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if !insertedMediaTag, let extm3uIndex = result.firstIndex(where: { $0.hasPrefix("#EXTM3U") }) {
|
|
156
|
+
result.insert(mediaTag, at: extm3uIndex + 1)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result.joined(separator: "\n")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private func subtitleMediaPlaylist() -> String {
|
|
163
|
+
"""
|
|
164
|
+
#EXTM3U
|
|
165
|
+
#EXT-X-VERSION:3
|
|
166
|
+
#EXT-X-TARGETDURATION:600
|
|
167
|
+
#EXT-X-MEDIA-SEQUENCE:0
|
|
168
|
+
#EXT-X-PLAYLIST-TYPE:VOD
|
|
169
|
+
#EXTINF:600.0,
|
|
170
|
+
\(subtitleURL.absoluteString)
|
|
171
|
+
#EXT-X-ENDLIST
|
|
172
|
+
"""
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private func fulfill(_ loadingRequest: AVAssetResourceLoadingRequest, with data: Data) {
|
|
176
|
+
if let contentRequest = loadingRequest.contentInformationRequest {
|
|
177
|
+
contentRequest.contentType = "application/vnd.apple.mpegurl"
|
|
178
|
+
contentRequest.contentLength = Int64(data.count)
|
|
179
|
+
contentRequest.isByteRangeAccessSupported = false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
loadingRequest.dataRequest?.respond(with: data)
|
|
183
|
+
loadingRequest.finishLoading()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum ProgressiveVideoPlayerItemFactory {
|
|
5
|
+
static func createPlayerItem(
|
|
6
|
+
videoAsset: AVURLAsset,
|
|
7
|
+
subtitleURL: URL,
|
|
8
|
+
subtitleLanguage: String?
|
|
9
|
+
) async -> AVPlayerItem {
|
|
10
|
+
let subtitleAsset = AVURLAsset(url: subtitleURL)
|
|
11
|
+
|
|
12
|
+
do {
|
|
13
|
+
let videoDuration = try await videoAsset.load(.duration)
|
|
14
|
+
let composition = AVMutableComposition()
|
|
15
|
+
|
|
16
|
+
try await insertTracks(
|
|
17
|
+
from: videoAsset,
|
|
18
|
+
mediaTypes: [.video, .audio],
|
|
19
|
+
duration: videoDuration,
|
|
20
|
+
into: composition
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
let hasPlayableTracks = composition.tracks.contains {
|
|
24
|
+
$0.mediaType == .video || $0.mediaType == .audio
|
|
25
|
+
}
|
|
26
|
+
if !hasPlayableTracks {
|
|
27
|
+
return AVPlayerItem(asset: videoAsset)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var subtitleAdded = false
|
|
31
|
+
let subtitleTracks = try await subtitleAsset.loadTracks(withMediaType: .text)
|
|
32
|
+
if let subtitleTrack = subtitleTracks.first {
|
|
33
|
+
let compositionTrack = composition.addMutableTrack(
|
|
34
|
+
withMediaType: .text,
|
|
35
|
+
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
36
|
+
)
|
|
37
|
+
let subtitleDuration = try await subtitleAsset.load(.duration)
|
|
38
|
+
let duration = CMTimeCompare(subtitleDuration, videoDuration) < 0 ? subtitleDuration : videoDuration
|
|
39
|
+
try compositionTrack?.insertTimeRange(
|
|
40
|
+
CMTimeRange(start: .zero, duration: duration),
|
|
41
|
+
of: subtitleTrack,
|
|
42
|
+
at: CMTime.zero
|
|
43
|
+
)
|
|
44
|
+
subtitleAdded = true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let playerItem = AVPlayerItem(asset: composition)
|
|
48
|
+
|
|
49
|
+
if subtitleAdded {
|
|
50
|
+
selectSubtitle(in: playerItem, language: subtitleLanguage)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return playerItem
|
|
54
|
+
} catch {
|
|
55
|
+
return AVPlayerItem(asset: videoAsset)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private static func selectSubtitle(in playerItem: AVPlayerItem, language: String?) {
|
|
60
|
+
guard let group = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let selectedOption = selectSubtitleOption(in: group, language: language)
|
|
65
|
+
?? group.defaultOption
|
|
66
|
+
?? group.options.first
|
|
67
|
+
|
|
68
|
+
if let selectedOption {
|
|
69
|
+
playerItem.select(selectedOption, in: group)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private static func insertTracks(
|
|
74
|
+
from asset: AVURLAsset,
|
|
75
|
+
mediaTypes: [AVMediaType],
|
|
76
|
+
duration: CMTime,
|
|
77
|
+
into composition: AVMutableComposition
|
|
78
|
+
) async throws {
|
|
79
|
+
for mediaType in mediaTypes {
|
|
80
|
+
let tracks = try await asset.loadTracks(withMediaType: mediaType)
|
|
81
|
+
guard let sourceTrack = tracks.first else { continue }
|
|
82
|
+
|
|
83
|
+
let compositionTrack = composition.addMutableTrack(
|
|
84
|
+
withMediaType: mediaType,
|
|
85
|
+
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
86
|
+
)
|
|
87
|
+
try compositionTrack?.insertTimeRange(
|
|
88
|
+
CMTimeRange(start: .zero, duration: duration),
|
|
89
|
+
of: sourceTrack,
|
|
90
|
+
at: .zero
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private static func selectSubtitleOption(in group: AVMediaSelectionGroup, language: String?) -> AVMediaSelectionOption? {
|
|
96
|
+
guard let language, !language.isEmpty else { return nil }
|
|
97
|
+
|
|
98
|
+
return group.options.first { option in
|
|
99
|
+
option.extendedLanguageTag == language
|
|
100
|
+
|| option.locale?.identifier == language
|
|
101
|
+
|| option.locale?.languageCode == language
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
}
|
|
@@ -8,7 +8,7 @@ import AVKit
|
|
|
8
8
|
*/
|
|
9
9
|
@objc(VideoPlayerPlugin)
|
|
10
10
|
public class VideoPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
-
private let pluginVersion: String = "8.1.
|
|
11
|
+
private let pluginVersion: String = "8.1.22"
|
|
12
12
|
public let identifier = "VideoPlayerPlugin"
|
|
13
13
|
public let jsName = "VideoPlayer"
|
|
14
14
|
public let pluginMethods: [CAPPluginMethod] = [
|