@capgo/capacitor-jw-player 0.0.5 → 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.
@@ -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? = nil
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? = nil
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 = .fullScreen
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
- // print("[JWPlayer] Starting playback")
658
- // // player.play()
659
- // print("[JWPlayer] Playback started")
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 (self.isBeingDismissed || self.isMovingFromParent) {
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
  }