@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.
- package/README.md +1 -0
- package/RNJWPlayer.podspec +3 -3
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
- package/android/build.gradle +8 -1
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +368 -471
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +226 -56
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +15 -1
- package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +65 -9
- package/badges/version.svg +1 -1
- package/index.d.ts +47 -8
- package/index.js +51 -2
- package/ios/RNJWPlayer/RNJWPlayerView.swift +96 -34
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +15 -9
- package/ios/RNJWPlayer/RNJWPlayerViewManager.m +8 -1
- package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +244 -166
- package/jwplayer-jwplayer-react-native-v1.1.0.tgz +0 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
package/badges/version.svg
CHANGED
|
@@ -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
|
|
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
|
|
502
|
-
warning
|
|
520
|
+
code?: number;
|
|
521
|
+
warning?: string;
|
|
522
|
+
adErrorCode?: number; // Android only
|
|
503
523
|
}
|
|
504
524
|
interface AdEventProps {
|
|
505
|
-
client
|
|
525
|
+
client: number;
|
|
506
526
|
reason?: string;
|
|
507
527
|
type: number;
|
|
508
528
|
}
|
|
509
|
-
type
|
|
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?(["
|
|
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
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
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?(["
|
|
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) {
|