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