@jwplayer/jwplayer-react-native 1.2.0 → 1.3.0
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 +114 -21
- package/RNJWPlayer.podspec +1 -1
- package/android/build.gradle +14 -1
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +19 -4
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +270 -105
- package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +13 -1
- package/badges/version.svg +1 -1
- package/docs/CONFIG-REFERENCE.md +747 -0
- package/docs/MIGRATION-GUIDE.md +617 -0
- package/docs/PLATFORM-DIFFERENCES.md +693 -0
- package/docs/props.md +15 -3
- package/index.d.ts +207 -249
- package/ios/RNJWPlayer/RNJWPlayerView.swift +278 -21
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +33 -16
- package/package.json +2 -2
- package/types/advertising.d.ts +514 -0
- package/types/index.d.ts +21 -0
- package/types/legacy.d.ts +82 -0
- package/types/platform-specific.d.ts +641 -0
- package/types/playlist.d.ts +410 -0
- package/types/unified-config.d.ts +591 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/docs/types.md +0 -254
|
@@ -310,8 +310,17 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
310
310
|
private var pendingPlayerConfig: [String: Any]?
|
|
311
311
|
private var playerConfigTimeout: Timer?
|
|
312
312
|
private let maxPendingTime: TimeInterval = 5.0 // Maximum time to wait for PiP to close
|
|
313
|
+
private var isRecreatingPlayer: Bool = false // Prevents re-entrant calls during recreation
|
|
313
314
|
|
|
314
315
|
@objc func recreatePlayerWithConfig(_ config: [String: Any]) {
|
|
316
|
+
// Prevent re-entrant calls while player is being recreated
|
|
317
|
+
if isRecreatingPlayer {
|
|
318
|
+
print("Warning: Player recreation already in progress, queueing this config change")
|
|
319
|
+
// Override any pending config with the latest one
|
|
320
|
+
pendingPlayerConfig = config
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
315
324
|
// Cancel any existing pending configuration
|
|
316
325
|
if pendingPlayerConfig != nil {
|
|
317
326
|
print("Warning: Overriding pending content switch")
|
|
@@ -325,7 +334,7 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
325
334
|
return
|
|
326
335
|
}
|
|
327
336
|
|
|
328
|
-
// 1. Handle PiP state
|
|
337
|
+
// 1. Handle PiP state (must exit PiP before any changes)
|
|
329
338
|
var isPipActive = false
|
|
330
339
|
var pipController: AVPictureInPictureController?
|
|
331
340
|
|
|
@@ -337,11 +346,10 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
337
346
|
isPipActive = pipController?.isPictureInPictureActive ?? false
|
|
338
347
|
}
|
|
339
348
|
|
|
340
|
-
// 2. If in PiP, store the config and exit PiP
|
|
341
349
|
if isPipActive {
|
|
342
350
|
guard let pipController = pipController else {
|
|
343
351
|
print("Warning: PiP appears active but controller is nil, proceeding with direct switch")
|
|
344
|
-
|
|
352
|
+
proceedWithConfigChange(config: config)
|
|
345
353
|
return
|
|
346
354
|
}
|
|
347
355
|
|
|
@@ -353,31 +361,255 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
353
361
|
print("Warning: PiP close timeout reached, forcing content switch")
|
|
354
362
|
if let pendingConfig = self.pendingPlayerConfig {
|
|
355
363
|
self.pendingPlayerConfig = nil
|
|
356
|
-
self.
|
|
364
|
+
self.proceedWithConfigChange(config: pendingConfig)
|
|
357
365
|
}
|
|
358
366
|
}
|
|
359
367
|
|
|
360
368
|
// Attempt to stop PiP
|
|
361
369
|
pipController.stopPictureInPicture()
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 2. Handle fullscreen state (only exit if we need to recreate)
|
|
374
|
+
let isFullscreen = playerViewController?.isFullScreen ?? false
|
|
375
|
+
|
|
376
|
+
if isFullscreen && requiresPlayerRecreation(config) {
|
|
377
|
+
// Only exit fullscreen if we need to recreate the player
|
|
378
|
+
// For reconfiguration, fullscreen state will be preserved automatically
|
|
379
|
+
print("Fullscreen active and recreation needed - exiting fullscreen first")
|
|
380
|
+
pendingPlayerConfig = config
|
|
362
381
|
|
|
363
|
-
|
|
364
|
-
|
|
382
|
+
// Set a timeout to prevent infinite waiting
|
|
383
|
+
playerConfigTimeout = Timer.scheduledTimer(withTimeInterval: maxPendingTime, repeats: false) { [weak self] _ in
|
|
384
|
+
guard let self = self else { return }
|
|
385
|
+
print("Warning: Fullscreen exit timeout reached, forcing content switch")
|
|
386
|
+
if let pendingConfig = self.pendingPlayerConfig {
|
|
387
|
+
self.pendingPlayerConfig = nil
|
|
388
|
+
self.proceedWithConfigChange(config: pendingConfig)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Exit fullscreen
|
|
393
|
+
playerViewController?.dismiss(animated: true) { [weak self] in
|
|
394
|
+
guard let self = self else { return }
|
|
395
|
+
self.playerConfigTimeout?.invalidate()
|
|
396
|
+
self.playerConfigTimeout = nil
|
|
397
|
+
if let pendingConfig = self.pendingPlayerConfig {
|
|
398
|
+
self.pendingPlayerConfig = nil
|
|
399
|
+
self.proceedWithConfigChange(config: pendingConfig)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return
|
|
365
403
|
}
|
|
404
|
+
|
|
405
|
+
// 3. No special state handling needed - proceed with config change
|
|
406
|
+
// If in fullscreen and just reconfiguring, state will be preserved
|
|
407
|
+
proceedWithConfigChange(config: config)
|
|
366
408
|
}
|
|
367
409
|
|
|
368
|
-
|
|
410
|
+
/// Determines the appropriate way to apply the config change
|
|
411
|
+
private func proceedWithConfigChange(config: [String: Any]) {
|
|
369
412
|
// Clear any pending timeout
|
|
370
413
|
playerConfigTimeout?.invalidate()
|
|
371
414
|
playerConfigTimeout = nil
|
|
372
415
|
|
|
373
416
|
// Ensure we're on the main thread
|
|
374
|
-
|
|
417
|
+
guard Thread.isMainThread else {
|
|
418
|
+
DispatchQueue.main.async { [weak self] in
|
|
419
|
+
self?.proceedWithConfigChange(config: config)
|
|
420
|
+
}
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Decide: recreate or reconfigure?
|
|
425
|
+
if requiresPlayerRecreation(config) {
|
|
426
|
+
print("Player recreation required - performing full recreation")
|
|
427
|
+
completePlayerRecreation(config: config)
|
|
428
|
+
} else {
|
|
429
|
+
print("Reconfiguring existing player without recreation (optimized)")
|
|
430
|
+
reconfigurePlayer(config: config)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/// Determines if a config change requires full player recreation.
|
|
435
|
+
/// Only returns true for changes that genuinely cannot be handled by reconfiguration.
|
|
436
|
+
private func requiresPlayerRecreation(_ config: [String: Any]) -> Bool {
|
|
437
|
+
// If no player exists, we need to create one
|
|
438
|
+
guard playerViewController != nil || playerView != nil else {
|
|
439
|
+
return true
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// If no previous config, this is first-time setup
|
|
443
|
+
guard let currentConfig = currentConfig else {
|
|
444
|
+
return true
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Check for license changes (requires recreation)
|
|
448
|
+
let newLicense = config["license"] as? String
|
|
449
|
+
let oldLicense = currentConfig["license"] as? String
|
|
450
|
+
|
|
451
|
+
if newLicense != oldLicense {
|
|
452
|
+
if newLicense != nil && oldLicense != nil {
|
|
453
|
+
print("License changed from '\(oldLicense!)' to '\(newLicense!)' - recreation required")
|
|
454
|
+
return true
|
|
455
|
+
} else if newLicense == nil || oldLicense == nil {
|
|
456
|
+
print("License presence changed - recreation required")
|
|
457
|
+
return true
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Check for viewOnly mode changes (playerView vs playerViewController)
|
|
462
|
+
let newViewOnly = config["viewOnly"] as? Bool ?? false
|
|
463
|
+
let oldViewOnly = currentConfig["viewOnly"] as? Bool ?? false
|
|
464
|
+
if newViewOnly != oldViewOnly {
|
|
465
|
+
print("viewOnly mode changed - recreation required")
|
|
466
|
+
return true
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// All other changes can be handled by reconfiguration (optimized path!)
|
|
470
|
+
print("iOS: Using reconfiguration path (optimized)")
|
|
471
|
+
return false
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// Reconfigures the existing player with new settings without recreation.
|
|
475
|
+
/// This is the preferred path for config updates as it preserves the player instance
|
|
476
|
+
/// and maintains state (fullscreen, etc.), following JWPlayer SDK's design intent.
|
|
477
|
+
private func reconfigurePlayer(config: [String: Any]) {
|
|
478
|
+
guard let playerViewController = playerViewController else {
|
|
479
|
+
// No player exists, need to create
|
|
480
|
+
print("No player exists - falling back to recreation")
|
|
481
|
+
completePlayerRecreation(config: config)
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Prevent re-entrant calls during reconfiguration (Issue #192 - Error 180001)
|
|
486
|
+
if isRecreatingPlayer {
|
|
487
|
+
print("Warning: Reconfiguration already in progress, queueing this config change")
|
|
488
|
+
pendingPlayerConfig = config
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
isRecreatingPlayer = true
|
|
493
|
+
|
|
494
|
+
// Preserve state
|
|
495
|
+
let wasFullscreen = playerViewController.isFullScreen
|
|
496
|
+
let currentState = playerViewController.player.getState()
|
|
497
|
+
let wasPlaying = currentState == .playing
|
|
498
|
+
|
|
499
|
+
// Stop playback before reconfiguration (prevents issues)
|
|
500
|
+
playerViewController.player.stop()
|
|
501
|
+
|
|
502
|
+
// Parse config early (before setting license) to check if it's valid
|
|
503
|
+
let forceLegacyConfig = config["forceLegacyConfig"] as? Bool ?? false
|
|
504
|
+
let playlistItemCallback = config["playlistItemCallbackEnabled"] as? Bool ?? false
|
|
505
|
+
|
|
506
|
+
// Set license FIRST (before parsing config fully)
|
|
507
|
+
let license = config["license"] as? String
|
|
508
|
+
self.setLicense(license: license)
|
|
509
|
+
|
|
510
|
+
// Handle audio session for background/PiP
|
|
511
|
+
if let bae = config["backgroundAudioEnabled"] as? Bool, let pe = config["pipEnabled"] as? Bool {
|
|
512
|
+
backgroundAudioEnabled = bae
|
|
513
|
+
pipEnabled = pe
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if backgroundAudioEnabled || pipEnabled {
|
|
517
|
+
let category = config["category"] != nil ? config["category"] as? String : "playback"
|
|
518
|
+
let categoryOptions = config["categoryOptions"] as? [String]
|
|
519
|
+
let mode = config["mode"] as? String
|
|
520
|
+
self.initAudioSession(category: category, categoryOptions: categoryOptions, mode: mode)
|
|
521
|
+
} else {
|
|
522
|
+
self.deinitAudioSession()
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Handle DRM parameters
|
|
526
|
+
processSpcUrl = config["processSpcUrl"] as? String
|
|
527
|
+
fairplayCertUrl = config["certificateUrl"] as? String
|
|
528
|
+
contentUUID = config["contentUUID"] as? String
|
|
529
|
+
|
|
530
|
+
// Handle legacy DRM in playlist
|
|
531
|
+
if forceLegacyConfig {
|
|
532
|
+
if let playlist = config["playlist"] as? [AnyObject] {
|
|
533
|
+
let item = playlist.first
|
|
534
|
+
if let itemMap = item as? [String: Any] {
|
|
535
|
+
if itemMap["processSpcUrl"] != nil {
|
|
536
|
+
processSpcUrl = itemMap["processSpcUrl"] as? String
|
|
537
|
+
}
|
|
538
|
+
if itemMap["certificateUrl"] != nil {
|
|
539
|
+
fairplayCertUrl = itemMap["certificateUrl"] as? String
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Build new configuration
|
|
546
|
+
do {
|
|
547
|
+
let playerConfig: JWPlayerConfiguration
|
|
548
|
+
|
|
549
|
+
if forceLegacyConfig {
|
|
550
|
+
playerConfig = try getPlayerConfiguration(config: config)
|
|
551
|
+
} else {
|
|
552
|
+
guard let data = try? JSONSerialization.data(withJSONObject: config, options: .prettyPrinted),
|
|
553
|
+
let jwConfig = try? JWJSONParser.config(from: data) else {
|
|
554
|
+
print("Failed to parse config - falling back to recreation")
|
|
555
|
+
completePlayerRecreation(config: config)
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
playerConfig = jwConfig
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Update stored config
|
|
562
|
+
currentConfig = config
|
|
563
|
+
|
|
564
|
+
// Reconfigure existing player (this is the key optimization!)
|
|
565
|
+
playerViewController.player.configurePlayer(with: playerConfig)
|
|
566
|
+
|
|
567
|
+
// Setup playlist item callback if needed
|
|
568
|
+
if playlistItemCallback {
|
|
569
|
+
setupPlaylistItemCallback()
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Fullscreen state is automatically preserved by the view controller
|
|
573
|
+
// No need to manually restore it
|
|
574
|
+
print("Player reconfigured successfully (fullscreen: \(wasFullscreen))")
|
|
575
|
+
|
|
576
|
+
// Clear the reconfiguration flag after a delay to ensure SDK completes initialization
|
|
577
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
578
|
+
guard let self = self else { return }
|
|
579
|
+
self.isRecreatingPlayer = false
|
|
580
|
+
|
|
581
|
+
// If there's a queued config change, process it now
|
|
582
|
+
if let queuedConfig = self.pendingPlayerConfig {
|
|
583
|
+
print("Processing queued config change after reconfiguration")
|
|
584
|
+
self.pendingPlayerConfig = nil
|
|
585
|
+
self.recreatePlayerWithConfig(queuedConfig)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Optionally restart playback if it was playing
|
|
590
|
+
// (Usually handled by autostart in config)
|
|
591
|
+
|
|
592
|
+
} catch {
|
|
593
|
+
print("Error during reconfiguration: \(error) - falling back to recreation")
|
|
594
|
+
isRecreatingPlayer = false // Clear flag before fallback
|
|
595
|
+
completePlayerRecreation(config: config)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/// Performs full player recreation (destroy + create).
|
|
600
|
+
/// This is the expensive path and should only be used when truly necessary.
|
|
601
|
+
private func completePlayerRecreation(config: [String: Any]) {
|
|
602
|
+
// Ensure we're on the main thread
|
|
603
|
+
guard Thread.isMainThread else {
|
|
375
604
|
DispatchQueue.main.async { [weak self] in
|
|
376
|
-
self?.
|
|
605
|
+
self?.completePlayerRecreation(config: config)
|
|
377
606
|
}
|
|
378
607
|
return
|
|
379
608
|
}
|
|
380
609
|
|
|
610
|
+
// Set flag to prevent re-entrant calls
|
|
611
|
+
isRecreatingPlayer = true
|
|
612
|
+
|
|
381
613
|
// 1. Stop current playback safely
|
|
382
614
|
if let playerView = playerView {
|
|
383
615
|
let state = playerView.player.getState()
|
|
@@ -391,12 +623,26 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
391
623
|
}
|
|
392
624
|
}
|
|
393
625
|
|
|
394
|
-
// 2.
|
|
626
|
+
// 2. Destroy current player
|
|
395
627
|
dismissPlayerViewController()
|
|
396
628
|
removePlayerView()
|
|
397
629
|
|
|
398
|
-
// 3.
|
|
630
|
+
// 3. Create new player with new config
|
|
399
631
|
setNewConfig(config: config)
|
|
632
|
+
|
|
633
|
+
// 4. Clear the recreation flag after a delay to ensure setup completes
|
|
634
|
+
// The iOS SDK needs time to finish initialization
|
|
635
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
636
|
+
guard let self = self else { return }
|
|
637
|
+
self.isRecreatingPlayer = false
|
|
638
|
+
|
|
639
|
+
// If there's a queued config change, process it now
|
|
640
|
+
if let queuedConfig = self.pendingPlayerConfig {
|
|
641
|
+
print("Processing queued config change")
|
|
642
|
+
self.pendingPlayerConfig = nil
|
|
643
|
+
self.recreatePlayerWithConfig(queuedConfig)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
400
646
|
}
|
|
401
647
|
|
|
402
648
|
func setNewConfig(config: [String : Any]) {
|
|
@@ -436,7 +682,6 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
436
682
|
contentUUID = config["contentUUID"] as? String
|
|
437
683
|
|
|
438
684
|
if forceLegacyConfig == true {
|
|
439
|
-
|
|
440
685
|
// Dangerous: check playlist for processSpcUrl / fairplayCertUrl in playlist
|
|
441
686
|
// Only checks first playlist item as multi-item DRM playlists are ill advised
|
|
442
687
|
if let playlist = config["playlist"] as? [AnyObject] {
|
|
@@ -450,7 +695,6 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
450
695
|
}
|
|
451
696
|
}
|
|
452
697
|
}
|
|
453
|
-
} else {
|
|
454
698
|
}
|
|
455
699
|
|
|
456
700
|
do {
|
|
@@ -1263,26 +1507,30 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
1263
1507
|
|
|
1264
1508
|
func appIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) {
|
|
1265
1509
|
guard let fairplayCertUrlString = fairplayCertUrl, let finalUrl = URL(string: fairplayCertUrlString) else {
|
|
1510
|
+
handler(nil)
|
|
1266
1511
|
return
|
|
1267
1512
|
}
|
|
1268
1513
|
|
|
1269
1514
|
let request = URLRequest(url: finalUrl)
|
|
1270
1515
|
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
|
1271
1516
|
if let error = error {
|
|
1272
|
-
print("
|
|
1517
|
+
print("Error fetching FairPlay certificate: \(error.localizedDescription)")
|
|
1518
|
+
handler(nil)
|
|
1519
|
+
return
|
|
1273
1520
|
}
|
|
1521
|
+
|
|
1274
1522
|
handler(data)
|
|
1275
1523
|
}
|
|
1276
1524
|
task.resume()
|
|
1277
1525
|
}
|
|
1278
1526
|
|
|
1279
1527
|
func contentKeyWithSPCData(_ spcData: Data, completionHandler handler: @escaping (Data?, Date?, String?) -> Void) {
|
|
1280
|
-
|
|
1528
|
+
guard let processSpcUrlString = processSpcUrl else {
|
|
1529
|
+
handler(nil, nil, nil)
|
|
1281
1530
|
return
|
|
1282
1531
|
}
|
|
1283
1532
|
|
|
1284
|
-
guard let processSpcUrl = URL(string:
|
|
1285
|
-
print("Invalid processSpcUrl")
|
|
1533
|
+
guard let processSpcUrl = URL(string: processSpcUrlString) else {
|
|
1286
1534
|
handler(nil, nil, nil)
|
|
1287
1535
|
return
|
|
1288
1536
|
}
|
|
@@ -1293,13 +1541,22 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
1293
1541
|
ckcRequest.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
|
1294
1542
|
|
|
1295
1543
|
URLSession.shared.dataTask(with: ckcRequest) { (data, response, error) in
|
|
1296
|
-
if let
|
|
1297
|
-
print("
|
|
1544
|
+
if let error = error {
|
|
1545
|
+
print("Error fetching FairPlay license: \(error.localizedDescription)")
|
|
1298
1546
|
handler(nil, nil, nil)
|
|
1299
1547
|
return
|
|
1300
1548
|
}
|
|
1301
1549
|
|
|
1302
|
-
|
|
1550
|
+
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
|
|
1551
|
+
handler(nil, nil, nil)
|
|
1552
|
+
return
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
if let data = data {
|
|
1556
|
+
handler(data, nil, "application/octet-stream")
|
|
1557
|
+
} else {
|
|
1558
|
+
handler(nil, nil, nil)
|
|
1559
|
+
}
|
|
1303
1560
|
}.resume()
|
|
1304
1561
|
}
|
|
1305
1562
|
|
|
@@ -1326,7 +1583,7 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
1326
1583
|
if let config = pendingPlayerConfig {
|
|
1327
1584
|
pendingPlayerConfig = nil
|
|
1328
1585
|
DispatchQueue.main.async { [weak self] in
|
|
1329
|
-
self?.
|
|
1586
|
+
self?.proceedWithConfigChange(config: config)
|
|
1330
1587
|
}
|
|
1331
1588
|
}
|
|
1332
1589
|
}
|
|
@@ -202,40 +202,57 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerF
|
|
|
202
202
|
|
|
203
203
|
func appIdentifierForURL(_ url: URL, completionHandler handler: @escaping (Data?) -> Void) {
|
|
204
204
|
guard let fairplayCertUrlString = parentView?.fairplayCertUrl, let fairplayCertUrl = URL(string: fairplayCertUrlString) else {
|
|
205
|
+
handler(nil)
|
|
205
206
|
return
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
let request = URLRequest(url: fairplayCertUrl)
|
|
209
210
|
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
|
210
211
|
if let error = error {
|
|
211
|
-
print("
|
|
212
|
+
print("Error fetching FairPlay certificate: \(error.localizedDescription)")
|
|
213
|
+
handler(nil)
|
|
214
|
+
return
|
|
212
215
|
}
|
|
216
|
+
|
|
213
217
|
handler(data)
|
|
214
218
|
}
|
|
215
219
|
task.resume()
|
|
216
220
|
}
|
|
217
221
|
|
|
218
222
|
func contentKeyWithSPCData(_ spcData: Data, completionHandler handler: @escaping (Data?, Date?, String?) -> Void) {
|
|
219
|
-
|
|
223
|
+
guard let processSpcUrlString = parentView?.processSpcUrl else {
|
|
224
|
+
handler(nil, nil, nil)
|
|
220
225
|
return
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
ckcRequest.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
|
228
|
-
|
|
229
|
-
URLSession.shared.dataTask(with: ckcRequest as URLRequest) { (data, response, error) in
|
|
230
|
-
if let httpResponse = response as? HTTPURLResponse, (error != nil || httpResponse.statusCode != 200) {
|
|
231
|
-
NSLog("DRM ckc request error - %@", error.debugDescription)
|
|
232
|
-
handler(nil, nil, nil)
|
|
233
|
-
return
|
|
234
|
-
}
|
|
228
|
+
guard let processSpcUrl = URL(string: processSpcUrlString) else {
|
|
229
|
+
handler(nil, nil, nil)
|
|
230
|
+
return
|
|
231
|
+
}
|
|
235
232
|
|
|
233
|
+
let ckcRequest = NSMutableURLRequest(url: processSpcUrl)
|
|
234
|
+
ckcRequest.httpMethod = "POST"
|
|
235
|
+
ckcRequest.httpBody = spcData
|
|
236
|
+
ckcRequest.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
|
237
|
+
|
|
238
|
+
URLSession.shared.dataTask(with: ckcRequest as URLRequest) { (data, response, error) in
|
|
239
|
+
if let error = error {
|
|
240
|
+
print("Error fetching FairPlay license: \(error.localizedDescription)")
|
|
241
|
+
handler(nil, nil, nil)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
|
|
246
|
+
handler(nil, nil, nil)
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if let data = data {
|
|
236
251
|
handler(data, nil, "application/octet-stream")
|
|
237
|
-
}
|
|
238
|
-
|
|
252
|
+
} else {
|
|
253
|
+
handler(nil, nil, nil)
|
|
254
|
+
}
|
|
255
|
+
}.resume()
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
// MARK: - AV Picture In Picture Delegate
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwplayer/jwplayer-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "React-native Android/iOS plugin for JWPlayer SDK (https://www.jwplayer.com/)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/jwplayer/jwplayer-react-native"
|
|
17
|
+
"url": "git+https://github.com/jwplayer/jwplayer-react-native.git"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"react",
|