@jwplayer/jwplayer-react-native 1.0.2 → 1.1.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.
@@ -1,4 +1,3 @@
1
-
2
1
  package com.jwplayer.rnjwplayer;
3
2
 
4
3
  import com.facebook.react.bridge.ReactApplicationContext;
@@ -41,6 +40,9 @@ public class RNJWPlayerViewManager extends SimpleViewManager<RNJWPlayerView> {
41
40
 
42
41
  @ReactProp(name = "controls")
43
42
  public void setControls(RNJWPlayerView view, Boolean controls) {
43
+ if (view == null || view.mPlayerView == null) {
44
+ return;
45
+ }
44
46
  view.mPlayerView.getPlayer().setControls(controls);
45
47
  }
46
48
 
@@ -155,6 +157,14 @@ public class RNJWPlayerViewManager extends SimpleViewManager<RNJWPlayerView> {
155
157
  MapBuilder.of(
156
158
  "phasedRegistrationNames",
157
159
  MapBuilder.of("bubbled", "onAudioTracks")))
160
+ .put("topCaptionsChanged",
161
+ MapBuilder.of(
162
+ "phasedRegistrationNames",
163
+ MapBuilder.of("bubbled", "onCaptionsChanged")))
164
+ .put("topCaptionsList",
165
+ MapBuilder.of(
166
+ "phasedRegistrationNames",
167
+ MapBuilder.of("bubbled", "onCaptionsList")))
158
168
  .put("topCasting",
159
169
  MapBuilder.of(
160
170
  "phasedRegistrationNames",
@@ -163,6 +173,10 @@ public class RNJWPlayerViewManager extends SimpleViewManager<RNJWPlayerView> {
163
173
  MapBuilder.of(
164
174
  "phasedRegistrationNames",
165
175
  MapBuilder.of("bubbled", "onLoaded")))
176
+ .put("topBeforeNextPlaylistItem",
177
+ MapBuilder.of(
178
+ "phasedRegistrationNames",
179
+ MapBuilder.of("bubbled", "onBeforeNextPlaylistItem")))
166
180
  .build();
167
181
  }
168
182
 
@@ -2,17 +2,23 @@ package com.jwplayer.rnjwplayer;
2
2
 
3
3
  import static androidx.media3.common.util.Util.toByteArray;
4
4
 
5
+ import android.util.Log;
5
6
  import android.util.Patterns;
6
7
  import android.webkit.URLUtil;
7
8
 
8
9
  import com.facebook.react.bridge.ReadableArray;
9
10
  import com.facebook.react.bridge.ReadableMap;
11
+ import com.jwplayer.pub.api.JsonHelper;
12
+ import com.jwplayer.pub.api.configuration.ads.AdvertisingConfig;
13
+ import com.jwplayer.pub.api.events.Event;
10
14
  import com.jwplayer.pub.api.media.ads.AdBreak;
11
15
  import com.jwplayer.pub.api.media.captions.Caption;
12
16
  import com.jwplayer.pub.api.media.captions.CaptionType;
13
17
  import com.jwplayer.pub.api.media.playlists.MediaSource;
14
18
  import com.jwplayer.pub.api.media.playlists.PlaylistItem;
15
19
 
20
+ import org.json.JSONObject;
21
+
16
22
  import java.io.IOException;
17
23
  import java.io.InputStream;
18
24
  import java.io.OutputStream;
@@ -20,8 +26,8 @@ import java.net.HttpURLConnection;
20
26
  import java.net.URL;
21
27
  import java.util.ArrayList;
22
28
  import java.util.List;
23
- import java.util.Map;
24
29
  import java.util.Locale;
30
+ import java.util.Map;
25
31
 
26
32
  public class Util {
27
33
 
@@ -62,7 +68,7 @@ public class Util {
62
68
  }
63
69
  }
64
70
 
65
- public static boolean isValidURL(String url){
71
+ public static boolean isValidURL(String url) {
66
72
  return URLUtil.isValidUrl(url) && Patterns.WEB_URL.matcher(url).matches();
67
73
  }
68
74
 
@@ -75,14 +81,28 @@ public class Util {
75
81
  while (playlistItems.size() > j) {
76
82
  ReadableMap playlistItem = playlistItems.getMap(j);
77
83
 
78
- PlaylistItem newPlayListItem = getPlaylistItem((playlistItem));
79
- playlist.add(newPlayListItem);
84
+ JSONObject obj;
85
+ PlaylistItem item = null;
86
+ // Try since legacy config may or may not conform to this standard
87
+ try {
88
+ obj = MapUtil.toJSONObject(playlistItem);
89
+ item = JsonHelper.parsePlaylistItemJson(obj);
90
+ } catch (Exception ex) {
91
+ Log.e("createPlaylist", ex.toString());
92
+ }
93
+ if (item != null) {
94
+ playlist.add(item);
95
+ } else {
96
+ // Try to use the legacy format
97
+ PlaylistItem newPlayListItem = getPlaylistItem((playlistItem));
98
+ playlist.add(newPlayListItem);
99
+ }
80
100
  j++;
81
101
  }
82
102
  return playlist;
83
103
  }
84
104
 
85
- public static PlaylistItem getPlaylistItem (ReadableMap playlistItem) {
105
+ public static PlaylistItem getPlaylistItem(ReadableMap playlistItem) {
86
106
  PlaylistItem.Builder itemBuilder = new PlaylistItem.Builder();
87
107
 
88
108
  if (playlistItem.hasKey("file")) {
@@ -194,11 +214,12 @@ public class Util {
194
214
 
195
215
  /**
196
216
  * Internal helper for parsing a caption type from a known string
217
+ *
197
218
  * @param type one of "CAPTIONS", "CHAPTERS", "THUMBNAILS"
198
219
  * @return the correct Enum CaptionType
199
220
  */
200
- public static CaptionType getCaptionType(String type){
201
- for (CaptionType captionType: CaptionType.values()) {
221
+ public static CaptionType getCaptionType(String type) {
222
+ for (CaptionType captionType : CaptionType.values()) {
202
223
  if (captionType.name().equals(type)) {
203
224
  return CaptionType.valueOf(type);
204
225
  }
@@ -234,7 +255,42 @@ public class Util {
234
255
  }
235
256
 
236
257
  // Method to get the event type value
237
- public static int getEventTypeValue(AdEventType eventType) {
258
+ public static int getAdEventTypeValue(AdEventType eventType) {
238
259
  return eventType.getValue();
239
260
  }
240
- }
261
+
262
+ public enum AdEventClient {
263
+ JWAdEventClientJWPlayer(0),
264
+ JWAdEventClientGoogleIMA(1),
265
+ JWAdEventClientGoogleIMADAI(2),
266
+ JWAdEventClientUnknown(3);
267
+
268
+ private final int value;
269
+
270
+ AdEventClient(int value) {
271
+ this.value = value;
272
+ }
273
+
274
+ public int getValue() {
275
+ return value;
276
+ }
277
+ }
278
+
279
+ public static int getAdEventClientValue(Event adEvent) {
280
+ AdvertisingConfig adConfig = adEvent.getPlayer().getConfig().getAdvertisingConfig();
281
+ if (adConfig == null) {
282
+ return AdEventClient.JWAdEventClientUnknown.getValue();
283
+ }
284
+
285
+ switch (adConfig.getAdClient()) {
286
+ case IMA:
287
+ return AdEventClient.JWAdEventClientGoogleIMA.getValue();
288
+ case IMA_DAI:
289
+ return AdEventClient.JWAdEventClientGoogleIMADAI.getValue();
290
+ case VAST:
291
+ return AdEventClient.JWAdEventClientJWPlayer.getValue();
292
+ default:
293
+ return AdEventClient.JWAdEventClientUnknown.getValue();
294
+ }
295
+ }
296
+ }
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20" role="img" aria-label="version: 1.0.2"><title>version: 1.0.2</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="51" height="20" fill="#555"/><rect x="51" width="39" height="20" fill="#007ec6"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="265" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="410">version</text><text x="265" y="140" transform="scale(.1)" fill="#fff" textLength="410">version</text><text aria-hidden="true" x="695" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">1.0.2</text><text x="695" y="140" transform="scale(.1)" fill="#fff" textLength="290">1.0.2</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20" role="img" aria-label="version: 1.1.0"><title>version: 1.1.0</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="51" height="20" fill="#555"/><rect x="51" width="39" height="20" fill="#007ec6"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="265" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="410">version</text><text x="265" y="140" transform="scale(.1)" fill="#fff" textLength="410">version</text><text aria-hidden="true" x="695" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">1.1.0</text><text x="695" y="140" transform="scale(.1)" fill="#fff" textLength="290">1.1.0</text></g></svg>
package/index.d.ts CHANGED
@@ -6,6 +6,10 @@ declare module "@jwplayer/jwplayer-react-native" {
6
6
  pid?: string;
7
7
  mute?: boolean;
8
8
  forceLegacyConfig?: boolean;
9
+ /**
10
+ * If true, `onBeforeNextPlaylistItem` MUST be impelemented with `player.resolveNextPlaylistItem()` called in the callback or content will hang
11
+ */
12
+ playlistItemCallbackEnabled?: boolean;
9
13
  useTextureView?: boolean;
10
14
  autostart?: boolean;
11
15
  nextupoffset?: string | number; // String with % or number
@@ -26,7 +30,13 @@ declare module "@jwplayer/jwplayer-react-native" {
26
30
  advertising?: JwAdvertisingConfig;
27
31
  playbackRates?: number[];
28
32
  playbackRateControls?: boolean;
33
+ // Non-Json Parsing props
29
34
  license: string;
35
+ playerInModal?: boolean;
36
+ fullScreenOnLandscape?: boolean;
37
+ landscapeOnFullScreen?: boolean;
38
+ portraitOnExitFullScreen?: boolean;
39
+ exitFullScreenOnPortrait?: boolean;
30
40
  }
31
41
 
32
42
  type JwThumbnailPreview = 101 | 102 | 103;
@@ -477,6 +487,15 @@ declare module "@jwplayer/jwplayer-react-native" {
477
487
  rate: number;
478
488
  at: number;
479
489
  }
490
+ interface PlayerSetupErrorProps {
491
+ errorMessage?: string;
492
+ errorCode?: number;
493
+ }
494
+ interface PlayerErrorProps {
495
+ error?: string;
496
+ errorCode?: number;
497
+ description?: string; // Android Only
498
+ }
480
499
  interface TimeEventProps {
481
500
  position: number;
482
501
  duration: number;
@@ -498,15 +517,26 @@ declare module "@jwplayer/jwplayer-react-native" {
498
517
  error: string;
499
518
  }
500
519
  interface PlayerWarningEventProps {
501
- code: string;
502
- warning: string;
520
+ code?: number;
521
+ warning?: string;
522
+ adErrorCode?: number; // Android only
503
523
  }
504
524
  interface AdEventProps {
505
- client?: string;
525
+ client: number;
506
526
  reason?: string;
507
527
  type: number;
508
528
  }
509
- type NativeError = (event: BaseEvent<PlayerErrorEventProps>) => void;
529
+ // Overloaded type to be used in multiple error events
530
+ interface CaptionsChangedEventProps {
531
+ index?: number;
532
+ }
533
+ interface CaptionsListEventProps {
534
+ index: number;
535
+ file?: string;
536
+ label: string;
537
+ default: string;
538
+ }
539
+ type NativeError = (event: BaseEvent<PlayerErrorEventProps> | BaseEvent<PlayerSetupErrorProps> | BaseEvent<PlayerErrorProps>) => void;
510
540
  type NativeWarning = (event: BaseEvent<PlayerWarningEventProps>) => void;
511
541
  interface PropsType {
512
542
  config: Config | JwConfig;
@@ -526,9 +556,9 @@ declare module "@jwplayer/jwplayer-react-native" {
526
556
  onRateChanged?: (event?: BaseEvent<RateChangedEventProps>) => void;
527
557
  onSetupPlayerError?: NativeError;
528
558
  onPlayerError?: NativeError;
529
- onPlayerWarning?: NativeWarning;
530
- onPlayerAdError?: NativeError;
531
- onPlayerAdWarning?: NativeWarning;
559
+ onPlayerWarning?: NativeWarning;
560
+ onPlayerAdError?: NativeError;
561
+ onPlayerAdWarning?: NativeWarning;
532
562
  onAdEvent?: (event: BaseEvent<AdEventProps>) => void;
533
563
  onAdTime?: (event: BaseEvent<TimeEventProps>) => void;
534
564
  onBuffer?: () => void;
@@ -540,8 +570,11 @@ declare module "@jwplayer/jwplayer-react-native" {
540
570
  onControlBarVisible?: (event: BaseEvent<ControlBarVisibleEventProps>) => void;
541
571
  onPlaylistComplete?: () => void;
542
572
  onPlaylistItem?: (event: BaseEvent<PlaylistItemEventProps>) => void;
573
+ onCaptionsChanged?: (event: BaseEvent<CaptionsChangedEventProps>) => void;
574
+ onCaptionsList?: (event: BaseEvent<CaptionsListEventProps>) => void;
543
575
  onAudioTracks?: () => void;
544
576
  shouldComponentUpdate?: (nextProps: any, nextState: any) => boolean;
577
+ onBeforeNextPlaylistItem?: (event: BaseEvent<PlaylistItemEventProps>) => void;
545
578
  }
546
579
 
547
580
  export default class JWPlayer extends React.Component<PropsType> {
@@ -558,7 +591,7 @@ declare module "@jwplayer/jwplayer-react-native" {
558
591
  setControls(show: boolean): void;
559
592
  setLockScreenControls(show: boolean): void;
560
593
  seekTo(time: number): void;
561
- loadPlaylist(playlistItems: PlaylistItem[]): void;
594
+ loadPlaylist(playlistItems: PlaylistItem[] | JwPlaylistItem[] | string): void;
562
595
  setFullscreen(fullScreen: boolean): void;
563
596
  position(): Promise<number>;
564
597
  setUpCastController(): void;
@@ -571,6 +604,12 @@ declare module "@jwplayer/jwplayer-react-native" {
571
604
  getCurrentAudioTrack(): Promise<number | null>;
572
605
  setCurrentAudioTrack(index: number): void;
573
606
  setCurrentCaptions(index: number): void;
607
+ getCurrentCaptions(): Promise<number | null>;
574
608
  setVisibility(visibility: boolean, controls: JWControlType[]): void;
609
+ /**
610
+ * Only called inside `onBeforeNextPlaylistItem` callback, and once per callback
611
+ * @param playlistItem `PlaylistItem` or `JwPlaylistItem`
612
+ */
613
+ resolveNextPlaylistItem(playlistItem: PlaylistItem | JwPlaylistItem): void;
575
614
  }
576
615
  }
package/index.js CHANGED
@@ -174,6 +174,12 @@ export default class JWPlayer extends Component {
174
174
  config: PropTypes.shape({
175
175
  license: PropTypes.string.isRequired,
176
176
  forceLegacyConfig: PropTypes.bool,
177
+ /**
178
+ * Only enable if you are prepared to implement `onBeforeNextPlaylistItem` on the JS side
179
+ * And resolve the promise with `resolveNextPlaylistItem` inside the callback
180
+ * Otherwise, the player will not proceed to the next item
181
+ */
182
+ playlistItemCallbackEnabled: PropTypes.bool,
177
183
  backgroundAudioEnabled: PropTypes.bool,
178
184
  category: PropTypes.oneOf([
179
185
  'Ambient',
@@ -370,7 +376,20 @@ export default class JWPlayer extends Component {
370
376
  getCurrentAudioTrack: PropTypes.func,
371
377
  setCurrentAudioTrack: PropTypes.func,
372
378
  setCurrentCaptions: PropTypes.func,
379
+ getCurrentCaptions: PropTypes.func,
380
+ onCaptionsChanged: PropTypes.func,
381
+ onCaptionsList: PropTypes.func,
373
382
  onAudioTracks: PropTypes.func,
383
+ /**
384
+ * Callback that is fired when the player is about to play the next playlist item.
385
+ * Indented to be paired with `playlistItemCallbackEnabled` prop
386
+ *
387
+ * Android will only fire after index 0 (starting at index 1)
388
+ *
389
+ * iOS will fire all playlist items (including index 0)
390
+ */
391
+ onBeforeNextPlaylistItem: PropTypes.func,
392
+ resolveNextPlaylistItem: PropTypes.func
374
393
  };
375
394
 
376
395
  constructor(props) {
@@ -378,8 +397,7 @@ export default class JWPlayer extends Component {
378
397
 
379
398
  this._playerId = playerId++;
380
399
  this.ref_key = `${RCT_RNJWPLAYER_REF}-${this._playerId}`;
381
-
382
- this.quite();
400
+
383
401
  }
384
402
 
385
403
  shouldComponentUpdate(nextProps, nextState) {
@@ -688,6 +706,37 @@ export default class JWPlayer extends Component {
688
706
  );
689
707
  }
690
708
  }
709
+
710
+ async getCurrentCaptions() {
711
+ if (RNJWPlayerManager) {
712
+ try {
713
+ var currentCaptionTrack =
714
+ await RNJWPlayerManager.getCurrentCaptions(
715
+ this.getRNJWPlayerBridgeHandle()
716
+ );
717
+ return currentCaptionTrack;
718
+ } catch (e) {
719
+ console.error(e);
720
+ return null;
721
+ }
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Only to be called in the onBeforeNextPlaylistItem callback.
727
+ * If called outside of the callback, it will not have any effect.
728
+ *
729
+ * Can only be called once inside the onBeforeNextPlaylistItem callback.
730
+ * @param {PlaylistItem | JwPlaylistItem} playlistItem
731
+ */
732
+ resolveNextPlaylistItem(playlistItem) {
733
+ if (RNJWPlayerManager) {
734
+ RNJWPlayerManager.resolveNextPlaylistItem(
735
+ this.getRNJWPlayerBridgeHandle(),
736
+ playlistItem
737
+ );
738
+ }
739
+ }
691
740
 
692
741
  getRNJWPlayerBridgeHandle() {
693
742
  return findNodeHandle(this[this.ref_key]);
@@ -41,6 +41,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
41
41
  var castController: JWCastController!
42
42
  var isCasting: Bool = false
43
43
  var availableDevices: [AnyObject]!
44
+ var onBeforeNextPlaylistItemCompletion: ((JWPlayerItem?) -> ())?
44
45
 
45
46
  @objc var onBuffer: RCTDirectEventBlock?
46
47
  @objc var onUpdateBuffer: RCTDirectEventBlock?
@@ -85,6 +86,9 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
85
86
  @objc var onCasting: RCTDirectEventBlock?
86
87
  @objc var onCastingEnded: RCTDirectEventBlock?
87
88
  @objc var onCastingFailed: RCTDirectEventBlock?
89
+ @objc var onCaptionsChanged: RCTDirectEventBlock?
90
+ @objc var onCaptionsList: RCTDirectEventBlock?
91
+ @objc var onBeforeNextPlaylistItem: RCTDirectEventBlock?
88
92
 
89
93
  init() {
90
94
  super.init(frame: CGRect(x: 20, y: 0, width: UIScreen.main.bounds.width - 40, height: 300))
@@ -281,6 +285,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
281
285
 
282
286
  func setNewConfig(config: [String : Any]) {
283
287
  let forceLegacyConfig = config["forceLegacyConfig"] as? Bool?
288
+ let playlistItemCallback = config["playlistItemCallbackEnabled"] as? Bool?
284
289
  let data:Data! = try? JSONSerialization.data(withJSONObject: config, options:.prettyPrinted)
285
290
  let jwConfig = try? JWJSONParser.config(from:data)
286
291
 
@@ -299,7 +304,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
299
304
  }
300
305
 
301
306
  if backgroundAudioEnabled || pipEnabled {
302
- let category = config["category"] as? String
307
+ let category = config["category"] != nil ? config["category"] as? String : "playback" // default category for playback
303
308
  let categoryOptions = config["categoryOptions"] as? [String]
304
309
  let mode = config["mode"] as? String
305
310
 
@@ -346,6 +351,11 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
346
351
  self.setupPlayerViewController(config: config, playerConfig: jwConfig!)
347
352
  }
348
353
  }
354
+
355
+ if playlistItemCallback == true {
356
+ self.setupPlaylistItemCallback()
357
+ }
358
+
349
359
  } catch {
350
360
  print(error)
351
361
  }
@@ -360,6 +370,47 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
360
370
  }
361
371
  }
362
372
 
373
+ func setupPlaylistItemCallback() {
374
+ playerViewController.player.setPlaylistItemCallback { [weak self] item, index, completion in
375
+ print("setPlaylistItemCallback called with index \(index)")
376
+ guard let self = self else {
377
+ print("setPlaylistItemCallback: self is nil, resuming normally")
378
+ completion(item)
379
+ return
380
+ }
381
+
382
+ if let onBeforeNextPlaylistItem = self.onBeforeNextPlaylistItem {
383
+ print("Storing completion handler and triggering onBeforeNextPlaylistItem")
384
+ // Store the completion handler first, before any other operations
385
+ self.onBeforeNextPlaylistItemCompletion = completion
386
+ print("Completion handler stored: \(self.onBeforeNextPlaylistItemCompletion != nil)")
387
+
388
+ do {
389
+ let data = try JSONSerialization.data(withJSONObject: item.toJSONObject(), options: [.prettyPrinted])
390
+ let jsonString = String(data: data, encoding: .utf8) ?? "{}"
391
+
392
+ print("Triggering onBeforeNextPlaylistItem with index \(index)")
393
+ print("Completion handler before event: \(self.onBeforeNextPlaylistItemCompletion != nil)")
394
+
395
+ // Pass the playlist item to the React Native side
396
+ onBeforeNextPlaylistItem([
397
+ "playlistItem": jsonString,
398
+ "index": index
399
+ ])
400
+ print("Completion handler after event: \(self.onBeforeNextPlaylistItemCompletion != nil)")
401
+
402
+ } catch {
403
+ print("Error serializing playlist item: \(error)")
404
+ self.onBeforeNextPlaylistItemCompletion?(item) // Call completion handler directly on error
405
+ self.onBeforeNextPlaylistItemCompletion = nil
406
+ }
407
+ } else {
408
+ print("No onBeforeNextPlaylistItem handler set, calling completion directly")
409
+ completion(item)
410
+ }
411
+ }
412
+ }
413
+
363
414
  // MARK: - RNJWPlayer styling
364
415
 
365
416
  func colorWithHexString(hex:String!) -> UIColor! {
@@ -960,12 +1011,12 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
960
1011
  }
961
1012
 
962
1013
  func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) {
963
- self.onPlayerError?(["error": message])
1014
+ self.onPlayerError?(["error": message, "errorCode": code])
964
1015
  playerFailed = true
965
1016
  }
966
1017
 
967
1018
  func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) {
968
- self.onSetupPlayerError?(["error": message])
1019
+ self.onSetupPlayerError?(["errorMessage": message, "errorCode": code])
969
1020
  playerFailed = true
970
1021
  }
971
1022
 
@@ -979,7 +1030,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
979
1030
 
980
1031
 
981
1032
  func jwplayer(_ player:JWPlayer, encounteredAdWarning code:UInt, message:String) {
982
- self.onPlayerAdWarning?(["warning": message])
1033
+ self.onPlayerAdWarning?(["warning": message, "code": code])
983
1034
  }
984
1035
 
985
1036
 
@@ -1179,10 +1230,6 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
1179
1230
 
1180
1231
  // MARK: - JWPlayer State Delegate
1181
1232
 
1182
- func jwplayerContentIsBuffering(_ player:JWPlayer) {
1183
- self.onBuffer?([:])
1184
- }
1185
-
1186
1233
  func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) {
1187
1234
  self.onBuffer?([:])
1188
1235
  }
@@ -1233,6 +1280,10 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
1233
1280
  func jwplayerContentDidComplete(_ player:JWPlayer) {
1234
1281
  self.onComplete?([:])
1235
1282
  }
1283
+
1284
+ func jwplayerContentIsBuffering(_ player: any JWPlayerKit.JWPlayer) {
1285
+
1286
+ }
1236
1287
 
1237
1288
  func jwplayer(_ player:JWPlayer, didLoadPlaylistItem item:JWPlayerItem, at index:UInt) {
1238
1289
  // var sourceDict: [String: Any] = [:]
@@ -1382,7 +1433,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
1382
1433
  // MARK: - JWPlayer Ad Delegate
1383
1434
 
1384
1435
  func jwplayer(_ player:JWPlayer, adEvent event:JWAdEvent) {
1385
- self.onAdEvent?(["client": event.client, "type": event.type])
1436
+ self.onAdEvent?(["client": event.client.rawValue, "type": event.type.rawValue])
1386
1437
  }
1387
1438
 
1388
1439
  // MARK: - JWPlayer AV Delegate
@@ -1400,7 +1451,7 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
1400
1451
  }
1401
1452
 
1402
1453
  func jwplayer(_ player:JWPlayer, captionTrackChanged index:Int) {
1403
-
1454
+ self.onCaptionsChanged?(["index": index])
1404
1455
  }
1405
1456
 
1406
1457
  func jwplayer(_ player: JWPlayer, visualQualityChanged currentVisualQuality: JWVisualQuality) {
@@ -1416,7 +1467,15 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
1416
1467
  }
1417
1468
 
1418
1469
  func jwplayer(_ player:JWPlayer, updatedCaptionList options:[JWMediaSelectionOption]) {
1419
-
1470
+ var tracks: [[String: Any]] = []
1471
+ for track in player.captionsTracks {
1472
+ var dict: [String: Any] = [:]
1473
+ dict["label"] = track.name
1474
+ dict["default"] = track.defaultOption
1475
+ tracks.append(dict)
1476
+ }
1477
+ let currentIndex = player.currentCaptionsTrack
1478
+ self.onCaptionsList?(["index": currentIndex, "tracks": tracks])
1420
1479
  }
1421
1480
 
1422
1481
  // MARK: - JWPlayer audio session && interruption handling
@@ -1518,29 +1577,32 @@ class RNJWPlayerView : UIView, JWPlayerDelegate, JWPlayerStateDelegate, JWAdDele
1518
1577
  }
1519
1578
 
1520
1579
  var options: AVAudioSession.CategoryOptions = []
1521
- if categoryOptions.contains("MixWithOthers") {
1522
- options.insert(.mixWithOthers)
1523
- }
1524
- if categoryOptions.contains("DuckOthers") {
1525
- options.insert(.duckOthers)
1526
- }
1527
- if categoryOptions.contains("AllowBluetooth") {
1528
- options.insert(.allowBluetooth)
1529
- }
1530
- if categoryOptions.contains("InterruptSpokenAudioAndMix") {
1531
- options.insert(.interruptSpokenAudioAndMixWithOthers)
1532
- }
1533
- if categoryOptions.contains("AllowBluetoothA2DP") {
1534
- options.insert(.allowBluetoothA2DP)
1535
- }
1536
- if categoryOptions.contains("AllowAirPlay") {
1537
- options.insert(.allowAirPlay)
1538
- }
1539
- if categoryOptions.contains("OverrideMutedMicrophone") {
1540
- if #available(iOS 14.5, *) {
1541
- options.insert(.overrideMutedMicrophoneInterruption)
1542
- } else {
1543
- // Handle the case for earlier versions if needed
1580
+ // If the user doesn't specify any options
1581
+ if categoryOptions != nil {
1582
+ if categoryOptions.contains("MixWithOthers") {
1583
+ options.insert(.mixWithOthers)
1584
+ }
1585
+ if categoryOptions.contains("DuckOthers") {
1586
+ options.insert(.duckOthers)
1587
+ }
1588
+ if categoryOptions.contains("AllowBluetooth") {
1589
+ options.insert(.allowBluetooth)
1590
+ }
1591
+ if categoryOptions.contains("InterruptSpokenAudioAndMix") {
1592
+ options.insert(.interruptSpokenAudioAndMixWithOthers)
1593
+ }
1594
+ if categoryOptions.contains("AllowBluetoothA2DP") {
1595
+ options.insert(.allowBluetoothA2DP)
1596
+ }
1597
+ if categoryOptions.contains("AllowAirPlay") {
1598
+ options.insert(.allowAirPlay)
1599
+ }
1600
+ if categoryOptions.contains("OverrideMutedMicrophone") {
1601
+ if #available(iOS 14.5, *) {
1602
+ options.insert(.overrideMutedMicrophoneInterruption)
1603
+ } else {
1604
+ // Handle the case for earlier versions if needed
1605
+ }
1544
1606
  }
1545
1607
  }
1546
1608
 
@@ -55,13 +55,13 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD
55
55
 
56
56
  override func jwplayer(_ player:JWPlayer, failedWithError code:UInt, message:String) {
57
57
  super.jwplayer(player, failedWithError:code, message:message)
58
- parentView?.onPlayerError?(["error": message])
58
+ parentView?.onPlayerError?(["error": message, "errorCode": code])
59
59
  parentView?.playerFailed = true
60
60
  }
61
61
 
62
62
  override func jwplayer(_ player:JWPlayer, failedWithSetupError code:UInt, message:String) {
63
63
  super.jwplayer(player, failedWithSetupError:code, message:message)
64
- parentView?.onSetupPlayerError?(["error": message])
64
+ parentView?.onSetupPlayerError?(["errorMessage": message, "errorCode": code])
65
65
  parentView?.playerFailed = true
66
66
  }
67
67
 
@@ -78,7 +78,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD
78
78
 
79
79
  override func jwplayer(_ player:JWPlayer, encounteredAdWarning code:UInt, message:String) {
80
80
  super.jwplayer(player, encounteredAdWarning:code, message:message)
81
- parentView?.onPlayerAdWarning?(["warning": message])
81
+ parentView?.onPlayerAdWarning?(["warning": message, "code": code])
82
82
  }
83
83
 
84
84
 
@@ -263,11 +263,6 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD
263
263
 
264
264
  // MARK: - JWPlayer State Delegate
265
265
 
266
- override func jwplayerContentIsBuffering(_ player:JWPlayer) {
267
- super.jwplayerContentIsBuffering(player)
268
- parentView?.onBuffer?([:])
269
- }
270
-
271
266
  override func jwplayer(_ player:JWPlayer, isBufferingWithReason reason:JWBufferReason) {
272
267
  super.jwplayer(player, isBufferingWithReason:reason)
273
268
  parentView?.onBuffer?([:])
@@ -491,7 +486,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD
491
486
 
492
487
  override func jwplayer(_ player: JWPlayer, adEvent event: JWAdEvent) {
493
488
  super.jwplayer(player, adEvent:event)
494
- parentView?.onAdEvent?(["client": event.client, "type": event.type])
489
+ parentView?.onAdEvent?(["client": event.client.rawValue, "type": event.type.rawValue])
495
490
  }
496
491
 
497
492
  // MARK: - JWPlayer Cast Delegate
@@ -588,6 +583,7 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD
588
583
 
589
584
  override func jwplayer(_ player:JWPlayer, captionTrackChanged index:Int) {
590
585
  super.jwplayer(player, captionTrackChanged:index)
586
+ parentView.onCaptionsChanged?(["index": index])
591
587
  }
592
588
 
593
589
  override func jwplayer(_ player:JWPlayer, qualityLevelChanged currentLevel:Int) {
@@ -600,6 +596,16 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerD
600
596
 
601
597
  override func jwplayer(_ player:JWPlayer, updatedCaptionList options:[JWMediaSelectionOption]) {
602
598
  super.jwplayer(player, updatedCaptionList:options)
599
+
600
+ var tracks: [[String: Any]] = []
601
+ for track in player.captionsTracks {
602
+ var dict: [String: Any] = [:]
603
+ dict["label"] = track.name
604
+ dict["default"] = track.defaultOption
605
+ tracks.append(dict)
606
+ }
607
+ let currentIndex = player.currentCaptionsTrack
608
+ parentView.onCaptionsList?(["index": currentIndex, "tracks": tracks])
603
609
  }
604
610
 
605
611
  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {