@capgo/capacitor-video-player 8.1.15 → 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.
@@ -899,7 +899,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
899
899
  DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory();
900
900
  httpDataSourceFactory.setUserAgent("jeep-exoplayer-plugin");
901
901
  httpDataSourceFactory.setConnectTimeoutMs(DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS);
902
- httpDataSourceFactory.setReadTimeoutMs(1800000);
902
+ httpDataSourceFactory.setReadTimeoutMs(600000);
903
903
  httpDataSourceFactory.setAllowCrossProtocolRedirects(true);
904
904
 
905
905
  // If headers is not null and has data we pass them to the HttpDataSourceFactory
@@ -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.15";
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
- // Create player item with asset
79
- playerItem = AVPlayerItem(asset: asset)
104
+ return asset
105
+ }
106
+
107
+ private func createPlayerItem(videoAsset: AVURLAsset, subtitleURL: URL) async -> AVPlayerItem {
108
+ let subtitleAsset = AVURLAsset(url: subtitleURL)
80
109
 
81
- // Create player
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
- // Setup observers
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.15"
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
- self.configureCallbacks(for: player, playerId: playerId)
121
+ player.setupPlayer {
122
+ self.configureCallbacks(for: player, playerId: playerId)
119
123
 
120
- // Store player
121
- self.videoPlayers[playerId] = player
122
- self.currentPlayerId = playerId
124
+ // Store player
125
+ self.videoPlayers[playerId] = player
126
+ self.currentPlayerId = playerId
123
127
 
124
- player.present(on: viewController) {
125
- call.resolve([
126
- "result": true,
127
- "method": "initPlayer",
128
- "value": playerId
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-video-player",
3
- "version": "8.1.15",
3
+ "version": "8.1.17",
4
4
  "description": "Capacitor plugin to play video in native player",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",