@jwplayer/jwplayer-react-native 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -21
- package/RNJWPlayer.podspec +1 -1
- package/android/build.gradle +14 -1
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +19 -4
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +270 -105
- package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +13 -1
- package/badges/version.svg +1 -1
- package/docs/CONFIG-REFERENCE.md +747 -0
- package/docs/MIGRATION-GUIDE.md +617 -0
- package/docs/PLATFORM-DIFFERENCES.md +693 -0
- package/docs/props.md +15 -3
- package/index.d.ts +207 -249
- package/ios/RNJWPlayer/RNJWPlayerView.swift +278 -21
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +33 -16
- package/package.json +2 -2
- package/types/advertising.d.ts +514 -0
- package/types/index.d.ts +21 -0
- package/types/legacy.d.ts +82 -0
- package/types/platform-specific.d.ts +641 -0
- package/types/playlist.d.ts +410 -0
- package/types/unified-config.d.ts +591 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/docs/types.md +0 -254
|
@@ -21,6 +21,7 @@ import android.os.Handler;
|
|
|
21
21
|
import android.os.Looper;
|
|
22
22
|
import android.util.Log;
|
|
23
23
|
import android.view.View;
|
|
24
|
+
import android.view.View.MeasureSpec;
|
|
24
25
|
import android.view.ViewGroup;
|
|
25
26
|
import android.view.Window;
|
|
26
27
|
import android.view.WindowManager;
|
|
@@ -318,6 +319,17 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
318
319
|
getReactContext().addLifecycleEventListener(this);
|
|
319
320
|
}
|
|
320
321
|
|
|
322
|
+
@Override
|
|
323
|
+
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
324
|
+
super.onLayout(changed, l, t, r, b);
|
|
325
|
+
|
|
326
|
+
// Standard React Native layout handling
|
|
327
|
+
// Since we're no longer constantly swapping views, this is simpler
|
|
328
|
+
if (mPlayerView != null) {
|
|
329
|
+
mPlayerView.layout(0, 0, r - l, b - t);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
321
333
|
private LifecycleObserver lifecycleObserver = new LifecycleEventObserver() {
|
|
322
334
|
@Override
|
|
323
335
|
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
|
|
@@ -445,7 +457,13 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
445
457
|
);
|
|
446
458
|
|
|
447
459
|
mPlayer = null;
|
|
448
|
-
|
|
460
|
+
|
|
461
|
+
// Remove the old player view from the view hierarchy to prevent
|
|
462
|
+
// the old UI controls from receiving touch events (fixes GitHub issue #188 crash)
|
|
463
|
+
if (mPlayerView != null) {
|
|
464
|
+
removeView(mPlayerView);
|
|
465
|
+
mPlayerView = null;
|
|
466
|
+
}
|
|
449
467
|
|
|
450
468
|
getReactContext().removeLifecycleEventListener(this);
|
|
451
469
|
|
|
@@ -896,55 +914,174 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
896
914
|
}
|
|
897
915
|
}
|
|
898
916
|
|
|
917
|
+
/**
|
|
918
|
+
* Main entry point for setting/updating player configuration.
|
|
919
|
+
* Uses a smart approach: only recreate the player view when absolutely necessary,
|
|
920
|
+
* otherwise reconfigure the existing player instance.
|
|
921
|
+
*
|
|
922
|
+
* This follows JWPlayer SDK's intended usage pattern and significantly reduces overhead.
|
|
923
|
+
*/
|
|
899
924
|
public void setConfig(ReadableMap prop) {
|
|
900
925
|
if (mConfig == null || !mConfig.equals(prop)) {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
boolean wasFullscreen = mPlayer.getFullscreen();
|
|
905
|
-
UiConfig uiConfig = createUiConfigWithControlsContainer(mPlayer, oldConfig.getUiConfig());
|
|
906
|
-
PlayerConfig config = new PlayerConfig.Builder()
|
|
907
|
-
.autostart(oldConfig.getAutostart())
|
|
908
|
-
.nextUpOffset(oldConfig.getNextUpOffset())
|
|
909
|
-
.repeat(oldConfig.getRepeat())
|
|
910
|
-
.relatedConfig(oldConfig.getRelatedConfig())
|
|
911
|
-
.displayDescription(oldConfig.getDisplayDescription())
|
|
912
|
-
.displayTitle(oldConfig.getDisplayTitle())
|
|
913
|
-
.advertisingConfig(oldConfig.getAdvertisingConfig())
|
|
914
|
-
.stretching(oldConfig.getStretching())
|
|
915
|
-
.uiConfig(uiConfig)
|
|
916
|
-
.playlist(Util.createPlaylist(mPlaylistProp))
|
|
917
|
-
.allowCrossProtocolRedirects(oldConfig.getAllowCrossProtocolRedirects())
|
|
918
|
-
.preload(oldConfig.getPreload())
|
|
919
|
-
.useTextureView(oldConfig.useTextureView())
|
|
920
|
-
.thumbnailPreview(oldConfig.getThumbnailPreview())
|
|
921
|
-
.mute(oldConfig.getMute())
|
|
922
|
-
.build();
|
|
923
|
-
|
|
924
|
-
mPlayer.setup(config);
|
|
925
|
-
// if the player was fullscreen, set it to fullscreen again as the player is recreated
|
|
926
|
-
// The fullscreen view is still active but the internals don't know it is
|
|
927
|
-
if (wasFullscreen) {
|
|
928
|
-
mPlayer.setFullscreen(true, true);
|
|
929
|
-
}
|
|
926
|
+
// Set license key if provided
|
|
927
|
+
if (prop.hasKey("license")) {
|
|
928
|
+
new LicenseUtil().setLicenseKey(getReactContext(), prop.getString("license"));
|
|
930
929
|
} else {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
} else {
|
|
934
|
-
Log.e(TAG, "JW SDK license not set");
|
|
935
|
-
}
|
|
930
|
+
Log.e(TAG, "JW SDK license not set");
|
|
931
|
+
}
|
|
936
932
|
|
|
937
|
-
|
|
938
|
-
|
|
933
|
+
// First time setup - need to create player view
|
|
934
|
+
if (mPlayer == null) {
|
|
935
|
+
this.createPlayerView(prop);
|
|
936
|
+
mConfig = prop;
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Check if we need full player recreation (rare cases only)
|
|
941
|
+
if (requiresPlayerRecreation(prop)) {
|
|
942
|
+
Log.d(TAG, "Player recreation required - destroying and recreating player view");
|
|
943
|
+
this.destroyPlayer();
|
|
944
|
+
this.createPlayerView(prop);
|
|
945
|
+
} else {
|
|
946
|
+
// Normal case: reconfigure existing player without recreation
|
|
947
|
+
Log.d(TAG, "Reconfiguring existing player without recreation");
|
|
948
|
+
this.reconfigurePlayer(prop);
|
|
939
949
|
}
|
|
940
|
-
} else {
|
|
941
|
-
// No change
|
|
942
950
|
}
|
|
943
951
|
|
|
944
952
|
mConfig = prop;
|
|
945
953
|
}
|
|
946
954
|
|
|
955
|
+
/**
|
|
956
|
+
* Determines if a config change requires full player view recreation.
|
|
957
|
+
* Only return true for changes that genuinely cannot be handled by reconfiguration.
|
|
958
|
+
*
|
|
959
|
+
* Currently, the JWPlayer SDK can handle almost all config changes via setup(),
|
|
960
|
+
* so we only recreate for critical changes like license updates.
|
|
961
|
+
*/
|
|
962
|
+
private boolean requiresPlayerRecreation(ReadableMap prop) {
|
|
963
|
+
if (mConfig == null || mPlayer == null) {
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// License change requires recreation
|
|
968
|
+
if (prop.hasKey("license") && mConfig.hasKey("license")) {
|
|
969
|
+
String newLicense = prop.getString("license");
|
|
970
|
+
String oldLicense = mConfig.getString("license");
|
|
971
|
+
if (newLicense != null && !newLicense.equals(oldLicense)) {
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Add other cases here if needed in the future
|
|
977
|
+
// For example: switching between playerView and playerViewController modes
|
|
978
|
+
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Reconfigures the existing player instance with new settings.
|
|
984
|
+
* This is the preferred path for config updates as it preserves the player instance
|
|
985
|
+
* and video surface, following JWPlayer SDK's design intent.
|
|
986
|
+
*
|
|
987
|
+
* Based on the pattern used in loadPlaylist() and loadPlaylistWithUrl().
|
|
988
|
+
*/
|
|
989
|
+
private void reconfigurePlayer(ReadableMap prop) {
|
|
990
|
+
if (mPlayer == null) {
|
|
991
|
+
Log.e(TAG, "Cannot reconfigure - player is null");
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
PlayerConfig oldConfig = mPlayer.getConfig();
|
|
996
|
+
boolean wasFullscreen = mPlayer.getFullscreen();
|
|
997
|
+
boolean currentControlsState = mPlayer.getControls();
|
|
998
|
+
|
|
999
|
+
// Stop playback before reconfiguration to avoid issues (Issue #188 fix)
|
|
1000
|
+
mPlayer.stop();
|
|
1001
|
+
|
|
1002
|
+
// Build new configuration
|
|
1003
|
+
PlayerConfig newConfig = buildPlayerConfig(prop, oldConfig);
|
|
1004
|
+
|
|
1005
|
+
// ALWAYS ensure PLAYER_CONTROLS_CONTAINER is shown in UiConfig after setup.
|
|
1006
|
+
// This prevents issues where controls are off and JWPlayer SDK hides UI groups,
|
|
1007
|
+
// leaving them in a state where setControls(true) won't work.
|
|
1008
|
+
// We'll manage controls state via setControls() API after setup for clean state management.
|
|
1009
|
+
UiConfig fixedUiConfig = new UiConfig.Builder(newConfig.getUiConfig())
|
|
1010
|
+
.show(UiGroup.PLAYER_CONTROLS_CONTAINER)
|
|
1011
|
+
.build();
|
|
1012
|
+
newConfig = new PlayerConfig.Builder(newConfig)
|
|
1013
|
+
.uiConfig(fixedUiConfig)
|
|
1014
|
+
.build();
|
|
1015
|
+
|
|
1016
|
+
// Apply new configuration to existing player
|
|
1017
|
+
mPlayer.setup(newConfig);
|
|
1018
|
+
|
|
1019
|
+
// Now manage controls state via API (after setup, when UI groups are in clean state)
|
|
1020
|
+
if (prop.hasKey("controls")) {
|
|
1021
|
+
// Developer explicitly set controls in props - use that value
|
|
1022
|
+
mPlayer.setControls(prop.getBoolean("controls"));
|
|
1023
|
+
} else if (!currentControlsState) {
|
|
1024
|
+
// Controls were off before reconfigure and no explicit prop provided
|
|
1025
|
+
// Restore the off state (after ensuring UI groups are visible)
|
|
1026
|
+
mPlayer.setControls(false);
|
|
1027
|
+
}
|
|
1028
|
+
// Note: If controls were on and no prop provided, they'll stay on (default from configureUI)
|
|
1029
|
+
|
|
1030
|
+
// Restore fullscreen state if needed
|
|
1031
|
+
// The fullscreen view is still active but internals need to be notified
|
|
1032
|
+
if (wasFullscreen) {
|
|
1033
|
+
mPlayer.setFullscreen(true, true);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Builds a PlayerConfig from React Native props, preserving relevant old config values.
|
|
1039
|
+
* This ensures smooth transitions when reconfiguring the player.
|
|
1040
|
+
*/
|
|
1041
|
+
private PlayerConfig buildPlayerConfig(ReadableMap prop, PlayerConfig oldConfig) {
|
|
1042
|
+
PlayerConfig.Builder configBuilder = new PlayerConfig.Builder();
|
|
1043
|
+
|
|
1044
|
+
// Try to parse as JW config first
|
|
1045
|
+
JSONObject obj;
|
|
1046
|
+
PlayerConfig jwConfig = null;
|
|
1047
|
+
Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
|
|
1048
|
+
Boolean isJwConfig = false;
|
|
1049
|
+
|
|
1050
|
+
if (!forceLegacy) {
|
|
1051
|
+
try {
|
|
1052
|
+
obj = MapUtil.toJSONObject(prop);
|
|
1053
|
+
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
1054
|
+
isJwConfig = true;
|
|
1055
|
+
return jwConfig; // Return directly if valid JW config
|
|
1056
|
+
} catch (Exception ex) {
|
|
1057
|
+
Log.d(TAG, "Not a JW config format, using legacy builder");
|
|
1058
|
+
isJwConfig = false;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Legacy config building
|
|
1063
|
+
configurePlaylist(configBuilder, prop);
|
|
1064
|
+
configureBasicSettings(configBuilder, prop);
|
|
1065
|
+
configureStyling(configBuilder, prop);
|
|
1066
|
+
configureAdvertising(configBuilder, prop);
|
|
1067
|
+
configureUI(configBuilder, prop);
|
|
1068
|
+
|
|
1069
|
+
return configBuilder.build();
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Utility method to check if only a specific key differs between configs.
|
|
1074
|
+
* This is kept for potential future optimizations or debugging, but is no longer
|
|
1075
|
+
* used in the main setConfig flow since we now reconfigure the player for all changes.
|
|
1076
|
+
*
|
|
1077
|
+
* @deprecated Consider using reconfigurePlayer() for all config changes instead
|
|
1078
|
+
*/
|
|
1079
|
+
@Deprecated
|
|
947
1080
|
public boolean isOnlyDiff(ReadableMap prop, String keyName) {
|
|
1081
|
+
if (mConfig == null || prop == null) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
948
1085
|
// Convert ReadableMap to HashMap
|
|
949
1086
|
Map<String, Object> mConfigMap = mConfig.toHashMap();
|
|
950
1087
|
Map<String, Object> propMap = prop.toHashMap();
|
|
@@ -1052,12 +1189,23 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1052
1189
|
}
|
|
1053
1190
|
|
|
1054
1191
|
private void configureUI(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1192
|
+
// Handle controls property - default to true if not specified
|
|
1193
|
+
boolean controls = true; // Default to showing controls
|
|
1055
1194
|
if (prop.hasKey("controls")) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1195
|
+
controls = prop.getBoolean("controls");
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
if (!controls) {
|
|
1199
|
+
UiConfig uiConfig = new UiConfig.Builder().hideAllControls().build();
|
|
1200
|
+
configBuilder.uiConfig(uiConfig);
|
|
1201
|
+
} else {
|
|
1202
|
+
// Explicitly show controls and ensure controls container is visible
|
|
1203
|
+
// This ensures controls work even if setControls() is called later
|
|
1204
|
+
UiConfig uiConfig = new UiConfig.Builder()
|
|
1205
|
+
.displayAllControls()
|
|
1206
|
+
.show(UiGroup.PLAYER_CONTROLS_CONTAINER)
|
|
1207
|
+
.build();
|
|
1208
|
+
configBuilder.uiConfig(uiConfig);
|
|
1061
1209
|
}
|
|
1062
1210
|
|
|
1063
1211
|
if (prop.hasKey("hideUIGroups")) {
|
|
@@ -1076,7 +1224,13 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1076
1224
|
}
|
|
1077
1225
|
}
|
|
1078
1226
|
|
|
1079
|
-
|
|
1227
|
+
/**
|
|
1228
|
+
* Creates a new player view and initializes it with the provided configuration.
|
|
1229
|
+
* This should only be called for initial setup or when full recreation is required.
|
|
1230
|
+
*
|
|
1231
|
+
* Note: This method calls destroyPlayer() first to ensure clean state.
|
|
1232
|
+
*/
|
|
1233
|
+
private void createPlayerView(ReadableMap prop) {
|
|
1080
1234
|
PlayerConfig.Builder configBuilder = new PlayerConfig.Builder();
|
|
1081
1235
|
|
|
1082
1236
|
JSONObject obj;
|
|
@@ -1091,8 +1245,8 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1091
1245
|
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
1092
1246
|
isJwConfig = true;
|
|
1093
1247
|
} catch (Exception ex) {
|
|
1094
|
-
Log.e("
|
|
1095
|
-
isJwConfig = false;
|
|
1248
|
+
Log.e(TAG, "Not a valid JW config format, falling back to legacy: " + ex.toString());
|
|
1249
|
+
isJwConfig = false;
|
|
1096
1250
|
}
|
|
1097
1251
|
}
|
|
1098
1252
|
|
|
@@ -1106,26 +1260,30 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1106
1260
|
|
|
1107
1261
|
Context simpleContext = getNonBuggyContext(getReactContext(), getAppContext());
|
|
1108
1262
|
|
|
1263
|
+
// Ensure clean state before creating new player view
|
|
1109
1264
|
this.destroyPlayer();
|
|
1110
1265
|
|
|
1266
|
+
// Create new player view
|
|
1111
1267
|
mPlayerView = new RNJWPlayer(simpleContext);
|
|
1112
|
-
|
|
1113
1268
|
mPlayerView.setFocusable(true);
|
|
1114
1269
|
mPlayerView.setFocusableInTouchMode(true);
|
|
1115
1270
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1271
|
+
// Set layout parameters
|
|
1272
|
+
setLayoutParams(new ViewGroup.LayoutParams(
|
|
1273
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1274
|
+
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
1118
1275
|
mPlayerView.setLayoutParams(new LinearLayout.LayoutParams(
|
|
1119
1276
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
1120
1277
|
LinearLayout.LayoutParams.MATCH_PARENT));
|
|
1278
|
+
|
|
1279
|
+
// Add to view hierarchy - React Native will handle layout
|
|
1121
1280
|
addView(mPlayerView);
|
|
1122
1281
|
|
|
1123
|
-
//
|
|
1124
|
-
registry.setCurrentState(registry.getCurrentState()); // This is a hack to ensure player and view know the lifecycle state
|
|
1125
|
-
|
|
1282
|
+
// Get player instance
|
|
1126
1283
|
mPlayer = mPlayerView.getPlayer(this);
|
|
1127
1284
|
|
|
1128
|
-
|
|
1285
|
+
// Apply view-specific props
|
|
1286
|
+
if (prop.hasKey("controls")) {
|
|
1129
1287
|
mPlayerView.getPlayer().setControls(prop.getBoolean("controls"));
|
|
1130
1288
|
}
|
|
1131
1289
|
|
|
@@ -1139,7 +1297,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1139
1297
|
}
|
|
1140
1298
|
|
|
1141
1299
|
if (prop.hasKey("portraitOnExitFullScreen")) {
|
|
1142
|
-
portraitOnExitFullScreen = prop.getBoolean("
|
|
1300
|
+
portraitOnExitFullScreen = prop.getBoolean("portraitOnExitFullScreen");
|
|
1143
1301
|
}
|
|
1144
1302
|
|
|
1145
1303
|
if (prop.hasKey("playerInModal")) {
|
|
@@ -1151,6 +1309,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1151
1309
|
mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
|
|
1152
1310
|
}
|
|
1153
1311
|
|
|
1312
|
+
// Setup player with config
|
|
1154
1313
|
if (isJwConfig) {
|
|
1155
1314
|
mPlayer.setup(jwConfig);
|
|
1156
1315
|
} else {
|
|
@@ -1158,6 +1317,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1158
1317
|
mPlayer.setup(playerConfig);
|
|
1159
1318
|
}
|
|
1160
1319
|
|
|
1320
|
+
// Configure PiP if enabled
|
|
1161
1321
|
if (mActivity != null && prop.hasKey("pipEnabled")) {
|
|
1162
1322
|
boolean pipEnabled = prop.getBoolean("pipEnabled");
|
|
1163
1323
|
if (pipEnabled) {
|
|
@@ -1169,56 +1329,12 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1169
1329
|
}
|
|
1170
1330
|
}
|
|
1171
1331
|
|
|
1172
|
-
// Legacy
|
|
1173
|
-
// This isn't the ideal way to do this on Android. All drawables/colors/themes
|
|
1174
|
-
// be
|
|
1175
|
-
|
|
1176
|
-
// color/drawable/theme, open an `Ask` issue.
|
|
1177
|
-
if (mColors != null) {
|
|
1178
|
-
if (mColors.hasKey("backgroundColor")) {
|
|
1179
|
-
mPlayerView.setBackgroundColor(Color.parseColor("#" + mColors.getString("backgroundColor")));
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (mColors.hasKey("buttons")) {
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
if (mColors.hasKey("timeslider")) {
|
|
1187
|
-
CueMarkerSeekbar seekBar = findViewById(com.longtailvideo.jwplayer.R.id.controlbar_seekbar);
|
|
1188
|
-
ReadableMap timeslider = mColors.getMap("timeslider");
|
|
1189
|
-
if (timeslider != null) {
|
|
1190
|
-
LayerDrawable progressDrawable = (LayerDrawable) seekBar.getProgressDrawable();
|
|
1191
|
-
|
|
1192
|
-
if (timeslider.hasKey("progress")) {
|
|
1193
|
-
// seekBar.getProgressDrawable().setColorFilter(Color.parseColor("#" +
|
|
1194
|
-
// timeslider.getString("progress")), PorterDuff.Mode.SRC_IN);
|
|
1195
|
-
Drawable processDrawable = progressDrawable.findDrawableByLayerId(android.R.id.progress);
|
|
1196
|
-
processDrawable.setColorFilter(Color.parseColor("#" + timeslider.getString("progress")),
|
|
1197
|
-
PorterDuff.Mode.SRC_IN);
|
|
1198
|
-
}
|
|
1332
|
+
// Legacy styling support
|
|
1333
|
+
// NOTE: This isn't the ideal way to do this on Android. All drawables/colors/themes should
|
|
1334
|
+
// be targeted using styling. See https://docs.jwplayer.com/players/docs/android-styling-guide
|
|
1335
|
+
applyLegacyStyling();
|
|
1199
1336
|
|
|
1200
|
-
|
|
1201
|
-
Drawable secondaryProgressDrawable = progressDrawable
|
|
1202
|
-
.findDrawableByLayerId(android.R.id.secondaryProgress);
|
|
1203
|
-
secondaryProgressDrawable.setColorFilter(Color.parseColor("#" + timeslider.getString("buffer")),
|
|
1204
|
-
PorterDuff.Mode.SRC_IN);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
if (timeslider.hasKey("rail")) {
|
|
1208
|
-
Drawable backgroundDrawable = progressDrawable.findDrawableByLayerId(android.R.id.background);
|
|
1209
|
-
backgroundDrawable.setColorFilter(Color.parseColor("#" + timeslider.getString("rail")),
|
|
1210
|
-
PorterDuff.Mode.SRC_IN);
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
if (timeslider.hasKey("thumb")) {
|
|
1214
|
-
seekBar.getThumb().setColorFilter(Color.parseColor("#" + timeslider.getString("thumb")),
|
|
1215
|
-
PorterDuff.Mode.SRC_IN);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// Needed to handle volume control
|
|
1337
|
+
// Setup audio
|
|
1222
1338
|
audioManager = (AudioManager) simpleContext.getSystemService(Context.AUDIO_SERVICE);
|
|
1223
1339
|
|
|
1224
1340
|
if (prop.hasKey("backgroundAudioEnabled")) {
|
|
@@ -1228,12 +1344,61 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1228
1344
|
setupPlayerView(backgroundAudioEnabled, playlistItemCallbackEnabled);
|
|
1229
1345
|
|
|
1230
1346
|
if (backgroundAudioEnabled) {
|
|
1231
|
-
audioManager = (AudioManager) simpleContext.getSystemService(Context.AUDIO_SERVICE);
|
|
1232
1347
|
mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
|
|
1233
1348
|
.build();
|
|
1234
1349
|
}
|
|
1235
1350
|
}
|
|
1236
1351
|
|
|
1352
|
+
/**
|
|
1353
|
+
* Applies legacy color/styling customizations.
|
|
1354
|
+
* Extracted to separate method for clarity.
|
|
1355
|
+
*/
|
|
1356
|
+
private void applyLegacyStyling() {
|
|
1357
|
+
if (mColors == null) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (mColors.hasKey("backgroundColor")) {
|
|
1362
|
+
mPlayerView.setBackgroundColor(Color.parseColor("#" + mColors.getString("backgroundColor")));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (mColors.hasKey("timeslider")) {
|
|
1366
|
+
CueMarkerSeekbar seekBar = findViewById(com.longtailvideo.jwplayer.R.id.controlbar_seekbar);
|
|
1367
|
+
ReadableMap timeslider = mColors.getMap("timeslider");
|
|
1368
|
+
if (timeslider != null && seekBar != null) {
|
|
1369
|
+
LayerDrawable progressDrawable = (LayerDrawable) seekBar.getProgressDrawable();
|
|
1370
|
+
|
|
1371
|
+
if (timeslider.hasKey("progress")) {
|
|
1372
|
+
Drawable processDrawable = progressDrawable.findDrawableByLayerId(android.R.id.progress);
|
|
1373
|
+
processDrawable.setColorFilter(
|
|
1374
|
+
Color.parseColor("#" + timeslider.getString("progress")),
|
|
1375
|
+
PorterDuff.Mode.SRC_IN);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (timeslider.hasKey("buffer")) {
|
|
1379
|
+
Drawable secondaryProgressDrawable = progressDrawable
|
|
1380
|
+
.findDrawableByLayerId(android.R.id.secondaryProgress);
|
|
1381
|
+
secondaryProgressDrawable.setColorFilter(
|
|
1382
|
+
Color.parseColor("#" + timeslider.getString("buffer")),
|
|
1383
|
+
PorterDuff.Mode.SRC_IN);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if (timeslider.hasKey("rail")) {
|
|
1387
|
+
Drawable backgroundDrawable = progressDrawable.findDrawableByLayerId(android.R.id.background);
|
|
1388
|
+
backgroundDrawable.setColorFilter(
|
|
1389
|
+
Color.parseColor("#" + timeslider.getString("rail")),
|
|
1390
|
+
PorterDuff.Mode.SRC_IN);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (timeslider.hasKey("thumb")) {
|
|
1394
|
+
seekBar.getThumb().setColorFilter(
|
|
1395
|
+
Color.parseColor("#" + timeslider.getString("thumb")),
|
|
1396
|
+
PorterDuff.Mode.SRC_IN);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1237
1402
|
// Audio Focus
|
|
1238
1403
|
|
|
1239
1404
|
public void requestAudioFocus() {
|
|
@@ -55,6 +55,14 @@ public class Util {
|
|
|
55
55
|
out.close();
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
// Check response code before reading
|
|
60
|
+
int responseCode = urlConnection.getResponseCode();
|
|
61
|
+
|
|
62
|
+
if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
63
|
+
throw new IOException("HTTP POST failed with code " + responseCode);
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
// Read and return the response body.
|
|
59
67
|
InputStream inputStream = urlConnection.getInputStream();
|
|
60
68
|
try {
|
|
@@ -62,6 +70,9 @@ public class Util {
|
|
|
62
70
|
} finally {
|
|
63
71
|
inputStream.close();
|
|
64
72
|
}
|
|
73
|
+
} catch (IOException e) {
|
|
74
|
+
Log.e("Util", "❌ [HTTP POST] Exception: " + e.getMessage(), e);
|
|
75
|
+
throw e;
|
|
65
76
|
} finally {
|
|
66
77
|
if (urlConnection != null) {
|
|
67
78
|
urlConnection.disconnect();
|
|
@@ -194,7 +205,8 @@ public class Util {
|
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
if (playlistItem.hasKey("authUrl")) {
|
|
197
|
-
|
|
208
|
+
String authUrl = playlistItem.getString("authUrl");
|
|
209
|
+
itemBuilder.mediaDrmCallback(new WidevineCallback(authUrl));
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
if (playlistItem.hasKey("adSchedule")) {
|
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.3.0"><title>version: 1.3.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.3.0</text><text x="695" y="140" transform="scale(.1)" fill="#fff" textLength="290">1.3.0</text></g></svg>
|