@capgo/capacitor-video-player 8.1.16 → 8.1.17
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.
|
@@ -38,7 +38,7 @@ import java.util.Map;
|
|
|
38
38
|
)
|
|
39
39
|
public class VideoPlayerPlugin extends Plugin {
|
|
40
40
|
|
|
41
|
-
private final String pluginVersion = "8.1.
|
|
41
|
+
private final String pluginVersion = "8.1.17";
|
|
42
42
|
|
|
43
43
|
// Permission alias constants
|
|
44
44
|
private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
|
|
@@ -28,6 +28,8 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
28
28
|
private var fairplayContentKeySpcUrl: String?
|
|
29
29
|
private var contentKeySession: AVContentKeySession?
|
|
30
30
|
private var castController: VideoPlayerCastController?
|
|
31
|
+
private var subtitleUrl: String?
|
|
32
|
+
private var subtitleLanguage: String?
|
|
31
33
|
|
|
32
34
|
init(
|
|
33
35
|
playerId: String,
|
|
@@ -41,6 +43,8 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
41
43
|
title: String? = nil,
|
|
42
44
|
smallTitle: String? = nil,
|
|
43
45
|
artwork: String? = nil,
|
|
46
|
+
subtitleUrl: String? = nil,
|
|
47
|
+
subtitleLanguage: String? = nil,
|
|
44
48
|
fairplayCertificateUrl: String? = nil,
|
|
45
49
|
fairplayContentKeySpcUrl: String? = nil
|
|
46
50
|
) {
|
|
@@ -55,19 +59,41 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
55
59
|
self.title = title
|
|
56
60
|
self.smallTitle = smallTitle
|
|
57
61
|
self.artwork = artwork
|
|
62
|
+
self.subtitleUrl = subtitleUrl
|
|
63
|
+
self.subtitleLanguage = subtitleLanguage
|
|
58
64
|
self.fairplayCertificateUrl = fairplayCertificateUrl
|
|
59
65
|
self.fairplayContentKeySpcUrl = fairplayContentKeySpcUrl
|
|
60
66
|
super.init()
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
func setupPlayer() {
|
|
69
|
+
func setupPlayer(completion: @escaping () -> Void) {
|
|
64
70
|
guard let url = URL(string: videoUrl) else {
|
|
71
|
+
completion()
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let asset = makeVideoAsset(url: url)
|
|
76
|
+
|
|
77
|
+
guard let subtitleUrlString = subtitleUrl,
|
|
78
|
+
!subtitleUrlString.isEmpty,
|
|
79
|
+
let subtitleURL = URL(string: subtitleUrlString) else {
|
|
80
|
+
configurePlayer(with: AVPlayerItem(asset: asset))
|
|
81
|
+
completion()
|
|
65
82
|
return
|
|
66
83
|
}
|
|
67
84
|
|
|
85
|
+
Task {
|
|
86
|
+
let item = await createPlayerItem(videoAsset: asset, subtitleURL: subtitleURL)
|
|
87
|
+
await MainActor.run {
|
|
88
|
+
self.configurePlayer(with: item)
|
|
89
|
+
completion()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private func makeVideoAsset(url: URL) -> AVURLAsset {
|
|
68
95
|
let asset = AVURLAsset(url: url)
|
|
69
96
|
|
|
70
|
-
// Configure FairPlay DRM if certificate URL is provided
|
|
71
97
|
if let certUrl = fairplayCertificateUrl, !certUrl.isEmpty {
|
|
72
98
|
let session = AVContentKeySession(keySystem: .fairPlayStreaming)
|
|
73
99
|
session.setDelegate(self, queue: DispatchQueue.global(qos: .default))
|
|
@@ -75,23 +101,109 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
75
101
|
self.contentKeySession = session
|
|
76
102
|
}
|
|
77
103
|
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
return asset
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private func createPlayerItem(videoAsset: AVURLAsset, subtitleURL: URL) async -> AVPlayerItem {
|
|
108
|
+
let subtitleAsset = AVURLAsset(url: subtitleURL)
|
|
80
109
|
|
|
81
|
-
|
|
110
|
+
do {
|
|
111
|
+
let videoDuration = try await videoAsset.load(.duration)
|
|
112
|
+
let composition = AVMutableComposition()
|
|
113
|
+
|
|
114
|
+
try await insertTracks(
|
|
115
|
+
from: videoAsset,
|
|
116
|
+
mediaTypes: [.video, .audio],
|
|
117
|
+
duration: videoDuration,
|
|
118
|
+
into: composition
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
var subtitleAdded = false
|
|
122
|
+
let subtitleTracks = try await subtitleAsset.loadTracks(withMediaType: .text)
|
|
123
|
+
if let subtitleTrack = subtitleTracks.first {
|
|
124
|
+
let compositionTrack = composition.addMutableTrack(
|
|
125
|
+
withMediaType: .text,
|
|
126
|
+
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
127
|
+
)
|
|
128
|
+
let subtitleDuration = try await subtitleAsset.load(.duration)
|
|
129
|
+
let duration = CMTimeCompare(subtitleDuration, videoDuration) < 0 ? subtitleDuration : videoDuration
|
|
130
|
+
try compositionTrack?.insertTimeRange(
|
|
131
|
+
CMTimeRange(start: .zero, duration: duration),
|
|
132
|
+
of: subtitleTrack,
|
|
133
|
+
at: CMTime.zero
|
|
134
|
+
)
|
|
135
|
+
subtitleAdded = true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let playerItem = AVPlayerItem(asset: composition)
|
|
139
|
+
|
|
140
|
+
if subtitleAdded {
|
|
141
|
+
selectSubtitle(in: playerItem, language: subtitleLanguage)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return playerItem
|
|
145
|
+
} catch {
|
|
146
|
+
return AVPlayerItem(asset: videoAsset)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private func selectSubtitle(in playerItem: AVPlayerItem, language: String?) {
|
|
151
|
+
guard let group = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let selectedOption = selectSubtitleOption(in: group, language: language)
|
|
156
|
+
?? group.defaultOption
|
|
157
|
+
?? group.options.first
|
|
158
|
+
|
|
159
|
+
if let selectedOption {
|
|
160
|
+
playerItem.select(selectedOption, in: group)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private func insertTracks(
|
|
165
|
+
from asset: AVURLAsset,
|
|
166
|
+
mediaTypes: [AVMediaType],
|
|
167
|
+
duration: CMTime,
|
|
168
|
+
into composition: AVMutableComposition
|
|
169
|
+
) async throws {
|
|
170
|
+
for mediaType in mediaTypes {
|
|
171
|
+
let tracks = try await asset.loadTracks(withMediaType: mediaType)
|
|
172
|
+
guard let sourceTrack = tracks.first else { continue }
|
|
173
|
+
|
|
174
|
+
let compositionTrack = composition.addMutableTrack(
|
|
175
|
+
withMediaType: mediaType,
|
|
176
|
+
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
177
|
+
)
|
|
178
|
+
try compositionTrack?.insertTimeRange(
|
|
179
|
+
CMTimeRange(start: .zero, duration: duration),
|
|
180
|
+
of: sourceTrack,
|
|
181
|
+
at: .zero
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private func selectSubtitleOption(in group: AVMediaSelectionGroup, language: String?) -> AVMediaSelectionOption? {
|
|
187
|
+
guard let language, !language.isEmpty else { return nil }
|
|
188
|
+
|
|
189
|
+
return group.options.first { option in
|
|
190
|
+
option.extendedLanguageTag == language
|
|
191
|
+
|| option.locale?.identifier == language
|
|
192
|
+
|| option.locale?.languageCode == language
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private func configurePlayer(with item: AVPlayerItem) {
|
|
197
|
+
playerItem = item
|
|
82
198
|
player = AVPlayer(playerItem: playerItem)
|
|
83
199
|
player?.rate = rate
|
|
84
200
|
|
|
85
|
-
// Create player view controller
|
|
86
201
|
playerViewController = AVPlayerViewController()
|
|
87
202
|
playerViewController?.player = player
|
|
88
203
|
playerViewController?.showsPlaybackControls = showControls
|
|
89
|
-
|
|
90
|
-
// Picture in Picture support
|
|
91
204
|
playerViewController?.allowsPictureInPicturePlayback = pipEnabled
|
|
92
|
-
setupChromecast()
|
|
93
205
|
|
|
94
|
-
|
|
206
|
+
setupChromecast()
|
|
95
207
|
setupObservers()
|
|
96
208
|
}
|
|
97
209
|
|
|
@@ -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.17"
|
|
12
12
|
public let identifier = "VideoPlayerPlugin"
|
|
13
13
|
public let jsName = "VideoPlayer"
|
|
14
14
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -79,6 +79,8 @@ extension VideoPlayerPlugin {
|
|
|
79
79
|
let title = call.getString("title")
|
|
80
80
|
let smallTitle = call.getString("smallTitle")
|
|
81
81
|
let artwork = call.getString("artwork")
|
|
82
|
+
let subtitle = call.getString("subtitle")
|
|
83
|
+
let language = call.getString("language")
|
|
82
84
|
|
|
83
85
|
// Extract FairPlay DRM options if provided
|
|
84
86
|
let drm = call.getObject("drm")
|
|
@@ -99,6 +101,8 @@ extension VideoPlayerPlugin {
|
|
|
99
101
|
title: title,
|
|
100
102
|
smallTitle: smallTitle,
|
|
101
103
|
artwork: artwork,
|
|
104
|
+
subtitleUrl: subtitle,
|
|
105
|
+
subtitleLanguage: language,
|
|
102
106
|
fairplayCertificateUrl: fairplayCertificateUrl,
|
|
103
107
|
fairplayContentKeySpcUrl: fairplayContentKeySpcUrl
|
|
104
108
|
)
|
|
@@ -114,19 +118,20 @@ extension VideoPlayerPlugin {
|
|
|
114
118
|
return
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
player.setupPlayer
|
|
118
|
-
|
|
121
|
+
player.setupPlayer {
|
|
122
|
+
self.configureCallbacks(for: player, playerId: playerId)
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
// Store player
|
|
125
|
+
self.videoPlayers[playerId] = player
|
|
126
|
+
self.currentPlayerId = playerId
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
player.present(on: viewController) {
|
|
129
|
+
call.resolve([
|
|
130
|
+
"result": true,
|
|
131
|
+
"method": "initPlayer",
|
|
132
|
+
"value": playerId
|
|
133
|
+
])
|
|
134
|
+
}
|
|
130
135
|
}
|
|
131
136
|
}
|
|
132
137
|
}
|