@jwplayer/jwplayer-react-native 1.1.3 → 1.2.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/RNJWPlayer.podspec +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +12 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +111 -107
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +16 -0
- package/badges/version.svg +1 -1
- package/index.d.ts +51 -0
- package/index.js +34 -0
- package/ios/RNJWPlayer/RNJWPlayerView.swift +99 -1
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +12 -0
- package/ios/RNJWPlayer/RNJWPlayerViewManager.m +2 -0
- package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +13 -0
- package/package.json +1 -1
package/RNJWPlayer.podspec
CHANGED
|
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
|
|
|
12
12
|
s.platform = :ios, "15.0"
|
|
13
13
|
s.source = { :git => "https://github.com/jwplayer/jwplayer-react-native.git", :tag => "v#{s.version}" }
|
|
14
14
|
s.source_files = "ios/RNJWPlayer/*.{h,m,swift}"
|
|
15
|
-
s.dependency 'JWPlayerKit', '4.23.
|
|
15
|
+
s.dependency 'JWPlayerKit', '4.23.2'
|
|
16
16
|
s.dependency 'React-Core'
|
|
17
17
|
s.static_framework = true
|
|
18
18
|
s.info_plist = {
|
package/android/build.gradle
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.jwplayer.rnjwplayer;
|
|
2
2
|
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
|
|
3
5
|
import android.os.Handler;
|
|
4
6
|
import android.os.Looper;
|
|
5
7
|
|
|
@@ -430,6 +432,16 @@ public class RNJWPlayerModule extends ReactContextBaseJavaModule {
|
|
|
430
432
|
});
|
|
431
433
|
}
|
|
432
434
|
|
|
435
|
+
@ReactMethod
|
|
436
|
+
/**
|
|
437
|
+
* Stub method for recreatePlayerWithConfig - this method is iOS only
|
|
438
|
+
* On Android, create a new player instance with the new configuration instead
|
|
439
|
+
*/
|
|
440
|
+
public void recreatePlayerWithConfig(final int reactTag, final ReadableMap config) {
|
|
441
|
+
// No-op on Android - this method is iOS only
|
|
442
|
+
Log.w("RNJWPlayer", "recreatePlayerWithConfig is not supported on Android. Create a new player instance with the new configuration instead.");
|
|
443
|
+
}
|
|
444
|
+
|
|
433
445
|
private int stateToInt(PlayerState playerState) {
|
|
434
446
|
switch (playerState) {
|
|
435
447
|
case IDLE:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.jwplayer.rnjwplayer;
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
import android.annotation.SuppressLint;
|
|
4
5
|
import android.app.Activity;
|
|
5
6
|
import android.app.ActivityManager;
|
|
6
7
|
import android.content.BroadcastReceiver;
|
|
@@ -372,9 +373,9 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
372
373
|
|
|
373
374
|
// If we are casting we need to break the cast session as there is no simple
|
|
374
375
|
// way to reconnect to an existing session if the player that created it is dead
|
|
375
|
-
|
|
376
|
+
|
|
376
377
|
// If this doesn't match your use case, using a single player object and load content
|
|
377
|
-
// into it rather than creating a new player for every piece of content.
|
|
378
|
+
// into it rather than creating a new player for every piece of content.
|
|
378
379
|
mPlayer.stop();
|
|
379
380
|
|
|
380
381
|
// send signal to JW SDK player is destroyed
|
|
@@ -537,7 +538,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
537
538
|
}
|
|
538
539
|
}
|
|
539
540
|
|
|
540
|
-
|
|
541
|
+
public void resolveNextPlaylistItem(ReadableMap playlistItem) {
|
|
541
542
|
if (itemUpdatePromise == null) {
|
|
542
543
|
return;
|
|
543
544
|
}
|
|
@@ -741,7 +742,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
741
742
|
public void run() {
|
|
742
743
|
// View may not have been removed properly (especially if returning from PiP)
|
|
743
744
|
mPlayerViewContainer.removeView(mPlayerView);
|
|
744
|
-
|
|
745
|
+
|
|
745
746
|
mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
|
|
746
747
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
747
748
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
@@ -856,6 +857,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
856
857
|
}
|
|
857
858
|
}
|
|
858
859
|
|
|
860
|
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
859
861
|
private void registerReceiver() {
|
|
860
862
|
mReceiver = new PipHandlerReceiver();
|
|
861
863
|
IntentFilter intentFilter = new IntentFilter("onPictureInPictureModeChanged");
|
|
@@ -974,130 +976,132 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
974
976
|
.deepEquals(new ReadableArray[]{mPlaylistProp}, new ReadableArray[]{prop.getArray("playlist")});
|
|
975
977
|
}
|
|
976
978
|
|
|
977
|
-
private void
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
obj = MapUtil.toJSONObject(prop);
|
|
989
|
-
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
990
|
-
isJwConfig = true;
|
|
991
|
-
} catch (Exception ex) {
|
|
992
|
-
Log.e("RNJWPlayerView", ex.toString());
|
|
993
|
-
isJwConfig = false; // not a valid jw config. Try to setup in legacy
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
if (!isJwConfig) {
|
|
998
|
-
// Legacy
|
|
999
|
-
if (playlistNotTheSame(prop)) {
|
|
1000
|
-
List<PlaylistItem> playlist = new ArrayList<>();
|
|
1001
|
-
mPlaylistProp = prop.getArray("playlist");
|
|
1002
|
-
if (mPlaylistProp != null && mPlaylistProp.size() > 0) {
|
|
1003
|
-
|
|
1004
|
-
int j = 0;
|
|
1005
|
-
while (mPlaylistProp.size() > j) {
|
|
1006
|
-
ReadableMap playlistItem = mPlaylistProp.getMap(j);
|
|
1007
|
-
|
|
1008
|
-
PlaylistItem newPlayListItem = Util.getPlaylistItem((playlistItem));
|
|
1009
|
-
playlist.add(newPlayListItem);
|
|
1010
|
-
j++;
|
|
1011
|
-
}
|
|
979
|
+
private void configurePlaylist(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
980
|
+
if (playlistNotTheSame(prop)) {
|
|
981
|
+
List<PlaylistItem> playlist = new ArrayList<>();
|
|
982
|
+
mPlaylistProp = prop.getArray("playlist");
|
|
983
|
+
if (mPlaylistProp != null && mPlaylistProp.size() > 0) {
|
|
984
|
+
int j = 0;
|
|
985
|
+
while (mPlaylistProp.size() > j) {
|
|
986
|
+
ReadableMap playlistItem = mPlaylistProp.getMap(j);
|
|
987
|
+
PlaylistItem newPlayListItem = Util.getPlaylistItem((playlistItem));
|
|
988
|
+
playlist.add(newPlayListItem);
|
|
989
|
+
j++;
|
|
1012
990
|
}
|
|
1013
|
-
|
|
1014
|
-
configBuilder.playlist(playlist);
|
|
1015
991
|
}
|
|
992
|
+
configBuilder.playlist(playlist);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
1016
995
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
996
|
+
private void configureBasicSettings(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
997
|
+
if (prop.hasKey("autostart")) {
|
|
998
|
+
boolean autostart = prop.getBoolean("autostart");
|
|
999
|
+
configBuilder.autostart(autostart);
|
|
1000
|
+
}
|
|
1022
1001
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
configBuilder.nextUpOffset(offsetSeconds).nextUpOffsetPercentage(offsetPercentage);
|
|
1031
|
-
}
|
|
1002
|
+
if (prop.hasKey("nextUpStyle")) {
|
|
1003
|
+
ReadableMap nextUpStyle = prop.getMap("nextUpStyle");
|
|
1004
|
+
if (nextUpStyle != null && nextUpStyle.hasKey("offsetSeconds")
|
|
1005
|
+
&& nextUpStyle.hasKey("offsetPercentage")) {
|
|
1006
|
+
int offsetSeconds = prop.getInt("offsetSeconds");
|
|
1007
|
+
int offsetPercentage = prop.getInt("offsetPercentage");
|
|
1008
|
+
configBuilder.nextUpOffset(offsetSeconds).nextUpOffsetPercentage(offsetPercentage);
|
|
1032
1009
|
}
|
|
1010
|
+
}
|
|
1033
1011
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}
|
|
1012
|
+
if (prop.hasKey("repeat")) {
|
|
1013
|
+
boolean repeat = prop.getBoolean("repeat");
|
|
1014
|
+
configBuilder.repeat(repeat);
|
|
1015
|
+
}
|
|
1039
1016
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
boolean displayDescription = styling.getBoolean("displayDescription");
|
|
1046
|
-
configBuilder.displayDescription(displayDescription);
|
|
1047
|
-
}
|
|
1017
|
+
if (prop.hasKey("stretching")) {
|
|
1018
|
+
String stretching = prop.getString("stretching");
|
|
1019
|
+
configBuilder.stretching(stretching);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1048
1022
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1023
|
+
private void configureStyling(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1024
|
+
if (prop.hasKey("styling")) {
|
|
1025
|
+
ReadableMap styling = prop.getMap("styling");
|
|
1026
|
+
if (styling != null) {
|
|
1027
|
+
if (styling.hasKey("displayDescription")) {
|
|
1028
|
+
boolean displayDescription = styling.getBoolean("displayDescription");
|
|
1029
|
+
configBuilder.displayDescription(displayDescription);
|
|
1030
|
+
}
|
|
1053
1031
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1032
|
+
if (styling.hasKey("displayTitle")) {
|
|
1033
|
+
boolean displayTitle = styling.getBoolean("displayTitle");
|
|
1034
|
+
configBuilder.displayTitle(displayTitle);
|
|
1057
1035
|
}
|
|
1058
|
-
}
|
|
1059
1036
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
ReadableMap ads = prop.getMap("advertising");
|
|
1063
|
-
AdvertisingConfig advertisingConfig = RNJWPlayerAds.getAdvertisingConfig(ads);
|
|
1064
|
-
if (advertisingConfig != null) {
|
|
1065
|
-
configBuilder.advertisingConfig(advertisingConfig);
|
|
1037
|
+
if (styling.hasKey("colors")) {
|
|
1038
|
+
mColors = styling.getMap("colors");
|
|
1066
1039
|
}
|
|
1067
1040
|
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1068
1043
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1044
|
+
private void configureAdvertising(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1045
|
+
if (prop.hasKey("advertising")) {
|
|
1046
|
+
ReadableMap ads = prop.getMap("advertising");
|
|
1047
|
+
AdvertisingConfig advertisingConfig = RNJWPlayerAds.getAdvertisingConfig(ads);
|
|
1048
|
+
if (advertisingConfig != null) {
|
|
1049
|
+
configBuilder.advertisingConfig(advertisingConfig);
|
|
1073
1050
|
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1074
1053
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
UiConfig uiConfig = new UiConfig.Builder().hideAllControls().build();
|
|
1082
|
-
configBuilder.uiConfig(uiConfig);
|
|
1083
|
-
}
|
|
1054
|
+
private void configureUI(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1055
|
+
if (prop.hasKey("controls")) {
|
|
1056
|
+
boolean controls = prop.getBoolean("controls");
|
|
1057
|
+
if (!controls) {
|
|
1058
|
+
UiConfig uiConfig = new UiConfig.Builder().hideAllControls().build();
|
|
1059
|
+
configBuilder.uiConfig(uiConfig);
|
|
1084
1060
|
}
|
|
1061
|
+
}
|
|
1085
1062
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
hideConfigBuilder.hide(uiGroup);
|
|
1095
|
-
}
|
|
1063
|
+
if (prop.hasKey("hideUIGroups")) {
|
|
1064
|
+
ReadableArray uiGroupsArray = prop.getArray("hideUIGroups");
|
|
1065
|
+
UiConfig.Builder hideConfigBuilder = new UiConfig.Builder().displayAllControls();
|
|
1066
|
+
for (int i = 0; i < uiGroupsArray.size(); i++) {
|
|
1067
|
+
if (uiGroupsArray.getType(i) == ReadableType.String) {
|
|
1068
|
+
UiGroup uiGroup = GROUP_TYPES.get(uiGroupsArray.getString(i));
|
|
1069
|
+
if (uiGroup != null) {
|
|
1070
|
+
hideConfigBuilder.hide(uiGroup);
|
|
1096
1071
|
}
|
|
1097
1072
|
}
|
|
1098
|
-
UiConfig hideJwControlbarUiConfig = hideConfigBuilder.build();
|
|
1099
|
-
configBuilder.uiConfig(hideJwControlbarUiConfig);
|
|
1100
1073
|
}
|
|
1074
|
+
UiConfig hideJwControlbarUiConfig = hideConfigBuilder.build();
|
|
1075
|
+
configBuilder.uiConfig(hideJwControlbarUiConfig);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
private void setupPlayer(ReadableMap prop) {
|
|
1080
|
+
PlayerConfig.Builder configBuilder = new PlayerConfig.Builder();
|
|
1081
|
+
|
|
1082
|
+
JSONObject obj;
|
|
1083
|
+
PlayerConfig jwConfig = null;
|
|
1084
|
+
Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
|
|
1085
|
+
Boolean playlistItemCallbackEnabled = prop.hasKey("playlistItemCallbackEnabled") ? prop.getBoolean("playlistItemCallbackEnabled") : false;
|
|
1086
|
+
Boolean isJwConfig = false;
|
|
1087
|
+
|
|
1088
|
+
if (!forceLegacy) {
|
|
1089
|
+
try {
|
|
1090
|
+
obj = MapUtil.toJSONObject(prop);
|
|
1091
|
+
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
1092
|
+
isJwConfig = true;
|
|
1093
|
+
} catch (Exception ex) {
|
|
1094
|
+
Log.e("RNJWPlayerView", ex.toString());
|
|
1095
|
+
isJwConfig = false; // not a valid jw config. Try to setup in legacy
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
if (!isJwConfig) {
|
|
1100
|
+
configurePlaylist(configBuilder, prop);
|
|
1101
|
+
configureBasicSettings(configBuilder, prop);
|
|
1102
|
+
configureStyling(configBuilder, prop);
|
|
1103
|
+
configureAdvertising(configBuilder, prop);
|
|
1104
|
+
configureUI(configBuilder, prop);
|
|
1101
1105
|
}
|
|
1102
1106
|
|
|
1103
1107
|
Context simpleContext = getNonBuggyContext(getReactContext(), getAppContext());
|
|
@@ -46,6 +46,22 @@ public class RNJWPlayerViewManager extends SimpleViewManager<RNJWPlayerView> {
|
|
|
46
46
|
view.mPlayerView.getPlayer().setControls(controls);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Recreates the player with a new configuration, handling cleanup and PiP state.
|
|
51
|
+
* This method ensures proper cleanup and state restoration during configuration changes.
|
|
52
|
+
*
|
|
53
|
+
* @param view The RNJWPlayerView instance
|
|
54
|
+
* @param config The new configuration to apply
|
|
55
|
+
*/
|
|
56
|
+
@ReactProp(name = "recreatePlayerWithConfig")
|
|
57
|
+
public void recreatePlayerWithConfig(RNJWPlayerView view, ReadableMap config) {
|
|
58
|
+
if (view == null || view.mPlayerView == null) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
view.mPlayerView.getPlayer().stop();
|
|
62
|
+
view.setConfig(config);
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
public Map getExportedCustomBubblingEventTypeConstants() {
|
|
50
66
|
return MapBuilder.builder()
|
|
51
67
|
.put(
|
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.
|
|
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.2.0"><title>version: 1.2.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.2.0</text><text x="695" y="140" transform="scale(.1)" fill="#fff" textLength="290">1.2.0</text></g></svg>
|
package/index.d.ts
CHANGED
|
@@ -685,6 +685,57 @@ declare module "@jwplayer/jwplayer-react-native" {
|
|
|
685
685
|
setCurrentCaptions(index: number): void;
|
|
686
686
|
getCurrentCaptions(): Promise<number | null>;
|
|
687
687
|
setVisibility(visibility: boolean, controls: JWControlType[]): void;
|
|
688
|
+
/**
|
|
689
|
+
* Recreates the player with a new configuration, handling cleanup and PiP state.
|
|
690
|
+
*
|
|
691
|
+
* NOTE: This method is only available on iOS. On Android, create a new player instance
|
|
692
|
+
* with the new configuration instead.
|
|
693
|
+
*
|
|
694
|
+
* IMPORTANT: This method should only be called after the player has been properly
|
|
695
|
+
* initialized and is ready (i.e., after onPlayerReady has fired). Calling this
|
|
696
|
+
* method before the player is ready may lead to undefined behavior.
|
|
697
|
+
*
|
|
698
|
+
* This method performs a complete player recreation by:
|
|
699
|
+
* 1. Safely handling PiP state if active (waits for PiP to close)
|
|
700
|
+
* 2. Performing complete cleanup of the current player instance
|
|
701
|
+
* 3. Creating a new player instance with the provided config
|
|
702
|
+
*
|
|
703
|
+
* Use this method when you need to:
|
|
704
|
+
* - Switch between different DRM configurations
|
|
705
|
+
* - Handle content changes during PiP mode
|
|
706
|
+
* - Force a complete player recreation
|
|
707
|
+
*
|
|
708
|
+
* Do NOT use this method:
|
|
709
|
+
* - Before the player is ready (wait for onPlayerReady)
|
|
710
|
+
* - For simple playlist updates (use loadPlaylist instead)
|
|
711
|
+
* - When the player is not properly initialized
|
|
712
|
+
* - On Android (create a new player instance instead)
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* ```typescript
|
|
716
|
+
* // Wait for player to be ready
|
|
717
|
+
* onPlayerReady={() => {
|
|
718
|
+
* // Now safe to use recreatePlayerWithConfig (iOS only)
|
|
719
|
+
* if (Platform.OS === 'ios') {
|
|
720
|
+
* playerRef.current?.recreatePlayerWithConfig({
|
|
721
|
+
* ...config,
|
|
722
|
+
* playlist: newPlaylist
|
|
723
|
+
* });
|
|
724
|
+
* } else {
|
|
725
|
+
* // On Android, create a new player instance
|
|
726
|
+
* setPlayerConfig({
|
|
727
|
+
* ...config,
|
|
728
|
+
* playlist: newPlaylist
|
|
729
|
+
* });
|
|
730
|
+
* }
|
|
731
|
+
* }}
|
|
732
|
+
* ```
|
|
733
|
+
*
|
|
734
|
+
* @platform ios
|
|
735
|
+
* @param config The new configuration to apply to the recreated player
|
|
736
|
+
* @throws May throw if called before player is ready or with invalid config
|
|
737
|
+
*/
|
|
738
|
+
recreatePlayerWithConfig(config: Config | JwConfig): void;
|
|
688
739
|
/**
|
|
689
740
|
* Only called inside `onBeforeNextPlaylistItem` callback, and once per callback
|
|
690
741
|
* @param playlistItem `PlaylistItem` or `JwPlaylistItem`
|
package/index.js
CHANGED
|
@@ -744,6 +744,40 @@ export default class JWPlayer extends Component {
|
|
|
744
744
|
}
|
|
745
745
|
}
|
|
746
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Recreates the player with a new configuration, handling cleanup and PiP state.
|
|
749
|
+
*
|
|
750
|
+
* IMPORTANT: This method should only be called after the player has been properly
|
|
751
|
+
* initialized and is ready (i.e., after onPlayerReady has fired). Calling this
|
|
752
|
+
* method before the player is ready may lead to undefined behavior.
|
|
753
|
+
*
|
|
754
|
+
* This method:
|
|
755
|
+
* 1. Safely handles PiP state if active
|
|
756
|
+
* 2. Performs complete cleanup of the current player instance
|
|
757
|
+
* 3. Creates a new player instance with the provided config
|
|
758
|
+
*
|
|
759
|
+
* Use this method when you need to:
|
|
760
|
+
* - Switch between different DRM configurations
|
|
761
|
+
* - Handle content changes during PiP mode
|
|
762
|
+
* - Force a complete player recreation
|
|
763
|
+
*
|
|
764
|
+
* @param {Config | JwConfig} config The new configuration to apply to the recreated player
|
|
765
|
+
* @throws {Error} May throw if called before player is ready or with invalid config
|
|
766
|
+
*/
|
|
767
|
+
recreatePlayerWithConfig(config) {
|
|
768
|
+
if (!config) {
|
|
769
|
+
console.warn('JWPlayer: Attempting to recreate player with null/undefined config');
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (RNJWPlayerManager) {
|
|
774
|
+
RNJWPlayerManager.recreatePlayerWithConfig(
|
|
775
|
+
this.getRNJWPlayerBridgeHandle(),
|
|
776
|
+
config
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
747
781
|
getRNJWPlayerBridgeHandle() {
|
|
748
782
|
return findNodeHandle(this[this.ref_key]);
|
|
749
783
|
}
|
|
@@ -307,6 +307,98 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
private var pendingPlayerConfig: [String: Any]?
|
|
311
|
+
private var playerConfigTimeout: Timer?
|
|
312
|
+
private let maxPendingTime: TimeInterval = 5.0 // Maximum time to wait for PiP to close
|
|
313
|
+
|
|
314
|
+
@objc func recreatePlayerWithConfig(_ config: [String: Any]) {
|
|
315
|
+
// Cancel any existing pending configuration
|
|
316
|
+
if pendingPlayerConfig != nil {
|
|
317
|
+
print("Warning: Overriding pending content switch")
|
|
318
|
+
playerConfigTimeout?.invalidate()
|
|
319
|
+
pendingPlayerConfig = nil
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Validate config
|
|
323
|
+
guard !config.isEmpty else {
|
|
324
|
+
print("Error: Empty config provided to recreatePlayerWithConfig")
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 1. Handle PiP state
|
|
329
|
+
var isPipActive = false
|
|
330
|
+
var pipController: AVPictureInPictureController?
|
|
331
|
+
|
|
332
|
+
if let playerView = playerView {
|
|
333
|
+
pipController = playerView.pictureInPictureController
|
|
334
|
+
isPipActive = pipController?.isPictureInPictureActive ?? false
|
|
335
|
+
} else if let playerViewController = playerViewController {
|
|
336
|
+
pipController = playerViewController.playerView.pictureInPictureController
|
|
337
|
+
isPipActive = pipController?.isPictureInPictureActive ?? false
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 2. If in PiP, store the config and exit PiP
|
|
341
|
+
if isPipActive {
|
|
342
|
+
guard let pipController = pipController else {
|
|
343
|
+
print("Warning: PiP appears active but controller is nil, proceeding with direct switch")
|
|
344
|
+
completePlayerReconfiguration(config: config)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
pendingPlayerConfig = config
|
|
349
|
+
|
|
350
|
+
// Set a timeout to prevent infinite waiting
|
|
351
|
+
playerConfigTimeout = Timer.scheduledTimer(withTimeInterval: maxPendingTime, repeats: false) { [weak self] _ in
|
|
352
|
+
guard let self = self else { return }
|
|
353
|
+
print("Warning: PiP close timeout reached, forcing content switch")
|
|
354
|
+
if let pendingConfig = self.pendingPlayerConfig {
|
|
355
|
+
self.pendingPlayerConfig = nil
|
|
356
|
+
self.completePlayerReconfiguration(config: pendingConfig)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Attempt to stop PiP
|
|
361
|
+
pipController.stopPictureInPicture()
|
|
362
|
+
|
|
363
|
+
} else {
|
|
364
|
+
completePlayerReconfiguration(config: config)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private func completePlayerReconfiguration(config: [String: Any]) {
|
|
369
|
+
// Clear any pending timeout
|
|
370
|
+
playerConfigTimeout?.invalidate()
|
|
371
|
+
playerConfigTimeout = nil
|
|
372
|
+
|
|
373
|
+
// Ensure we're on the main thread
|
|
374
|
+
if !Thread.isMainThread {
|
|
375
|
+
DispatchQueue.main.async { [weak self] in
|
|
376
|
+
self?.completePlayerReconfiguration(config: config)
|
|
377
|
+
}
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 1. Stop current playback safely
|
|
382
|
+
if let playerView = playerView {
|
|
383
|
+
let state = playerView.player.getState()
|
|
384
|
+
if state == .playing || state == .buffering {
|
|
385
|
+
playerView.player.stop()
|
|
386
|
+
}
|
|
387
|
+
} else if let playerViewController = playerViewController {
|
|
388
|
+
let state = playerViewController.player.getState()
|
|
389
|
+
if state == .playing || state == .buffering {
|
|
390
|
+
playerViewController.player.stop()
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 2. Reset player state
|
|
395
|
+
dismissPlayerViewController()
|
|
396
|
+
removePlayerView()
|
|
397
|
+
|
|
398
|
+
// 3. Set new config
|
|
399
|
+
setNewConfig(config: config)
|
|
400
|
+
}
|
|
401
|
+
|
|
310
402
|
func setNewConfig(config: [String : Any]) {
|
|
311
403
|
let forceLegacyConfig = config["forceLegacyConfig"] as? Bool?
|
|
312
404
|
let playlistItemCallback = config["playlistItemCallbackEnabled"] as? Bool?
|
|
@@ -1230,7 +1322,13 @@ class RNJWPlayerView: UIView, JWPlayerDelegate, JWPlayerStateDelegate,
|
|
|
1230
1322
|
}
|
|
1231
1323
|
|
|
1232
1324
|
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
1233
|
-
|
|
1325
|
+
// Handle any pending content switch
|
|
1326
|
+
if let config = pendingPlayerConfig {
|
|
1327
|
+
pendingPlayerConfig = nil
|
|
1328
|
+
DispatchQueue.main.async { [weak self] in
|
|
1329
|
+
self?.completePlayerReconfiguration(config: config)
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1234
1332
|
}
|
|
1235
1333
|
|
|
1236
1334
|
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
@@ -249,26 +249,38 @@ class RNJWPlayerViewController : JWPlayerViewController, JWPlayerViewControllerF
|
|
|
249
249
|
|
|
250
250
|
override func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
251
251
|
super.pictureInPictureControllerDidStopPictureInPicture(pictureInPictureController)
|
|
252
|
+
// Forward to parent view to handle pending config changes
|
|
253
|
+
parentView?.pictureInPictureControllerDidStopPictureInPicture(pictureInPictureController)
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
override func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
255
257
|
super.pictureInPictureControllerDidStartPictureInPicture(pictureInPictureController)
|
|
258
|
+
// Forward to parent view for logging/tracking
|
|
259
|
+
parentView?.pictureInPictureControllerDidStartPictureInPicture(pictureInPictureController)
|
|
256
260
|
}
|
|
257
261
|
|
|
258
262
|
override func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
259
263
|
super.pictureInPictureControllerWillStopPictureInPicture(pictureInPictureController)
|
|
264
|
+
// Forward to parent view for logging/tracking
|
|
265
|
+
parentView?.pictureInPictureControllerWillStopPictureInPicture(pictureInPictureController)
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
override func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
|
|
263
269
|
super.pictureInPictureController(pictureInPictureController, failedToStartPictureInPictureWithError: error)
|
|
270
|
+
// Forward to parent view for error handling
|
|
271
|
+
parentView?.pictureInPictureController(pictureInPictureController, failedToStartPictureInPictureWithError: error)
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
override func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController:AVPictureInPictureController) {
|
|
267
275
|
super.pictureInPictureControllerWillStartPictureInPicture(pictureInPictureController)
|
|
276
|
+
// Forward to parent view for logging/tracking
|
|
277
|
+
parentView?.pictureInPictureControllerWillStartPictureInPicture(pictureInPictureController)
|
|
268
278
|
}
|
|
269
279
|
|
|
270
280
|
override func pictureInPictureController(_ pictureInPictureController:AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler:@escaping (Bool) -> Void) {
|
|
271
281
|
super.pictureInPictureController(pictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: completionHandler)
|
|
282
|
+
// Forward to parent view
|
|
283
|
+
parentView?.pictureInPictureController(pictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: completionHandler)
|
|
272
284
|
}
|
|
273
285
|
|
|
274
286
|
// MARK: - JWPlayer State Delegate
|
|
@@ -136,6 +136,8 @@ RCT_EXTERN_METHOD(reset)
|
|
|
136
136
|
|
|
137
137
|
RCT_EXTERN_METHOD(loadPlaylist: (nonnull NSNumber *)reactTag: (nonnull NSArray *)playlist)
|
|
138
138
|
|
|
139
|
+
RCT_EXTERN_METHOD(recreatePlayerWithConfig: (nonnull NSNumber *)reactTag: (nonnull NSDictionary *)config)
|
|
140
|
+
|
|
139
141
|
RCT_EXTERN_METHOD(loadPlaylistWithUrl: (nonnull NSNumber *)reactTag: (nonnull NSString *)playlist)
|
|
140
142
|
|
|
141
143
|
RCT_EXTERN_METHOD(setFullscreen: (nonnull NSNumber *)reactTag: (BOOL)fullscreen)
|
|
@@ -552,6 +552,19 @@ class RNJWPlayerViewManager: RCTViewManager {
|
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
@objc func recreatePlayerWithConfig(_ reactTag: NSNumber, _ config: NSDictionary) {
|
|
556
|
+
DispatchQueue.main.async {
|
|
557
|
+
guard let view = self.bridge?.uiManager.view(
|
|
558
|
+
forReactTag: reactTag
|
|
559
|
+
) as? RNJWPlayerView else {
|
|
560
|
+
print("Invalid view returned from registry, expecting RNJWPlayerView")
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
view.recreatePlayerWithConfig(config as? [String: Any] ?? [:])
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
555
568
|
@objc func loadPlaylistWithUrl(_ reactTag: NSNumber, _ playlistString: String) {
|
|
556
569
|
DispatchQueue.main.async {
|
|
557
570
|
guard let view = self.getPlayerView(reactTag: reactTag) else {
|