@jwplayer/jwplayer-react-native 1.1.3 → 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 +27 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +373 -204
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +16 -0
- 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 +225 -216
- package/index.js +34 -0
- package/ios/RNJWPlayer/RNJWPlayerView.swift +365 -10
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +45 -16
- package/ios/RNJWPlayer/RNJWPlayerViewManager.m +2 -0
- package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +13 -0
- 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
|
@@ -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;
|
|
@@ -20,6 +21,7 @@ import android.os.Handler;
|
|
|
20
21
|
import android.os.Looper;
|
|
21
22
|
import android.util.Log;
|
|
22
23
|
import android.view.View;
|
|
24
|
+
import android.view.View.MeasureSpec;
|
|
23
25
|
import android.view.ViewGroup;
|
|
24
26
|
import android.view.Window;
|
|
25
27
|
import android.view.WindowManager;
|
|
@@ -317,6 +319,17 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
317
319
|
getReactContext().addLifecycleEventListener(this);
|
|
318
320
|
}
|
|
319
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
|
+
|
|
320
333
|
private LifecycleObserver lifecycleObserver = new LifecycleEventObserver() {
|
|
321
334
|
@Override
|
|
322
335
|
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
|
|
@@ -372,9 +385,9 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
372
385
|
|
|
373
386
|
// If we are casting we need to break the cast session as there is no simple
|
|
374
387
|
// way to reconnect to an existing session if the player that created it is dead
|
|
375
|
-
|
|
388
|
+
|
|
376
389
|
// 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.
|
|
390
|
+
// into it rather than creating a new player for every piece of content.
|
|
378
391
|
mPlayer.stop();
|
|
379
392
|
|
|
380
393
|
// send signal to JW SDK player is destroyed
|
|
@@ -444,7 +457,13 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
444
457
|
);
|
|
445
458
|
|
|
446
459
|
mPlayer = null;
|
|
447
|
-
|
|
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
|
+
}
|
|
448
467
|
|
|
449
468
|
getReactContext().removeLifecycleEventListener(this);
|
|
450
469
|
|
|
@@ -537,7 +556,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
537
556
|
}
|
|
538
557
|
}
|
|
539
558
|
|
|
540
|
-
|
|
559
|
+
public void resolveNextPlaylistItem(ReadableMap playlistItem) {
|
|
541
560
|
if (itemUpdatePromise == null) {
|
|
542
561
|
return;
|
|
543
562
|
}
|
|
@@ -741,7 +760,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
741
760
|
public void run() {
|
|
742
761
|
// View may not have been removed properly (especially if returning from PiP)
|
|
743
762
|
mPlayerViewContainer.removeView(mPlayerView);
|
|
744
|
-
|
|
763
|
+
|
|
745
764
|
mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
|
|
746
765
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
747
766
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
@@ -856,6 +875,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
856
875
|
}
|
|
857
876
|
}
|
|
858
877
|
|
|
878
|
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
859
879
|
private void registerReceiver() {
|
|
860
880
|
mReceiver = new PipHandlerReceiver();
|
|
861
881
|
IntentFilter intentFilter = new IntentFilter("onPictureInPictureModeChanged");
|
|
@@ -894,55 +914,174 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
894
914
|
}
|
|
895
915
|
}
|
|
896
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
|
+
*/
|
|
897
924
|
public void setConfig(ReadableMap prop) {
|
|
898
925
|
if (mConfig == null || !mConfig.equals(prop)) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
boolean wasFullscreen = mPlayer.getFullscreen();
|
|
903
|
-
UiConfig uiConfig = createUiConfigWithControlsContainer(mPlayer, oldConfig.getUiConfig());
|
|
904
|
-
PlayerConfig config = new PlayerConfig.Builder()
|
|
905
|
-
.autostart(oldConfig.getAutostart())
|
|
906
|
-
.nextUpOffset(oldConfig.getNextUpOffset())
|
|
907
|
-
.repeat(oldConfig.getRepeat())
|
|
908
|
-
.relatedConfig(oldConfig.getRelatedConfig())
|
|
909
|
-
.displayDescription(oldConfig.getDisplayDescription())
|
|
910
|
-
.displayTitle(oldConfig.getDisplayTitle())
|
|
911
|
-
.advertisingConfig(oldConfig.getAdvertisingConfig())
|
|
912
|
-
.stretching(oldConfig.getStretching())
|
|
913
|
-
.uiConfig(uiConfig)
|
|
914
|
-
.playlist(Util.createPlaylist(mPlaylistProp))
|
|
915
|
-
.allowCrossProtocolRedirects(oldConfig.getAllowCrossProtocolRedirects())
|
|
916
|
-
.preload(oldConfig.getPreload())
|
|
917
|
-
.useTextureView(oldConfig.useTextureView())
|
|
918
|
-
.thumbnailPreview(oldConfig.getThumbnailPreview())
|
|
919
|
-
.mute(oldConfig.getMute())
|
|
920
|
-
.build();
|
|
921
|
-
|
|
922
|
-
mPlayer.setup(config);
|
|
923
|
-
// if the player was fullscreen, set it to fullscreen again as the player is recreated
|
|
924
|
-
// The fullscreen view is still active but the internals don't know it is
|
|
925
|
-
if (wasFullscreen) {
|
|
926
|
-
mPlayer.setFullscreen(true, true);
|
|
927
|
-
}
|
|
926
|
+
// Set license key if provided
|
|
927
|
+
if (prop.hasKey("license")) {
|
|
928
|
+
new LicenseUtil().setLicenseKey(getReactContext(), prop.getString("license"));
|
|
928
929
|
} else {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
} else {
|
|
932
|
-
Log.e(TAG, "JW SDK license not set");
|
|
933
|
-
}
|
|
930
|
+
Log.e(TAG, "JW SDK license not set");
|
|
931
|
+
}
|
|
934
932
|
|
|
935
|
-
|
|
936
|
-
|
|
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);
|
|
937
949
|
}
|
|
938
|
-
} else {
|
|
939
|
-
// No change
|
|
940
950
|
}
|
|
941
951
|
|
|
942
952
|
mConfig = prop;
|
|
943
953
|
}
|
|
944
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
|
|
945
1080
|
public boolean isOnlyDiff(ReadableMap prop, String keyName) {
|
|
1081
|
+
if (mConfig == null || prop == null) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
946
1085
|
// Convert ReadableMap to HashMap
|
|
947
1086
|
Map<String, Object> mConfigMap = mConfig.toHashMap();
|
|
948
1087
|
Map<String, Object> propMap = prop.toHashMap();
|
|
@@ -974,154 +1113,177 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
974
1113
|
.deepEquals(new ReadableArray[]{mPlaylistProp}, new ReadableArray[]{prop.getArray("playlist")});
|
|
975
1114
|
}
|
|
976
1115
|
|
|
977
|
-
private void
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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
|
|
1116
|
+
private void configurePlaylist(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1117
|
+
if (playlistNotTheSame(prop)) {
|
|
1118
|
+
List<PlaylistItem> playlist = new ArrayList<>();
|
|
1119
|
+
mPlaylistProp = prop.getArray("playlist");
|
|
1120
|
+
if (mPlaylistProp != null && mPlaylistProp.size() > 0) {
|
|
1121
|
+
int j = 0;
|
|
1122
|
+
while (mPlaylistProp.size() > j) {
|
|
1123
|
+
ReadableMap playlistItem = mPlaylistProp.getMap(j);
|
|
1124
|
+
PlaylistItem newPlayListItem = Util.getPlaylistItem((playlistItem));
|
|
1125
|
+
playlist.add(newPlayListItem);
|
|
1126
|
+
j++;
|
|
1127
|
+
}
|
|
994
1128
|
}
|
|
1129
|
+
configBuilder.playlist(playlist);
|
|
995
1130
|
}
|
|
1131
|
+
}
|
|
996
1132
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
-
}
|
|
1012
|
-
}
|
|
1133
|
+
private void configureBasicSettings(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1134
|
+
if (prop.hasKey("autostart")) {
|
|
1135
|
+
boolean autostart = prop.getBoolean("autostart");
|
|
1136
|
+
configBuilder.autostart(autostart);
|
|
1137
|
+
}
|
|
1013
1138
|
|
|
1014
|
-
|
|
1139
|
+
if (prop.hasKey("nextUpStyle")) {
|
|
1140
|
+
ReadableMap nextUpStyle = prop.getMap("nextUpStyle");
|
|
1141
|
+
if (nextUpStyle != null && nextUpStyle.hasKey("offsetSeconds")
|
|
1142
|
+
&& nextUpStyle.hasKey("offsetPercentage")) {
|
|
1143
|
+
int offsetSeconds = prop.getInt("offsetSeconds");
|
|
1144
|
+
int offsetPercentage = prop.getInt("offsetPercentage");
|
|
1145
|
+
configBuilder.nextUpOffset(offsetSeconds).nextUpOffsetPercentage(offsetPercentage);
|
|
1015
1146
|
}
|
|
1147
|
+
}
|
|
1016
1148
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1149
|
+
if (prop.hasKey("repeat")) {
|
|
1150
|
+
boolean repeat = prop.getBoolean("repeat");
|
|
1151
|
+
configBuilder.repeat(repeat);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (prop.hasKey("stretching")) {
|
|
1155
|
+
String stretching = prop.getString("stretching");
|
|
1156
|
+
configBuilder.stretching(stretching);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1022
1159
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1160
|
+
private void configureStyling(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1161
|
+
if (prop.hasKey("styling")) {
|
|
1162
|
+
ReadableMap styling = prop.getMap("styling");
|
|
1163
|
+
if (styling != null) {
|
|
1164
|
+
if (styling.hasKey("displayDescription")) {
|
|
1165
|
+
boolean displayDescription = styling.getBoolean("displayDescription");
|
|
1166
|
+
configBuilder.displayDescription(displayDescription);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if (styling.hasKey("displayTitle")) {
|
|
1170
|
+
boolean displayTitle = styling.getBoolean("displayTitle");
|
|
1171
|
+
configBuilder.displayTitle(displayTitle);
|
|
1031
1172
|
}
|
|
1032
|
-
}
|
|
1033
1173
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
configBuilder.repeat(repeat);
|
|
1174
|
+
if (styling.hasKey("colors")) {
|
|
1175
|
+
mColors = styling.getMap("colors");
|
|
1176
|
+
}
|
|
1038
1177
|
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1039
1180
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1181
|
+
private void configureAdvertising(PlayerConfig.Builder configBuilder, ReadableMap prop) {
|
|
1182
|
+
if (prop.hasKey("advertising")) {
|
|
1183
|
+
ReadableMap ads = prop.getMap("advertising");
|
|
1184
|
+
AdvertisingConfig advertisingConfig = RNJWPlayerAds.getAdvertisingConfig(ads);
|
|
1185
|
+
if (advertisingConfig != null) {
|
|
1186
|
+
configBuilder.advertisingConfig(advertisingConfig);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1048
1190
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
|
1194
|
+
if (prop.hasKey("controls")) {
|
|
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);
|
|
1209
|
+
}
|
|
1053
1210
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1211
|
+
if (prop.hasKey("hideUIGroups")) {
|
|
1212
|
+
ReadableArray uiGroupsArray = prop.getArray("hideUIGroups");
|
|
1213
|
+
UiConfig.Builder hideConfigBuilder = new UiConfig.Builder().displayAllControls();
|
|
1214
|
+
for (int i = 0; i < uiGroupsArray.size(); i++) {
|
|
1215
|
+
if (uiGroupsArray.getType(i) == ReadableType.String) {
|
|
1216
|
+
UiGroup uiGroup = GROUP_TYPES.get(uiGroupsArray.getString(i));
|
|
1217
|
+
if (uiGroup != null) {
|
|
1218
|
+
hideConfigBuilder.hide(uiGroup);
|
|
1056
1219
|
}
|
|
1057
1220
|
}
|
|
1058
1221
|
}
|
|
1222
|
+
UiConfig hideJwControlbarUiConfig = hideConfigBuilder.build();
|
|
1223
|
+
configBuilder.uiConfig(hideJwControlbarUiConfig);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1059
1226
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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) {
|
|
1234
|
+
PlayerConfig.Builder configBuilder = new PlayerConfig.Builder();
|
|
1068
1235
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1236
|
+
JSONObject obj;
|
|
1237
|
+
PlayerConfig jwConfig = null;
|
|
1238
|
+
Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
|
|
1239
|
+
Boolean playlistItemCallbackEnabled = prop.hasKey("playlistItemCallbackEnabled") ? prop.getBoolean("playlistItemCallbackEnabled") : false;
|
|
1240
|
+
Boolean isJwConfig = false;
|
|
1074
1241
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
}
|
|
1242
|
+
if (!forceLegacy) {
|
|
1243
|
+
try {
|
|
1244
|
+
obj = MapUtil.toJSONObject(prop);
|
|
1245
|
+
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
1246
|
+
isJwConfig = true;
|
|
1247
|
+
} catch (Exception ex) {
|
|
1248
|
+
Log.e(TAG, "Not a valid JW config format, falling back to legacy: " + ex.toString());
|
|
1249
|
+
isJwConfig = false;
|
|
1084
1250
|
}
|
|
1251
|
+
}
|
|
1085
1252
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
UiGroup uiGroup = GROUP_TYPES.get(uiGroupsArray.getString(i));
|
|
1093
|
-
if (uiGroup != null) {
|
|
1094
|
-
hideConfigBuilder.hide(uiGroup);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
UiConfig hideJwControlbarUiConfig = hideConfigBuilder.build();
|
|
1099
|
-
configBuilder.uiConfig(hideJwControlbarUiConfig);
|
|
1100
|
-
}
|
|
1253
|
+
if (!isJwConfig) {
|
|
1254
|
+
configurePlaylist(configBuilder, prop);
|
|
1255
|
+
configureBasicSettings(configBuilder, prop);
|
|
1256
|
+
configureStyling(configBuilder, prop);
|
|
1257
|
+
configureAdvertising(configBuilder, prop);
|
|
1258
|
+
configureUI(configBuilder, prop);
|
|
1101
1259
|
}
|
|
1102
1260
|
|
|
1103
1261
|
Context simpleContext = getNonBuggyContext(getReactContext(), getAppContext());
|
|
1104
1262
|
|
|
1263
|
+
// Ensure clean state before creating new player view
|
|
1105
1264
|
this.destroyPlayer();
|
|
1106
1265
|
|
|
1266
|
+
// Create new player view
|
|
1107
1267
|
mPlayerView = new RNJWPlayer(simpleContext);
|
|
1108
|
-
|
|
1109
1268
|
mPlayerView.setFocusable(true);
|
|
1110
1269
|
mPlayerView.setFocusableInTouchMode(true);
|
|
1111
1270
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1271
|
+
// Set layout parameters
|
|
1272
|
+
setLayoutParams(new ViewGroup.LayoutParams(
|
|
1273
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1274
|
+
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
1114
1275
|
mPlayerView.setLayoutParams(new LinearLayout.LayoutParams(
|
|
1115
1276
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
1116
1277
|
LinearLayout.LayoutParams.MATCH_PARENT));
|
|
1278
|
+
|
|
1279
|
+
// Add to view hierarchy - React Native will handle layout
|
|
1117
1280
|
addView(mPlayerView);
|
|
1118
1281
|
|
|
1119
|
-
//
|
|
1120
|
-
registry.setCurrentState(registry.getCurrentState()); // This is a hack to ensure player and view know the lifecycle state
|
|
1121
|
-
|
|
1282
|
+
// Get player instance
|
|
1122
1283
|
mPlayer = mPlayerView.getPlayer(this);
|
|
1123
1284
|
|
|
1124
|
-
|
|
1285
|
+
// Apply view-specific props
|
|
1286
|
+
if (prop.hasKey("controls")) {
|
|
1125
1287
|
mPlayerView.getPlayer().setControls(prop.getBoolean("controls"));
|
|
1126
1288
|
}
|
|
1127
1289
|
|
|
@@ -1135,7 +1297,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1135
1297
|
}
|
|
1136
1298
|
|
|
1137
1299
|
if (prop.hasKey("portraitOnExitFullScreen")) {
|
|
1138
|
-
portraitOnExitFullScreen = prop.getBoolean("
|
|
1300
|
+
portraitOnExitFullScreen = prop.getBoolean("portraitOnExitFullScreen");
|
|
1139
1301
|
}
|
|
1140
1302
|
|
|
1141
1303
|
if (prop.hasKey("playerInModal")) {
|
|
@@ -1147,6 +1309,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1147
1309
|
mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
|
|
1148
1310
|
}
|
|
1149
1311
|
|
|
1312
|
+
// Setup player with config
|
|
1150
1313
|
if (isJwConfig) {
|
|
1151
1314
|
mPlayer.setup(jwConfig);
|
|
1152
1315
|
} else {
|
|
@@ -1154,6 +1317,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1154
1317
|
mPlayer.setup(playerConfig);
|
|
1155
1318
|
}
|
|
1156
1319
|
|
|
1320
|
+
// Configure PiP if enabled
|
|
1157
1321
|
if (mActivity != null && prop.hasKey("pipEnabled")) {
|
|
1158
1322
|
boolean pipEnabled = prop.getBoolean("pipEnabled");
|
|
1159
1323
|
if (pipEnabled) {
|
|
@@ -1165,56 +1329,12 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1165
1329
|
}
|
|
1166
1330
|
}
|
|
1167
1331
|
|
|
1168
|
-
// Legacy
|
|
1169
|
-
// This isn't the ideal way to do this on Android. All drawables/colors/themes
|
|
1170
|
-
// be
|
|
1171
|
-
|
|
1172
|
-
// color/drawable/theme, open an `Ask` issue.
|
|
1173
|
-
if (mColors != null) {
|
|
1174
|
-
if (mColors.hasKey("backgroundColor")) {
|
|
1175
|
-
mPlayerView.setBackgroundColor(Color.parseColor("#" + mColors.getString("backgroundColor")));
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
if (mColors.hasKey("buttons")) {
|
|
1179
|
-
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (mColors.hasKey("timeslider")) {
|
|
1183
|
-
CueMarkerSeekbar seekBar = findViewById(com.longtailvideo.jwplayer.R.id.controlbar_seekbar);
|
|
1184
|
-
ReadableMap timeslider = mColors.getMap("timeslider");
|
|
1185
|
-
if (timeslider != null) {
|
|
1186
|
-
LayerDrawable progressDrawable = (LayerDrawable) seekBar.getProgressDrawable();
|
|
1187
|
-
|
|
1188
|
-
if (timeslider.hasKey("progress")) {
|
|
1189
|
-
// seekBar.getProgressDrawable().setColorFilter(Color.parseColor("#" +
|
|
1190
|
-
// timeslider.getString("progress")), PorterDuff.Mode.SRC_IN);
|
|
1191
|
-
Drawable processDrawable = progressDrawable.findDrawableByLayerId(android.R.id.progress);
|
|
1192
|
-
processDrawable.setColorFilter(Color.parseColor("#" + timeslider.getString("progress")),
|
|
1193
|
-
PorterDuff.Mode.SRC_IN);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
if (timeslider.hasKey("buffer")) {
|
|
1197
|
-
Drawable secondaryProgressDrawable = progressDrawable
|
|
1198
|
-
.findDrawableByLayerId(android.R.id.secondaryProgress);
|
|
1199
|
-
secondaryProgressDrawable.setColorFilter(Color.parseColor("#" + timeslider.getString("buffer")),
|
|
1200
|
-
PorterDuff.Mode.SRC_IN);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
if (timeslider.hasKey("rail")) {
|
|
1204
|
-
Drawable backgroundDrawable = progressDrawable.findDrawableByLayerId(android.R.id.background);
|
|
1205
|
-
backgroundDrawable.setColorFilter(Color.parseColor("#" + timeslider.getString("rail")),
|
|
1206
|
-
PorterDuff.Mode.SRC_IN);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
if (timeslider.hasKey("thumb")) {
|
|
1210
|
-
seekBar.getThumb().setColorFilter(Color.parseColor("#" + timeslider.getString("thumb")),
|
|
1211
|
-
PorterDuff.Mode.SRC_IN);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
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();
|
|
1216
1336
|
|
|
1217
|
-
//
|
|
1337
|
+
// Setup audio
|
|
1218
1338
|
audioManager = (AudioManager) simpleContext.getSystemService(Context.AUDIO_SERVICE);
|
|
1219
1339
|
|
|
1220
1340
|
if (prop.hasKey("backgroundAudioEnabled")) {
|
|
@@ -1224,12 +1344,61 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1224
1344
|
setupPlayerView(backgroundAudioEnabled, playlistItemCallbackEnabled);
|
|
1225
1345
|
|
|
1226
1346
|
if (backgroundAudioEnabled) {
|
|
1227
|
-
audioManager = (AudioManager) simpleContext.getSystemService(Context.AUDIO_SERVICE);
|
|
1228
1347
|
mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
|
|
1229
1348
|
.build();
|
|
1230
1349
|
}
|
|
1231
1350
|
}
|
|
1232
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
|
+
|
|
1233
1402
|
// Audio Focus
|
|
1234
1403
|
|
|
1235
1404
|
public void requestAudioFocus() {
|