@capgo/capacitor-jw-player 0.0.1 → 0.0.12
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/CapgoCapacitorJwPlayer.podspec +3 -1
- package/Package.swift +12 -2
- package/README.md +19 -4
- package/android/build.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/ee/forgr/capacitor_jw_player/CastOptionsProvider.java +30 -0
- package/android/src/main/java/ee/forgr/capacitor_jw_player/JwPlayerPlugin.java +63 -59
- package/android/src/main/java/ee/forgr/capacitor_jw_player/PlayerActivity.java +72 -73
- package/dist/docs.json +6 -2
- package/dist/esm/definitions.d.ts +2 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.js.map +1 -1
- package/ios/Sources/JwPlayerPlugin/JwPlayer.swift +4 -4
- package/ios/Sources/JwPlayerPlugin/JwPlayerPlugin.swift +218 -120
- package/ios/Sources/JwPlayerPlugin/LinkedList.swift +28 -29
- package/ios/Sources/JwPlayerPlugin/VideosToPlay.swift +4 -5
- package/package.json +5 -5
|
@@ -2,7 +2,7 @@ import Foundation
|
|
|
2
2
|
import Capacitor
|
|
3
3
|
import JWPlayerKit
|
|
4
4
|
import AVKit // Import AVKit for AVAudioSession
|
|
5
|
-
|
|
5
|
+
import GoogleCast
|
|
6
6
|
/**
|
|
7
7
|
* Please read the Capacitor iOS Plugin Development Guide
|
|
8
8
|
* here: https://capacitorjs.com/docs/plugins/ios
|
|
@@ -15,9 +15,8 @@ protocol CallbackHandler {
|
|
|
15
15
|
func rePresentPlayerRequested() // Request plugin to re-present
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
@objc(JwPlayerPlugin)
|
|
20
|
-
public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
19
|
+
public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin, GCKLoggerDelegate {
|
|
21
20
|
public let identifier = "JwPlayerPlugin"
|
|
22
21
|
public let jsName = "JwPlayer"
|
|
23
22
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -42,12 +41,13 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
42
41
|
CAPPluginMethod(name: "currentPlaylist", returnType: CAPPluginReturnPromise),
|
|
43
42
|
CAPPluginMethod(name: "resume", returnType: CAPPluginReturnPromise)
|
|
44
43
|
]
|
|
45
|
-
private var viewController: CustomPlayerViewController?
|
|
46
|
-
|
|
44
|
+
private var viewController: CustomPlayerViewController?
|
|
47
45
|
|
|
48
46
|
override public func load() {
|
|
49
47
|
// Configure the audio session when the plugin loads
|
|
50
48
|
setupAudioSession()
|
|
49
|
+
print("[JWPlayer] Plugin loaded. Note: Chromecast is initialized in AppDelegate.")
|
|
50
|
+
initializeChromecast()
|
|
51
51
|
super.load()
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -62,7 +62,33 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
62
62
|
print("[JWPlayer] Error setting up AVAudioSession: \(error.localizedDescription)")
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
|
|
66
|
+
private func initializeChromecast() {
|
|
67
|
+
print("[ChromecastDebug] Starting Chromecast initialization")
|
|
68
|
+
let kReceiverAppID = "CC1AD845" // Custom App ID for Chromecast
|
|
69
|
+
let discoveryCriteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
|
|
70
|
+
let options = GCKCastOptions(discoveryCriteria: discoveryCriteria)
|
|
71
|
+
options.physicalVolumeButtonsWillControlDeviceVolume = true
|
|
72
|
+
options.disableDiscoveryAutostart = false
|
|
73
|
+
print("[ChromecastDebug] Setting custom options for Chromecast")
|
|
74
|
+
do {
|
|
75
|
+
GCKCastContext.setSharedInstanceWith(options)
|
|
76
|
+
print("[ChromecastDebug] Chromecast context initialized with App ID: \(kReceiverAppID)")
|
|
77
|
+
} catch {
|
|
78
|
+
print("[ChromecastDebug] Error initializing Chromecast context: \(error.localizedDescription)")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let filter = GCKLoggerFilter.init()
|
|
82
|
+
filter.minimumLevel = .verbose
|
|
83
|
+
GCKLogger.sharedInstance().filter = filter
|
|
84
|
+
GCKLogger.sharedInstance().delegate = self
|
|
85
|
+
print("[ChromecastDebug] Logger initialized")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public func logMessage(_ message: String, at level: GCKLoggerLevel, fromFunction function: String, location: String) {
|
|
89
|
+
print(function + " - " + message)
|
|
90
|
+
}
|
|
91
|
+
|
|
66
92
|
@objc func initialize(_ call: CAPPluginCall) {
|
|
67
93
|
print("[JWPlayer] initialize called")
|
|
68
94
|
guard let licenseKey = call.getString("licenseKey") else {
|
|
@@ -70,14 +96,14 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
70
96
|
call.reject("licenseKey is required for initialize")
|
|
71
97
|
return
|
|
72
98
|
}
|
|
73
|
-
|
|
99
|
+
|
|
74
100
|
print("[JWPlayer] Setting license key")
|
|
75
101
|
JWPlayerKitLicense.setLicenseKey(licenseKey)
|
|
76
102
|
print("[JWPlayer] License key set successfully")
|
|
77
|
-
|
|
103
|
+
|
|
78
104
|
call.resolve()
|
|
79
105
|
}
|
|
80
|
-
|
|
106
|
+
|
|
81
107
|
@objc func play(_ call: CAPPluginCall) {
|
|
82
108
|
print("[JWPlayer] play called with options: \(call.options)")
|
|
83
109
|
guard let mediaUrlStr = call.getString("mediaUrl") else {
|
|
@@ -85,29 +111,31 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
85
111
|
call.reject("mediaUrl is required for initialize")
|
|
86
112
|
return
|
|
87
113
|
}
|
|
88
|
-
|
|
114
|
+
|
|
89
115
|
guard let mediaUrl = URL(string: mediaUrlStr) else {
|
|
90
116
|
print("[JWPlayer] Error: mediaUrl is invalid URL: \(mediaUrlStr)")
|
|
91
117
|
call.reject("mediaUrl is invalid URL")
|
|
92
118
|
return
|
|
93
119
|
}
|
|
94
|
-
|
|
120
|
+
|
|
95
121
|
guard let mediaType = call.getString("mediaType") else {
|
|
96
122
|
print("[JWPlayer] Error: mediaType is missing")
|
|
97
123
|
call.reject("mediaType is required for initialize")
|
|
98
124
|
return
|
|
99
125
|
}
|
|
100
|
-
|
|
126
|
+
|
|
127
|
+
let autostart = call.getBool("autostart") ?? false
|
|
128
|
+
|
|
101
129
|
if self.viewController != nil {
|
|
102
130
|
print("[JWPlayer] Error: player is already active")
|
|
103
131
|
call.reject("the player is already active")
|
|
104
132
|
return
|
|
105
133
|
}
|
|
106
|
-
|
|
134
|
+
|
|
107
135
|
print("[JWPlayer] Media type: \(mediaType), URL: \(mediaUrl)")
|
|
108
|
-
|
|
136
|
+
|
|
109
137
|
// Create configuration directly
|
|
110
|
-
var config: JWPlayerConfiguration?
|
|
138
|
+
var config: JWPlayerConfiguration?
|
|
111
139
|
do {
|
|
112
140
|
switch mediaType {
|
|
113
141
|
case "video":
|
|
@@ -117,11 +145,13 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
117
145
|
.build()
|
|
118
146
|
config = try JWPlayerConfigurationBuilder()
|
|
119
147
|
.playlist(items: [item])
|
|
148
|
+
.autostart(autostart)
|
|
120
149
|
.build()
|
|
121
150
|
case "playlist":
|
|
122
151
|
print("[JWPlayer] Creating playlist configuration")
|
|
123
152
|
config = try JWPlayerConfigurationBuilder()
|
|
124
153
|
.playlist(url: mediaUrl)
|
|
154
|
+
.autostart(autostart)
|
|
125
155
|
.build()
|
|
126
156
|
default:
|
|
127
157
|
print("[JWPlayer] Error: Invalid mediaType: \(mediaType)")
|
|
@@ -133,13 +163,13 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
133
163
|
call.reject("Error creating player configuration: \(error.localizedDescription)")
|
|
134
164
|
return
|
|
135
165
|
}
|
|
136
|
-
|
|
166
|
+
|
|
137
167
|
guard let finalConfig = config else {
|
|
138
168
|
print("[JWPlayer] Error: Configuration is nil after creation")
|
|
139
169
|
call.reject("Failed to create player configuration")
|
|
140
170
|
return
|
|
141
171
|
}
|
|
142
|
-
|
|
172
|
+
|
|
143
173
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
144
174
|
print("[JWPlayer] Creating player view controller with config")
|
|
145
175
|
// Ensure any previous instance is fully dismissed first
|
|
@@ -147,127 +177,128 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
147
177
|
print("[JWPlayer] Warning: Play called while a player VC might still exist. Forcing cleanup.")
|
|
148
178
|
self.viewController = nil
|
|
149
179
|
}
|
|
150
|
-
|
|
180
|
+
|
|
151
181
|
let viewController = CustomPlayerViewController(config: finalConfig, callbackHandler: self)
|
|
152
|
-
viewController.modalPresentationStyle = .
|
|
182
|
+
viewController.modalPresentationStyle = .overCurrentContext
|
|
183
|
+
viewController.modalTransitionStyle = .crossDissolve
|
|
153
184
|
self.viewController = viewController // Keep strong reference
|
|
154
185
|
print("[JWPlayer] Resolving play call")
|
|
155
186
|
call.resolve()
|
|
156
|
-
print("[JWPlayer] Presenting view controller")
|
|
187
|
+
print("[JWPlayer] Presenting view controller over current context")
|
|
157
188
|
self.bridge?.viewController?.present(viewController, animated: true)
|
|
158
189
|
print("[JWPlayer] View controller presented")
|
|
159
190
|
})
|
|
160
191
|
}
|
|
161
|
-
|
|
192
|
+
|
|
162
193
|
@objc func pause(_ call: CAPPluginCall) {
|
|
163
194
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
164
195
|
guard let viewController = self.viewController else {
|
|
165
196
|
call.reject("No active player")
|
|
166
197
|
return
|
|
167
198
|
}
|
|
168
|
-
|
|
199
|
+
|
|
169
200
|
viewController.player.pause()
|
|
170
201
|
call.resolve()
|
|
171
202
|
})
|
|
172
203
|
}
|
|
173
|
-
|
|
204
|
+
|
|
174
205
|
@objc func stop(_ call: CAPPluginCall) {
|
|
175
206
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
176
207
|
guard let viewController = self.viewController else {
|
|
177
208
|
call.reject("No active player")
|
|
178
209
|
return
|
|
179
210
|
}
|
|
180
|
-
|
|
211
|
+
|
|
181
212
|
viewController.player.stop()
|
|
182
213
|
call.resolve()
|
|
183
214
|
})
|
|
184
215
|
}
|
|
185
|
-
|
|
216
|
+
|
|
186
217
|
@objc func seekTo(_ call: CAPPluginCall) {
|
|
187
218
|
guard let time = call.getDouble("time") else {
|
|
188
219
|
call.reject("time parameter is required")
|
|
189
220
|
return
|
|
190
221
|
}
|
|
191
|
-
|
|
222
|
+
|
|
192
223
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
193
224
|
guard let viewController = self.viewController else {
|
|
194
225
|
call.reject("No active player")
|
|
195
226
|
return
|
|
196
227
|
}
|
|
197
|
-
|
|
228
|
+
|
|
198
229
|
viewController.player.seek(to: TimeInterval(time))
|
|
199
230
|
call.resolve()
|
|
200
231
|
})
|
|
201
232
|
}
|
|
202
|
-
|
|
233
|
+
|
|
203
234
|
@objc func setVolume(_ call: CAPPluginCall) {
|
|
204
235
|
guard let volume = call.getDouble("volume") else {
|
|
205
236
|
call.reject("volume parameter is required")
|
|
206
237
|
return
|
|
207
238
|
}
|
|
208
|
-
|
|
239
|
+
|
|
209
240
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
210
241
|
guard let viewController = self.viewController else {
|
|
211
242
|
call.reject("No active player")
|
|
212
243
|
return
|
|
213
244
|
}
|
|
214
|
-
|
|
245
|
+
|
|
215
246
|
viewController.player.volume = volume
|
|
216
247
|
call.resolve()
|
|
217
248
|
})
|
|
218
249
|
}
|
|
219
|
-
|
|
250
|
+
|
|
220
251
|
@objc func getPosition(_ call: CAPPluginCall) {
|
|
221
252
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
222
253
|
guard let viewController = self.viewController else {
|
|
223
254
|
call.reject("No active player")
|
|
224
255
|
return
|
|
225
256
|
}
|
|
226
|
-
|
|
257
|
+
|
|
227
258
|
call.resolve(["position": viewController.player.time.position])
|
|
228
259
|
})
|
|
229
260
|
}
|
|
230
|
-
|
|
261
|
+
|
|
231
262
|
@objc func getState(_ call: CAPPluginCall) {
|
|
232
263
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
233
264
|
guard let viewController = self.viewController else {
|
|
234
265
|
call.reject("No active player")
|
|
235
266
|
return
|
|
236
267
|
}
|
|
237
|
-
|
|
268
|
+
|
|
238
269
|
call.resolve(["state": viewController.player.getState().rawValue])
|
|
239
270
|
})
|
|
240
271
|
}
|
|
241
|
-
|
|
272
|
+
|
|
242
273
|
@objc func setSpeed(_ call: CAPPluginCall) {
|
|
243
274
|
guard let speed = call.getDouble("speed") else {
|
|
244
275
|
call.reject("speed parameter is required")
|
|
245
276
|
return
|
|
246
277
|
}
|
|
247
|
-
|
|
278
|
+
|
|
248
279
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
249
280
|
guard let viewController = self.viewController else {
|
|
250
281
|
call.reject("No active player")
|
|
251
282
|
return
|
|
252
283
|
}
|
|
253
|
-
|
|
284
|
+
|
|
254
285
|
viewController.player.playbackRate = speed
|
|
255
286
|
call.resolve()
|
|
256
287
|
})
|
|
257
288
|
}
|
|
258
|
-
|
|
289
|
+
|
|
259
290
|
@objc func setPlaylistIndex(_ call: CAPPluginCall) {
|
|
260
291
|
guard let index = call.getInt("index") else {
|
|
261
292
|
call.reject("index parameter is required")
|
|
262
293
|
return
|
|
263
294
|
}
|
|
264
|
-
|
|
295
|
+
|
|
265
296
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
266
297
|
guard let viewController = self.viewController else {
|
|
267
298
|
call.reject("No active player")
|
|
268
299
|
return
|
|
269
300
|
}
|
|
270
|
-
|
|
301
|
+
|
|
271
302
|
// Use the loadPlayerItemAt method which should be available
|
|
272
303
|
do {
|
|
273
304
|
viewController.player.loadPlayerItemAt(index: index)
|
|
@@ -277,43 +308,43 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
277
308
|
}
|
|
278
309
|
})
|
|
279
310
|
}
|
|
280
|
-
|
|
311
|
+
|
|
281
312
|
@objc func loadPlaylist(_ call: CAPPluginCall) {
|
|
282
313
|
guard let playlistUrlStr = call.getString("playlistUrl") else {
|
|
283
314
|
call.reject("playlistUrl parameter is required")
|
|
284
315
|
return
|
|
285
316
|
}
|
|
286
|
-
|
|
317
|
+
|
|
287
318
|
guard let playlistUrl = URL(string: playlistUrlStr) else {
|
|
288
319
|
call.reject("playlistUrl is invalid URL")
|
|
289
320
|
return
|
|
290
321
|
}
|
|
291
|
-
|
|
322
|
+
|
|
292
323
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
293
324
|
guard let viewController = self.viewController else {
|
|
294
325
|
call.reject("No active player")
|
|
295
326
|
return
|
|
296
327
|
}
|
|
297
|
-
|
|
328
|
+
|
|
298
329
|
viewController.player.loadPlaylist(url: playlistUrl)
|
|
299
330
|
call.resolve()
|
|
300
331
|
})
|
|
301
332
|
}
|
|
302
|
-
|
|
333
|
+
|
|
303
334
|
@objc func loadPlaylistWithItems(_ call: CAPPluginCall) {
|
|
304
335
|
guard let playlist = call.getArray("playlist", [String: Any].self) else {
|
|
305
336
|
call.reject("playlist parameter is required")
|
|
306
337
|
return
|
|
307
338
|
}
|
|
308
|
-
|
|
339
|
+
|
|
309
340
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
310
341
|
guard let viewController = self.viewController else {
|
|
311
342
|
call.reject("No active player")
|
|
312
343
|
return
|
|
313
344
|
}
|
|
314
|
-
|
|
345
|
+
|
|
315
346
|
var items: [JWPlayerItem] = []
|
|
316
|
-
|
|
347
|
+
|
|
317
348
|
for item in playlist {
|
|
318
349
|
if let fileStr = item["file"] as? String, let url = URL(string: fileStr) {
|
|
319
350
|
do {
|
|
@@ -327,7 +358,7 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
327
358
|
}
|
|
328
359
|
}
|
|
329
360
|
}
|
|
330
|
-
|
|
361
|
+
|
|
331
362
|
if !items.isEmpty {
|
|
332
363
|
viewController.player.loadPlaylist(items: items)
|
|
333
364
|
call.resolve()
|
|
@@ -336,16 +367,16 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
336
367
|
}
|
|
337
368
|
})
|
|
338
369
|
}
|
|
339
|
-
|
|
370
|
+
|
|
340
371
|
@objc func getAudioTracks(_ call: CAPPluginCall) {
|
|
341
372
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
342
373
|
guard let viewController = self.viewController else {
|
|
343
374
|
call.reject("No active player")
|
|
344
375
|
return
|
|
345
376
|
}
|
|
346
|
-
|
|
377
|
+
|
|
347
378
|
let audioTracks = viewController.player.audioTracks
|
|
348
|
-
|
|
379
|
+
|
|
349
380
|
if audioTracks != nil && audioTracks.count > 0 {
|
|
350
381
|
var results: [[String: Any]] = []
|
|
351
382
|
for track in audioTracks {
|
|
@@ -362,46 +393,46 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
362
393
|
}
|
|
363
394
|
})
|
|
364
395
|
}
|
|
365
|
-
|
|
396
|
+
|
|
366
397
|
@objc func getCurrentAudioTrack(_ call: CAPPluginCall) {
|
|
367
398
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
368
399
|
guard let viewController = self.viewController else {
|
|
369
400
|
call.reject("No active player")
|
|
370
401
|
return
|
|
371
402
|
}
|
|
372
|
-
|
|
403
|
+
|
|
373
404
|
call.resolve(["index": viewController.player.currentAudioTrack])
|
|
374
405
|
})
|
|
375
406
|
}
|
|
376
|
-
|
|
407
|
+
|
|
377
408
|
@objc func setCurrentAudioTrack(_ call: CAPPluginCall) {
|
|
378
409
|
guard let index = call.getInt("index") else {
|
|
379
410
|
call.reject("index parameter is required")
|
|
380
411
|
return
|
|
381
412
|
}
|
|
382
|
-
|
|
413
|
+
|
|
383
414
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
384
415
|
guard let viewController = self.viewController else {
|
|
385
416
|
call.reject("No active player")
|
|
386
417
|
return
|
|
387
418
|
}
|
|
388
|
-
|
|
419
|
+
|
|
389
420
|
viewController.player.currentAudioTrack = index
|
|
390
421
|
call.resolve()
|
|
391
422
|
})
|
|
392
423
|
}
|
|
393
|
-
|
|
424
|
+
|
|
394
425
|
@objc func getCaptions(_ call: CAPPluginCall) {
|
|
395
426
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
396
427
|
guard let viewController = self.viewController else {
|
|
397
428
|
call.reject("No active player")
|
|
398
429
|
return
|
|
399
430
|
}
|
|
400
|
-
|
|
431
|
+
|
|
401
432
|
// JWPlayer iOS SDK doesn't have a direct way to get all caption tracks
|
|
402
433
|
// But we can use the advanced settings to access them
|
|
403
434
|
let captionsTrackId = viewController.player.currentCaptionsTrack
|
|
404
|
-
|
|
435
|
+
|
|
405
436
|
// Create a basic response with just the current caption track
|
|
406
437
|
let captionsArray: [[String: Any]] = [
|
|
407
438
|
// Caption track 0 is usually "Off"
|
|
@@ -415,34 +446,34 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
415
446
|
}
|
|
416
447
|
return false
|
|
417
448
|
}
|
|
418
|
-
|
|
449
|
+
|
|
419
450
|
call.resolve(["captions": captionsArray])
|
|
420
451
|
})
|
|
421
452
|
}
|
|
422
|
-
|
|
453
|
+
|
|
423
454
|
@objc func getCurrentCaptions(_ call: CAPPluginCall) {
|
|
424
455
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
425
456
|
guard let viewController = self.viewController else {
|
|
426
457
|
call.reject("No active player")
|
|
427
458
|
return
|
|
428
459
|
}
|
|
429
|
-
|
|
460
|
+
|
|
430
461
|
call.resolve(["index": viewController.player.currentCaptionsTrack])
|
|
431
462
|
})
|
|
432
463
|
}
|
|
433
|
-
|
|
464
|
+
|
|
434
465
|
@objc func setCurrentCaptions(_ call: CAPPluginCall) {
|
|
435
466
|
guard let index = call.getInt("index") else {
|
|
436
467
|
call.reject("index parameter is required")
|
|
437
468
|
return
|
|
438
469
|
}
|
|
439
|
-
|
|
470
|
+
|
|
440
471
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
441
472
|
guard let viewController = self.viewController else {
|
|
442
473
|
call.reject("No active player")
|
|
443
474
|
return
|
|
444
475
|
}
|
|
445
|
-
|
|
476
|
+
|
|
446
477
|
do {
|
|
447
478
|
try viewController.player.setCaptionTrack(index: index)
|
|
448
479
|
call.resolve()
|
|
@@ -451,21 +482,21 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
451
482
|
}
|
|
452
483
|
})
|
|
453
484
|
}
|
|
454
|
-
|
|
485
|
+
|
|
455
486
|
@objc func currentPlaylist(_ call: CAPPluginCall) {
|
|
456
487
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
457
488
|
guard let viewController = self.viewController else {
|
|
458
489
|
call.reject("No active player")
|
|
459
490
|
return
|
|
460
491
|
}
|
|
461
|
-
|
|
492
|
+
|
|
462
493
|
// Just return an empty array since we can't safely access playlist information
|
|
463
494
|
call.resolve([
|
|
464
495
|
"playlist": []
|
|
465
496
|
])
|
|
466
497
|
})
|
|
467
498
|
}
|
|
468
|
-
|
|
499
|
+
|
|
469
500
|
@objc func resume(_ call: CAPPluginCall) {
|
|
470
501
|
print("[JWPlayer] resume called")
|
|
471
502
|
DispatchQueue.main.async(execute: DispatchWorkItem {
|
|
@@ -474,7 +505,7 @@ public class JwPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
474
505
|
call.reject("No active player to resume")
|
|
475
506
|
return
|
|
476
507
|
}
|
|
477
|
-
|
|
508
|
+
|
|
478
509
|
print("[JWPlayer] Resuming playback")
|
|
479
510
|
viewController.player.play()
|
|
480
511
|
call.resolve()
|
|
@@ -489,7 +520,7 @@ extension JwPlayerPlugin: CallbackHandler {
|
|
|
489
520
|
print("[JWPlayer] Event: \(eventName), Data: \(String(describing: data))")
|
|
490
521
|
notifyListeners(eventName, data: data)
|
|
491
522
|
}
|
|
492
|
-
|
|
523
|
+
|
|
493
524
|
func onPlayerDismissed(isPiPDismissal: Bool) {
|
|
494
525
|
print("[JWPlayer] Player dismissed callback. Was PiP dismissal: \(isPiPDismissal)")
|
|
495
526
|
// Only nil out the reference if it wasn't a dismissal for starting PiP
|
|
@@ -503,7 +534,7 @@ extension JwPlayerPlugin: CallbackHandler {
|
|
|
503
534
|
self.notifyListeners("pipStarted", data: nil)
|
|
504
535
|
}
|
|
505
536
|
}
|
|
506
|
-
|
|
537
|
+
|
|
507
538
|
func rePresentPlayerRequested() {
|
|
508
539
|
print("[JWPlayer] Re-present player requested")
|
|
509
540
|
guard let vc = self.viewController else {
|
|
@@ -514,13 +545,13 @@ extension JwPlayerPlugin: CallbackHandler {
|
|
|
514
545
|
print("[JWPlayer] Error: Cannot re-present, bridge view controller is nil.")
|
|
515
546
|
return
|
|
516
547
|
}
|
|
517
|
-
|
|
548
|
+
|
|
518
549
|
// Ensure it's not already being presented or presenting something else
|
|
519
550
|
if vc.isBeingPresented || vc.presentingViewController != nil || bridgeVC.presentedViewController != nil {
|
|
520
551
|
print("[JWPlayer] Warning: Cannot re-present, presentation context busy.")
|
|
521
552
|
return
|
|
522
553
|
}
|
|
523
|
-
|
|
554
|
+
|
|
524
555
|
DispatchQueue.main.async {
|
|
525
556
|
print("[JWPlayer] Re-presenting player view controller")
|
|
526
557
|
bridgeVC.present(vc, animated: true)
|
|
@@ -537,102 +568,100 @@ class CustomPlayerViewController: JWPlayerViewController, JWPlayerViewController
|
|
|
537
568
|
"newSize": ["width": newSize.width, "height": newSize.height]
|
|
538
569
|
]
|
|
539
570
|
callbackHandler?.notifyEventListener("playerSizeChange", data: sizeData)
|
|
540
|
-
|
|
571
|
+
|
|
541
572
|
}
|
|
542
|
-
|
|
573
|
+
|
|
543
574
|
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, screenTappedAt position: CGPoint) {
|
|
544
575
|
print("[JWPlayer] Screen tapped at: \(position)")
|
|
545
576
|
let positionData: [String: Any] = ["x": position.x, "y": position.y]
|
|
546
577
|
callbackHandler?.notifyEventListener("screenTapped", data: positionData)
|
|
547
578
|
}
|
|
548
|
-
|
|
549
|
-
|
|
579
|
+
|
|
550
580
|
private var callbackHandler: CallbackHandler?
|
|
551
581
|
private var playerConfig: JWPlayerConfiguration?
|
|
552
582
|
private var closeButton: UIButton! // Custom close button
|
|
553
|
-
|
|
583
|
+
|
|
554
584
|
// Standard init is unavailable
|
|
555
585
|
@available(*, unavailable)
|
|
556
586
|
init() {
|
|
557
587
|
fatalError("init() is unavailable, use init(config:callbackHandler:)")
|
|
558
588
|
}
|
|
559
|
-
|
|
589
|
+
|
|
560
590
|
// Designated initializer
|
|
561
591
|
init(config: JWPlayerConfiguration, callbackHandler: CallbackHandler? = nil) {
|
|
562
592
|
print("[JWPlayer] CustomPlayerViewController init with config")
|
|
563
593
|
self.playerConfig = config
|
|
564
594
|
self.callbackHandler = callbackHandler
|
|
565
595
|
super.init(nibName: nil, bundle: nil)
|
|
566
|
-
|
|
596
|
+
|
|
567
597
|
// Set UI delegate
|
|
568
598
|
self.uiDelegate = self
|
|
569
599
|
print("[JWPlayer] Set self as JWPlayerViewControllerUIDelegate")
|
|
570
|
-
|
|
600
|
+
|
|
571
601
|
// Enable PiP
|
|
572
602
|
self.allowsPictureInPicturePlayback = true
|
|
573
603
|
print("[JWPlayer] allowsPictureInPicturePlayback set to true")
|
|
574
|
-
|
|
604
|
+
|
|
575
605
|
setupFullscreenConfig()
|
|
576
606
|
}
|
|
577
|
-
|
|
607
|
+
|
|
578
608
|
required init?(coder: NSCoder) {
|
|
579
609
|
print("[JWPlayer] CustomPlayerViewController init from coder - Not recommended")
|
|
580
610
|
self.playerConfig = nil
|
|
581
611
|
super.init(coder: coder)
|
|
582
|
-
|
|
612
|
+
|
|
583
613
|
// Set UI delegate
|
|
584
614
|
self.uiDelegate = self
|
|
585
615
|
print("[JWPlayer] Set self as JWPlayerViewControllerUIDelegate (coder init)")
|
|
586
|
-
|
|
616
|
+
|
|
587
617
|
// Enable PiP even if initialized from coder
|
|
588
618
|
self.allowsPictureInPicturePlayback = true
|
|
589
619
|
print("[JWPlayer] allowsPictureInPicturePlayback set to true (coder init)")
|
|
590
|
-
|
|
620
|
+
|
|
591
621
|
setupFullscreenConfig()
|
|
592
622
|
}
|
|
593
|
-
|
|
623
|
+
|
|
594
624
|
private func setupFullscreenConfig() {
|
|
595
625
|
print("[JWPlayer] Setting up fullscreen config")
|
|
596
626
|
// Ensure player is always fullscreen
|
|
597
627
|
modalPresentationStyle = .fullScreen
|
|
598
|
-
|
|
628
|
+
|
|
599
629
|
// Configure the player view to fill the screen
|
|
600
630
|
view.frame = UIScreen.main.bounds
|
|
601
631
|
self.forceFullScreenOnLandscape = false
|
|
602
632
|
self.forceLandscapeOnFullScreen = false
|
|
603
633
|
print("[JWPlayer] Fullscreen config set")
|
|
604
634
|
}
|
|
605
|
-
|
|
635
|
+
|
|
606
636
|
override func viewDidLoad() {
|
|
607
637
|
print("[JWPlayer] viewDidLoad")
|
|
608
638
|
super.viewDidLoad()
|
|
609
|
-
|
|
639
|
+
|
|
610
640
|
// Apply the configuration passed during initialization
|
|
611
641
|
if let config = self.playerConfig {
|
|
612
642
|
print("[JWPlayer] Configuring player from init config")
|
|
613
643
|
player.configurePlayer(with: config)
|
|
614
644
|
print("[JWPlayer] Player configured successfully from init config")
|
|
615
|
-
|
|
616
645
|
} else {
|
|
617
646
|
print("[JWPlayer] Error: Player configuration is missing in viewDidLoad")
|
|
618
647
|
self.callbackHandler?.notifyEventListener("error", data: ["message": "Player configuration missing"])
|
|
619
648
|
}
|
|
620
|
-
|
|
649
|
+
|
|
621
650
|
// Add the custom close button
|
|
622
651
|
setupCloseButton()
|
|
623
652
|
}
|
|
624
|
-
|
|
653
|
+
|
|
625
654
|
override func viewDidAppear(_ animated: Bool) {
|
|
626
655
|
print("[JWPlayer] viewDidAppear")
|
|
627
656
|
super.viewDidAppear(animated)
|
|
628
|
-
|
|
657
|
+
|
|
629
658
|
// Start playback automatically
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
//
|
|
659
|
+
// print("[JWPlayer] Starting playback")
|
|
660
|
+
// player.play()
|
|
661
|
+
// print("[JWPlayer] Playback started")
|
|
633
662
|
print("[JWPlayer] Button exists \(view.viewWithTag(2136) !== nil)")
|
|
634
663
|
}
|
|
635
|
-
|
|
664
|
+
|
|
636
665
|
// Setup and add the custom close button
|
|
637
666
|
private func setupCloseButton() {
|
|
638
667
|
closeButton = UIButton(type: .custom)
|
|
@@ -643,28 +672,28 @@ class CustomPlayerViewController: JWPlayerViewController, JWPlayerViewController
|
|
|
643
672
|
closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .bold)
|
|
644
673
|
closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .allTouchEvents)
|
|
645
674
|
closeButton.tag = 2136
|
|
646
|
-
|
|
675
|
+
|
|
647
676
|
// Position the button in the top-left corner
|
|
648
677
|
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
649
678
|
view.addSubview(closeButton)
|
|
650
|
-
|
|
679
|
+
|
|
651
680
|
NSLayoutConstraint.activate([
|
|
652
681
|
closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15),
|
|
653
682
|
closeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 15),
|
|
654
683
|
closeButton.widthAnchor.constraint(equalToConstant: 30),
|
|
655
684
|
closeButton.heightAnchor.constraint(equalToConstant: 30)
|
|
656
685
|
])
|
|
657
|
-
|
|
686
|
+
|
|
658
687
|
// Make sure it's always on top
|
|
659
688
|
closeButton.layer.zPosition = 1000
|
|
660
689
|
// Initially visible, will be updated by delegate method
|
|
661
690
|
closeButton.alpha = 1.0
|
|
662
691
|
print("[JWPlayer] Custom close button added")
|
|
663
|
-
|
|
692
|
+
|
|
664
693
|
self.setVisibility(.hidden, for: [.fullscreenButton])
|
|
665
694
|
print("[JWPlayer] Fullscreen button hidden")
|
|
666
695
|
}
|
|
667
|
-
|
|
696
|
+
|
|
668
697
|
// Action for the custom close button
|
|
669
698
|
@objc private func closeButtonTapped() {
|
|
670
699
|
print("[JWPlayer] Custom close button tapped - dismissing player manually")
|
|
@@ -673,21 +702,21 @@ class CustomPlayerViewController: JWPlayerViewController, JWPlayerViewController
|
|
|
673
702
|
self?.callbackHandler?.onPlayerDismissed(isPiPDismissal: false)
|
|
674
703
|
}
|
|
675
704
|
}
|
|
676
|
-
|
|
705
|
+
|
|
677
706
|
// MARK: - JWPlayerViewControllerUIDelegate Method
|
|
678
|
-
|
|
707
|
+
|
|
679
708
|
func playerViewController(_ controller: JWPlayerViewController, controlBarVisibilityChanged isVisible: Bool, frame: CGRect) {
|
|
680
709
|
print("[JWPlayer] Control bar visibility changed: \(isVisible)")
|
|
681
710
|
let targetAlpha: CGFloat = isVisible ? 1.0 : 0.0
|
|
682
|
-
|
|
711
|
+
|
|
683
712
|
// Set alpha directly without animation
|
|
684
713
|
// Ensure closeButton is not nil before accessing
|
|
685
714
|
guard let button = self.closeButton else { return }
|
|
686
715
|
button.alpha = targetAlpha
|
|
687
716
|
}
|
|
688
|
-
|
|
717
|
+
|
|
689
718
|
// MARK: - AVPictureInPictureControllerDelegate Methods
|
|
690
|
-
|
|
719
|
+
|
|
691
720
|
override func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
|
692
721
|
print("[JWPlayer] Delegate: PiP Will Start")
|
|
693
722
|
if #available(iOS 14.2, *) {
|
|
@@ -696,54 +725,123 @@ class CustomPlayerViewController: JWPlayerViewController, JWPlayerViewController
|
|
|
696
725
|
}
|
|
697
726
|
super.pictureInPictureControllerWillStartPictureInPicture(pictureInPictureController)
|
|
698
727
|
}
|
|
699
|
-
|
|
728
|
+
|
|
700
729
|
override func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
|
701
730
|
print("[JWPlayer] Delegate: PiP Did Start")
|
|
702
731
|
super.pictureInPictureControllerDidStartPictureInPicture(pictureInPictureController)
|
|
703
732
|
}
|
|
704
|
-
|
|
733
|
+
|
|
705
734
|
override func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
|
|
706
735
|
print("[JWPlayer] Delegate: PiP Failed to Start: \(error.localizedDescription)")
|
|
707
736
|
super.pictureInPictureController(pictureInPictureController, failedToStartPictureInPictureWithError: error)
|
|
708
737
|
}
|
|
709
|
-
|
|
738
|
+
|
|
710
739
|
override func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
|
711
740
|
print("[JWPlayer] Delegate: PiP Will Stop")
|
|
712
741
|
super.pictureInPictureControllerWillStopPictureInPicture(pictureInPictureController)
|
|
713
742
|
}
|
|
714
|
-
|
|
743
|
+
|
|
715
744
|
override func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
|
716
745
|
print("[JWPlayer] Delegate: PiP Did Stop")
|
|
717
746
|
super.pictureInPictureControllerDidStopPictureInPicture(pictureInPictureController)
|
|
718
|
-
|
|
747
|
+
|
|
719
748
|
// If PiP is stopped directly by the user without restoration, notify the plugin
|
|
720
749
|
// print("[JWPlayer] PiP stopped, cleaning up plugin reference.")
|
|
721
750
|
// self.callbackHandler?.onPlayerDismissed(isPiPDismissal: false)
|
|
722
751
|
}
|
|
723
|
-
|
|
752
|
+
|
|
724
753
|
override func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
|
725
754
|
// print("[JWPlayer] Delegate: PiP Restore UI Requested")
|
|
726
755
|
// Ask the plugin to re-present this view controller instance
|
|
727
756
|
// callbackHandler?.rePresentPlayerRequested()
|
|
728
|
-
|
|
757
|
+
|
|
729
758
|
// Here I will REMOVE the button to close and readd it
|
|
730
759
|
if let closeButton = self.closeButton {
|
|
731
760
|
closeButton.removeFromSuperview()
|
|
732
761
|
self.setupCloseButton()
|
|
733
762
|
}
|
|
734
|
-
|
|
763
|
+
|
|
735
764
|
// MARK: - IMPORTANT-
|
|
736
765
|
// Make sure to call the super method when you have restored the UI, it is important to notify the system of this.
|
|
737
766
|
super.pictureInPictureController(pictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: completionHandler)
|
|
738
767
|
}
|
|
739
|
-
|
|
768
|
+
|
|
740
769
|
override func viewWillDisappear(_ animated: Bool) {
|
|
741
770
|
super.viewWillDisappear(animated)
|
|
742
|
-
|
|
771
|
+
|
|
743
772
|
// Only trigger the dismissal callback if it's a manual dismissal
|
|
744
|
-
if
|
|
773
|
+
if self.isBeingDismissed || self.isMovingFromParent {
|
|
745
774
|
print("[JWPlayer] Manual dismissal detected in viewWillDisappear")
|
|
746
775
|
callbackHandler?.onPlayerDismissed(isPiPDismissal: false)
|
|
747
776
|
}
|
|
748
777
|
}
|
|
778
|
+
|
|
779
|
+
// MARK: - JWCastDelegate
|
|
780
|
+
// Optionally, override the following methods to receive and respond to events when casting.
|
|
781
|
+
// Always call the superclass's method when overriding these methods.
|
|
782
|
+
|
|
783
|
+
// Called when a new casting device comes online.
|
|
784
|
+
override func castController(_ controller: JWCastController, devicesAvailable devices: [JWCastingDevice]) {
|
|
785
|
+
super.castController(controller, devicesAvailable: devices)
|
|
786
|
+
print("[JWCastDelegate]: \(devices.count) became available: \(devices)")
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Called when a successful connection to a casting device is made.
|
|
790
|
+
override func castController(_ controller: JWCastController, connectedTo device: JWCastingDevice) {
|
|
791
|
+
super.castController(controller, connectedTo: device)
|
|
792
|
+
print("[JWCastDelegate]: Connected to device: \(device.identifier)")
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Called when the casting device disconnects.
|
|
796
|
+
override func castController(_ controller: JWCastController, disconnectedWithError error: Error?) {
|
|
797
|
+
super.castController(controller, disconnectedWithError: error)
|
|
798
|
+
|
|
799
|
+
if let error {
|
|
800
|
+
print("[JWCastDelegate]: Casting disconnected from device with error: \"\(error.localizedDescription)\"")
|
|
801
|
+
} else {
|
|
802
|
+
print("[JWCastDelegate]: Casting disconnected from device successfully.")
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Called when the connected casting device is temporarily disconnected. Video resumes on the mobile device until connection resumes.
|
|
807
|
+
override func castController(_ controller: JWCastController, connectionSuspendedWithDevice device: JWCastingDevice) {
|
|
808
|
+
super.castController(controller, connectionSuspendedWithDevice: device)
|
|
809
|
+
print("[JWCastDelegate]: Connection suspended with device: \(device.identifier)")
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Called after connection is reestablished following a temporary disconnection. Video resumes on the casting device.
|
|
813
|
+
override func castController(_ controller: JWCastController, connectionRecoveredWithDevice device: JWCastingDevice) {
|
|
814
|
+
super.castController(controller, connectionRecoveredWithDevice: device)
|
|
815
|
+
print("[JWCastDelegate]: Connection recovered with device: \(device.identifier)")
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Called when an attempt to connect to a casting device is unsuccessful.
|
|
819
|
+
override func castController(_ controller: JWCastController, connectionFailedWithError error: Error) {
|
|
820
|
+
super.castController(controller, connectionFailedWithError: error)
|
|
821
|
+
print("[JWCastDelegate]: Connection failed with error: \(error.localizedDescription)")
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Called when casting session begins.
|
|
825
|
+
override func castController(_ controller: JWCastController, castingBeganWithDevice device: JWCastingDevice) {
|
|
826
|
+
super.castController(controller, castingBeganWithDevice: device)
|
|
827
|
+
print("[JWCastDelegate]: Casting began with device: \(device.identifier)")
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Called when an attempt to cast to a casting device is unsuccessful.
|
|
831
|
+
override func castController(_ controller: JWCastController, castingFailedWithError error: Error) {
|
|
832
|
+
super.castController(controller, castingFailedWithError: error)
|
|
833
|
+
print("[JWCastDelegate]: Casting failed with error: \(error.localizedDescription)")
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Called when a casting session ends.
|
|
837
|
+
override func castController(_ controller: JWCastController, castingEndedWithError error: Error?) {
|
|
838
|
+
super.castController(controller, castingEndedWithError: error)
|
|
839
|
+
|
|
840
|
+
if let error {
|
|
841
|
+
print("[JWCastDelegate]: Casting ended with error: \"\(error.localizedDescription)\"")
|
|
842
|
+
} else {
|
|
843
|
+
print("[JWCastDelegate]: Casting ended successfully.")
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
749
847
|
}
|