@jwplayer/jwplayer-react-native 1.0.1 → 1.0.3

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.
Files changed (58) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +10 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +10 -0
  3. package/.github/ISSUE_TEMPLATE/implement.md +9 -0
  4. package/.github/ISSUE_TEMPLATE/question.md +10 -0
  5. package/README.md +2 -0
  6. package/RNJWPlayer.podspec +2 -2
  7. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  8. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  10. package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
  11. package/android/build.gradle +1 -1
  12. package/android/src/main/AndroidManifest.xml +2 -0
  13. package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +510 -458
  14. package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +384 -32
  15. package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +12 -0
  16. package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +26 -7
  17. package/badges/version.svg +1 -1
  18. package/index.d.ts +41 -7
  19. package/index.js +22 -2
  20. package/ios/RNJWPlayer/RNJWPlayerView.swift +47 -34
  21. package/ios/RNJWPlayer/RNJWPlayerViewController.swift +15 -9
  22. package/ios/RNJWPlayer/RNJWPlayerViewManager.m +9 -1
  23. package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +49 -15
  24. package/jwplayer-jwplayer-react-native-1.0.3.tgz +0 -0
  25. package/package.json +3 -3
  26. package/.idea/modules.xml +0 -8
  27. package/.idea/vcs.xml +0 -6
  28. package/android/.gradle/8.1.1/checksums/checksums.lock +0 -0
  29. package/android/.gradle/8.1.1/checksums/md5-checksums.bin +0 -0
  30. package/android/.gradle/8.1.1/checksums/sha1-checksums.bin +0 -0
  31. package/android/.gradle/8.1.1/dependencies-accessors/dependencies-accessors.lock +0 -0
  32. package/android/.gradle/8.1.1/fileHashes/fileHashes.lock +0 -0
  33. package/android/.gradle/8.4/checksums/checksums.lock +0 -0
  34. package/android/.gradle/8.4/checksums/md5-checksums.bin +0 -0
  35. package/android/.gradle/8.4/checksums/sha1-checksums.bin +0 -0
  36. package/android/.gradle/8.4/dependencies-accessors/dependencies-accessors.lock +0 -0
  37. package/android/.gradle/8.4/dependencies-accessors/gc.properties +0 -0
  38. package/android/.gradle/8.4/executionHistory/executionHistory.bin +0 -0
  39. package/android/.gradle/8.4/executionHistory/executionHistory.lock +0 -0
  40. package/android/.gradle/8.4/fileChanges/last-build.bin +0 -0
  41. package/android/.gradle/8.4/fileHashes/fileHashes.bin +0 -0
  42. package/android/.gradle/8.4/fileHashes/fileHashes.lock +0 -0
  43. package/android/.gradle/8.4/gc.properties +0 -0
  44. package/android/.gradle/config.properties +0 -2
  45. package/android/.gradle/file-system.probe +0 -0
  46. package/android/.idea/compiler.xml +0 -6
  47. package/android/.idea/gradle.xml +0 -18
  48. package/android/.idea/migrations.xml +0 -10
  49. package/android/.idea/misc.xml +0 -10
  50. package/android/.idea/vcs.xml +0 -6
  51. package/android/local.properties +0 -8
  52. package/ios/RNJWPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  53. package/ios/RNJWPlayer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  54. package/ios/RNJWPlayer.xcodeproj/project.xcworkspace/xcuserdata/jmilham.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  55. package/ios/RNJWPlayer.xcodeproj/xcuserdata/jmilham.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  56. /package/android/.gradle/{8.1.1 → 8.9}/dependencies-accessors/gc.properties +0 -0
  57. /package/android/.gradle/{8.1.1 → 8.9}/fileChanges/last-build.bin +0 -0
  58. /package/android/.gradle/{8.1.1 → 8.9}/gc.properties +0 -0
@@ -2,7 +2,11 @@ package com.jwplayer.rnjwplayer;
2
2
 
3
3
 
4
4
  import android.app.Activity;
5
+ import android.app.ActivityManager;
6
+ import android.content.BroadcastReceiver;
5
7
  import android.content.Context;
8
+ import android.content.Intent;
9
+ import android.content.IntentFilter;
6
10
  import android.content.pm.ActivityInfo;
7
11
  import android.graphics.Color;
8
12
  import android.graphics.PorterDuff;
@@ -12,6 +16,8 @@ import android.media.AudioAttributes;
12
16
  import android.media.AudioFocusRequest;
13
17
  import android.media.AudioManager;
14
18
  import android.os.Build;
19
+ import android.os.Handler;
20
+ import android.os.Looper;
15
21
  import android.util.Log;
16
22
  import android.view.View;
17
23
  import android.view.ViewGroup;
@@ -20,7 +26,13 @@ import android.view.WindowManager;
20
26
  import android.widget.LinearLayout;
21
27
  import android.widget.RelativeLayout;
22
28
 
29
+ import androidx.annotation.NonNull;
23
30
  import androidx.appcompat.app.AppCompatActivity;
31
+ import androidx.lifecycle.Lifecycle;
32
+ import androidx.lifecycle.LifecycleEventObserver;
33
+ import androidx.lifecycle.LifecycleObserver;
34
+ import androidx.lifecycle.LifecycleOwner;
35
+ import androidx.lifecycle.LifecycleRegistry;
24
36
 
25
37
  import com.facebook.react.ReactActivity;
26
38
  import com.facebook.react.bridge.Arguments;
@@ -28,15 +40,17 @@ import com.facebook.react.bridge.LifecycleEventListener;
28
40
  import com.facebook.react.bridge.ReactApplicationContext;
29
41
  import com.facebook.react.bridge.ReadableArray;
30
42
  import com.facebook.react.bridge.ReadableMap;
43
+ import com.facebook.react.bridge.WritableArray;
31
44
  import com.facebook.react.bridge.WritableMap;
32
45
  import com.facebook.react.common.MapBuilder;
33
46
  import com.facebook.react.uimanager.ThemedReactContext;
34
47
  import com.facebook.react.uimanager.events.RCTEventEmitter;
35
48
  import com.google.common.collect.ImmutableMap;
36
49
  import com.google.gson.Gson;
37
- import com.jwplayer.pub.api.JsonHelper;
38
50
  import com.jwplayer.pub.api.JWPlayer;
51
+ import com.jwplayer.pub.api.JsonHelper;
39
52
  import com.jwplayer.pub.api.UiGroup;
53
+ import com.jwplayer.pub.api.background.MediaService;
40
54
  import com.jwplayer.pub.api.background.MediaServiceController;
41
55
  import com.jwplayer.pub.api.configuration.PlayerConfig;
42
56
  import com.jwplayer.pub.api.configuration.UiConfig;
@@ -96,18 +110,25 @@ import com.jwplayer.pub.api.events.listeners.AdvertisingEvents;
96
110
  import com.jwplayer.pub.api.events.listeners.CastingEvents;
97
111
  import com.jwplayer.pub.api.events.listeners.PipPluginEvents;
98
112
  import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents;
113
+ import com.jwplayer.pub.api.fullscreen.ExtensibleFullscreenHandler;
114
+ import com.jwplayer.pub.api.fullscreen.FullscreenDialog;
99
115
  import com.jwplayer.pub.api.fullscreen.FullscreenHandler;
116
+ import com.jwplayer.pub.api.fullscreen.delegates.DeviceOrientationDelegate;
117
+ import com.jwplayer.pub.api.fullscreen.delegates.DialogLayoutDelegate;
118
+ import com.jwplayer.pub.api.fullscreen.delegates.SystemUiDelegate;
100
119
  import com.jwplayer.pub.api.license.LicenseUtil;
120
+ import com.jwplayer.pub.api.media.captions.Caption;
101
121
  import com.jwplayer.pub.api.media.playlists.PlaylistItem;
102
122
  import com.jwplayer.ui.views.CueMarkerSeekbar;
103
123
 
124
+ import org.json.JSONObject;
125
+
104
126
  import java.util.ArrayList;
105
127
  import java.util.Arrays;
106
128
  import java.util.HashMap;
107
129
  import java.util.List;
108
130
  import java.util.Map;
109
-
110
- import org.json.JSONObject;
131
+ import java.util.Objects;
111
132
 
112
133
  public class RNJWPlayerView extends RelativeLayout implements
113
134
  VideoPlayerEvents.OnFullscreenListener,
@@ -166,7 +187,7 @@ public class RNJWPlayerView extends RelativeLayout implements
166
187
 
167
188
  AudioManager.OnAudioFocusChangeListener,
168
189
 
169
- LifecycleEventListener {
190
+ LifecycleEventListener, LifecycleOwner {
170
191
  public RNJWPlayer mPlayerView = null;
171
192
  public JWPlayer mPlayer = null;
172
193
 
@@ -183,6 +204,7 @@ public class RNJWPlayerView extends RelativeLayout implements
183
204
  Boolean fullScreenOnLandscape = false;
184
205
  Boolean portraitOnExitFullScreen = false;
185
206
  Boolean exitFullScreenOnPortrait = false;
207
+ Boolean playerInModal = false;
186
208
 
187
209
  Number currentPlayingIndex;
188
210
 
@@ -212,10 +234,17 @@ public class RNJWPlayerView extends RelativeLayout implements
212
234
  private ThemedReactContext mThemedReactContext;
213
235
 
214
236
  private MediaServiceController mMediaServiceController;
237
+ private PipHandlerReceiver mReceiver = null;
215
238
 
216
239
  private void doBindService() {
217
240
  if (mMediaServiceController != null) {
218
- mMediaServiceController.bindService();
241
+ if (!isBackgroundAudioServiceRunning()) {
242
+ // This may not be your expected behavior, but is necessary to avoid crashing
243
+ // Do not use multiple player instances with background audio enabled
244
+
245
+ // don't rebind me if the service is already active with a player.
246
+ mMediaServiceController.bindService();
247
+ }
219
248
  }
220
249
  }
221
250
 
@@ -233,7 +262,7 @@ public class RNJWPlayerView extends RelativeLayout implements
233
262
  }
234
263
 
235
264
  private static Context getNonBuggyContext(ThemedReactContext reactContext,
236
- ReactApplicationContext appContext) {
265
+ ReactApplicationContext appContext) {
237
266
  Context superContext = reactContext;
238
267
  if (!contextHasBug(appContext.getCurrentActivity())) {
239
268
  superContext = appContext.getCurrentActivity();
@@ -250,10 +279,23 @@ public class RNJWPlayerView extends RelativeLayout implements
250
279
  return superContext;
251
280
  }
252
281
 
282
+ private boolean isBackgroundAudioServiceRunning() {
283
+ ActivityManager manager = (ActivityManager) mAppContext.getSystemService(Context.ACTIVITY_SERVICE);
284
+ for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
285
+ if (MediaService.class.getName().equals(service.service.getClassName())) {
286
+ Log.w(TAG, "MediaService is already running with another player loaded. To avoid crashing, this player, "
287
+ + mPlayerView.getTag() + " will not be loaded into the background service.");
288
+ return true;
289
+ }
290
+ }
291
+ return false;
292
+ }
293
+
253
294
  public RNJWPlayerView(ThemedReactContext reactContext, ReactApplicationContext appContext) {
254
295
  super(getNonBuggyContext(reactContext, appContext));
255
296
  mAppContext = appContext;
256
297
 
298
+ registry.setCurrentState(Lifecycle.State.CREATED);
257
299
  mThemedReactContext = reactContext;
258
300
 
259
301
  mActivity = (ReactActivity) getActivity();
@@ -261,11 +303,25 @@ public class RNJWPlayerView extends RelativeLayout implements
261
303
  mWindow = mActivity.getWindow();
262
304
  }
263
305
 
306
+ if (mActivity != null) {
307
+ mActivity.getLifecycle().addObserver(lifecycleObserver);
308
+ }
309
+
264
310
  mRootView = mActivity.findViewById(android.R.id.content);
265
311
 
266
312
  getReactContext().addLifecycleEventListener(this);
267
313
  }
268
314
 
315
+ private LifecycleObserver lifecycleObserver = new LifecycleEventObserver() {
316
+ @Override
317
+ public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
318
+ if (event.getTargetState() == Lifecycle.State.DESTROYED) {
319
+ return; // no op: handled elsewhere
320
+ }
321
+ registry.setCurrentState(event.getTargetState());
322
+ }
323
+ };
324
+
269
325
  public ReactApplicationContext getAppContext() {
270
326
  return mAppContext;
271
327
  }
@@ -288,10 +344,41 @@ public class RNJWPlayerView extends RelativeLayout implements
288
344
  return mThemedReactContext.getReactApplicationContext().getCurrentActivity();
289
345
  }
290
346
 
347
+ // The registry for lifecycle events. Required by player object. Main use case if for garbage collection / teardown
348
+ private final LifecycleRegistry registry = new LifecycleRegistry(this);
349
+
350
+ @NonNull
351
+ @Override
352
+ public Lifecycle getLifecycle() {
353
+ return registry;
354
+ }
355
+
356
+ // // closest to `ondestroy` for a view without listening to the activity event
357
+ // // The activity event can be deceptive in React-Native
358
+ // @Override
359
+ // protected void onDetachedFromWindow() {
360
+ // super.onDetachedFromWindow();
361
+ // registry.setCurrentState(Lifecycle.State.DESTROYED);
362
+ // }
363
+
291
364
  public void destroyPlayer() {
292
365
  if (mPlayer != null) {
366
+ unRegisterReceiver();
367
+
368
+ // If we are casting we need to break the cast session as there is no simple
369
+ // way to reconnect to an existing session if the player that created it is dead
370
+
371
+ // If this doesn't match your use case, using a single player object and load content
372
+ // into it rather than creating a new player for every piece of content.
293
373
  mPlayer.stop();
294
374
 
375
+ // send signal to JW SDK player is destroyed
376
+ registry.setCurrentState(Lifecycle.State.DESTROYED);
377
+
378
+ // Stop listening to activities lifecycle
379
+ mActivity.getLifecycle().removeObserver(lifecycleObserver);
380
+ mPlayer.deregisterActivityForPip();
381
+
295
382
  mPlayer.removeListeners(this,
296
383
  // VideoPlayerEvents
297
384
  EventType.READY,
@@ -348,7 +435,7 @@ public class RNJWPlayerView extends RelativeLayout implements
348
435
  EventType.PIP_OPEN
349
436
  );
350
437
 
351
- mPlayer = null;
438
+ mPlayer = null;
352
439
  mPlayerView = null;
353
440
 
354
441
  getReactContext().removeLifecycleEventListener(this);
@@ -427,12 +514,117 @@ public class RNJWPlayerView extends RelativeLayout implements
427
514
  EventType.PIP_OPEN
428
515
  );
429
516
 
430
- mPlayer.setFullscreenHandler(new fullscreenHandler());
517
+ if (playerInModal) {
518
+ mPlayer.setFullscreenHandler(createModalFullscreenHandler());
519
+ } else {
520
+ mPlayer.setFullscreenHandler(new fullscreenHandler());
521
+ }
431
522
 
432
523
  mPlayer.allowBackgroundAudio(backgroundAudioEnabled);
433
524
  }
434
525
  }
435
526
 
527
+ /**
528
+ * Helper to build the a generic `ExtensibleFullscreenHandler` with small tweaks to play nice with Modals
529
+ *
530
+ * @return {@link ExtensibleFullscreenHandler}
531
+ */
532
+ private ExtensibleFullscreenHandler createModalFullscreenHandler() {
533
+ DeviceOrientationDelegate delegate = getDeviceOrientationDelegate();
534
+ FullscreenDialog dialog = new FullscreenDialog(
535
+ mActivity,
536
+ mActivity,
537
+ android.R.style.Theme_Black_NoTitleBar_Fullscreen
538
+ );
539
+
540
+ return new ExtensibleFullscreenHandler(
541
+ new DialogLayoutDelegate(
542
+ mPlayerView,
543
+ dialog
544
+ ),
545
+ delegate,
546
+ new SystemUiDelegate(
547
+ mActivity,
548
+ mActivity.getLifecycle(),
549
+ new Handler(),
550
+ dialog.getWindow().getDecorView()
551
+ )
552
+ ) {
553
+ @Override
554
+ public void onFullscreenRequested() {
555
+ // if landscape is priorty we have to turn off full-screen portrait before allowing
556
+ // the default call for full-screen
557
+ mPlayer.allowFullscreenPortrait(!landscapeOnFullScreen);
558
+ super.onFullscreenRequested();
559
+ // safely set it back on UI thread after work can be finished
560
+ final Handler handler = new Handler(Looper.getMainLooper());
561
+ handler.postDelayed(() -> {
562
+ if (mPlayer != null) {
563
+ mPlayer.allowFullscreenPortrait(true);
564
+ }
565
+ }, 100);
566
+ WritableMap eventEnterFullscreen = Arguments.createMap();
567
+ eventEnterFullscreen.putString("message", "onFullscreenRequested");
568
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
569
+ getId(),
570
+ "topFullScreenRequested",
571
+ eventEnterFullscreen);
572
+ }
573
+
574
+ @Override
575
+ public void onFullscreenExitRequested() {
576
+ super.onFullscreenExitRequested();
577
+
578
+ WritableMap eventExitFullscreen = Arguments.createMap();
579
+ eventExitFullscreen.putString("message", "onFullscreenExitRequested");
580
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
581
+ getId(),
582
+ "topFullScreenExitRequested",
583
+ eventExitFullscreen);
584
+ }
585
+ };
586
+ }
587
+
588
+ /**
589
+ * Add logic here for your custom orientation implementation
590
+ *
591
+ * @return Default {@link DeviceOrientationDelegate}
592
+ */
593
+ private DeviceOrientationDelegate getDeviceOrientationDelegate() {
594
+ DeviceOrientationDelegate delegate = new DeviceOrientationDelegate(
595
+ mActivity,
596
+ mActivity.getLifecycle(),
597
+ new Handler()
598
+ ) {
599
+ @Override
600
+ public void setFullscreen(boolean fullscreen) {
601
+ super.setFullscreen(fullscreen);
602
+ }
603
+
604
+ @Override
605
+ public void onAllowRotationChanged(boolean allowRotation) {
606
+ super.onAllowRotationChanged(allowRotation);
607
+ }
608
+
609
+ @Override
610
+ protected void doRotation(boolean fullscreen, boolean allowFullscreenPortrait) {
611
+ super.doRotation(fullscreen, allowFullscreenPortrait);
612
+ }
613
+
614
+ @Override
615
+ protected void doRotationListener() {
616
+ super.doRotationListener();
617
+ }
618
+
619
+ @Override
620
+ public void onAllowFullscreenPortrait(boolean allowFullscreenPortrait) {
621
+ super.onAllowFullscreenPortrait(allowFullscreenPortrait);
622
+ }
623
+ };
624
+ delegate.onAllowRotationChanged(true);
625
+ return delegate;
626
+ }
627
+
436
628
  private class fullscreenHandler implements FullscreenHandler {
437
629
  ViewGroup mPlayerViewContainer = (ViewGroup) mPlayerView.getParent();
438
630
  private View mDecorView;
@@ -501,11 +693,21 @@ public class RNJWPlayerView extends RelativeLayout implements
501
693
  mPlayerViewContainer.post(new Runnable() {
502
694
  @Override
503
695
  public void run() {
696
+ // View may not have been removed properly (especially if returning from PiP)
697
+ mPlayerViewContainer.removeView(mPlayerView);
698
+
504
699
  mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
505
700
  ViewGroup.LayoutParams.MATCH_PARENT,
506
701
  ViewGroup.LayoutParams.MATCH_PARENT));
507
- mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
508
- mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
702
+ // returning from full-screen portrait requires a different measure
703
+ if (mActivity.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
704
+ ) {
705
+ mPlayerView.layout(mPlayerView.getLeft(), mPlayerViewContainer.getTop(),
706
+ mPlayerViewContainer.getMeasuredWidth(), mPlayerViewContainer.getBottom());
707
+ } else {
708
+ mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
709
+ mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
710
+ }
509
711
  }
510
712
  });
511
713
 
@@ -519,12 +721,12 @@ public class RNJWPlayerView extends RelativeLayout implements
519
721
 
520
722
  @Override
521
723
  public void onAllowRotationChanged(boolean b) {
522
- Log.e(TAG, "onAllowRotationChanged: " + b );
724
+ Log.e(TAG, "onAllowRotationChanged: " + b);
523
725
  }
524
726
 
525
727
  @Override
526
728
  public void onAllowFullscreenPortraitChanged(boolean allowFullscreenPortrait) {
527
- Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait );
729
+ Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait);
528
730
  }
529
731
 
530
732
  @Override
@@ -540,10 +742,101 @@ public class RNJWPlayerView extends RelativeLayout implements
540
742
  }
541
743
  }
542
744
 
745
+ private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
746
+
747
+ private class PipHandlerReceiver extends BroadcastReceiver {
748
+
749
+ @Override
750
+ public void onReceive(Context context, Intent intent) {
751
+ if (intent == null) {
752
+ return;
753
+ }
754
+ if (Objects.equals(intent.getAction(), "onPictureInPictureModeChanged")) {
755
+ if (intent.hasExtra("newConfig") && intent.hasExtra("isInPictureInPictureMode")) {
756
+ // Tell the JWP SDK we are toggling so it can handle toolbar / internal setup
757
+ mPlayer.onPictureInPictureModeChanged(intent.getBooleanExtra("isInPictureInPictureMode", false), intent.getParcelableExtra("newConfig"));
758
+
759
+ View decorView = mActivity.getWindow().getDecorView();
760
+ ViewGroup rootView = decorView.findViewById(android.R.id.content);
761
+
762
+ ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
763
+ ViewGroup.LayoutParams.MATCH_PARENT,
764
+ ViewGroup.LayoutParams.MATCH_PARENT);
765
+
766
+ if (intent.getBooleanExtra("isInPictureInPictureMode", false)) {
767
+ // Going into Picture in Picture
768
+ ViewGroup parent = (ViewGroup) mPlayerView.getParent();
769
+
770
+ // Remove the player view temporarily
771
+ if (parent != null) {
772
+ parent.removeView(mPlayerView);
773
+ }
774
+
775
+ // Hide all views but player view and keep a handle on them for later
776
+ for (int i = 0; i < rootView.getChildCount(); i++) {
777
+ if (rootView.getChildAt(i) != mPlayerView) {
778
+ rootViewChildrenOriginalVisibility.add(rootView.getChildAt(i).getVisibility());
779
+ rootView.getChildAt(i).setVisibility(View.GONE);
780
+ }
781
+ }
782
+ // Add player view back (This is safe since the JWP SDK has already calculated the PiP size/aspect off the View)
783
+ rootView.addView(mPlayerView, layoutParams);
784
+ } else {
785
+ // Exiting Picture in Picture
786
+
787
+ // Toggle controls to ensure we don't lose them -- weird UX bug fix where controls got lost
788
+ mPlayer.setForceControlsVisibility(true);
789
+ mPlayer.setForceControlsVisibility(false);
790
+
791
+ // If player was in fullscreen when going into PiP, we need to force it back out
792
+ if (mPlayer.getFullscreen()) {
793
+ mPlayer.setFullscreen(false, true);
794
+ }
795
+
796
+ // Strip player view
797
+ rootView.removeView(mPlayerView);
798
+
799
+ // Add visibility back to any other controls
800
+ for (int i = 0; i < rootView.getChildCount(); i++) {
801
+ rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i));
802
+ }
803
+ // Clear our list of views
804
+ rootViewChildrenOriginalVisibility.clear();
805
+ // Add player view back in main spot
806
+ addView(mPlayerView, 0, layoutParams);
807
+ }
808
+ }
809
+ }
810
+ }
811
+ }
812
+
813
+ private void registerReceiver() {
814
+ mReceiver = new PipHandlerReceiver();
815
+ IntentFilter intentFilter = new IntentFilter("onPictureInPictureModeChanged");
816
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
817
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
818
+ // Must be Exported to get intents
819
+ mActivity.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
820
+ } else {
821
+ mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
822
+ }
823
+ } else {
824
+ mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
825
+ }
826
+ }
827
+
828
+ private void unRegisterReceiver() {
829
+ if (mReceiver != null) {
830
+ mActivity.unregisterReceiver(mReceiver);
831
+ mReceiver = null;
832
+ }
833
+
834
+ }
835
+
543
836
  public void setConfig(ReadableMap prop) {
544
837
  if (mConfig == null || !mConfig.equals(prop)) {
545
838
  if (mConfig != null && isOnlyDiff(prop, "playlist") && mPlayer != null) { // still safe check, even with JW
546
- // JSON change
839
+ // JSON change
547
840
  PlayerConfig oldConfig = mPlayer.getConfig();
548
841
  PlayerConfig config = new PlayerConfig.Builder()
549
842
  .autostart(oldConfig.getAutostart())
@@ -610,7 +903,7 @@ public class RNJWPlayerView extends RelativeLayout implements
610
903
 
611
904
  boolean playlistNotTheSame(ReadableMap prop) {
612
905
  return prop.hasKey("playlist") && mPlaylistProp != prop.getArray("playlist") && !Arrays
613
- .deepEquals(new ReadableArray[] { mPlaylistProp }, new ReadableArray[] { prop.getArray("playlist") });
906
+ .deepEquals(new ReadableArray[]{mPlaylistProp}, new ReadableArray[]{prop.getArray("playlist")});
614
907
  }
615
908
 
616
909
  private void setupPlayer(ReadableMap prop) {
@@ -621,7 +914,7 @@ public class RNJWPlayerView extends RelativeLayout implements
621
914
  PlayerConfig jwConfig = null;
622
915
  Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
623
916
  Boolean isJwConfig = false;
624
- if(!forceLegacy){
917
+ if (!forceLegacy) {
625
918
  try {
626
919
  obj = MapUtil.toJSONObject(prop);
627
920
  jwConfig = JsonHelper.parseConfigJson(obj);
@@ -752,6 +1045,11 @@ public class RNJWPlayerView extends RelativeLayout implements
752
1045
  LinearLayout.LayoutParams.MATCH_PARENT));
753
1046
  addView(mPlayerView);
754
1047
 
1048
+ // Ensure we have a valid state before applying to the player
1049
+ registry.setCurrentState(Lifecycle.State.STARTED);
1050
+
1051
+ mPlayer = mPlayerView.getPlayer(this);
1052
+
755
1053
  if (prop.hasKey("controls")) { // Hack for controls hiding not working right away
756
1054
  mPlayerView.getPlayer().setControls(prop.getBoolean("controls"));
757
1055
  }
@@ -761,13 +1059,23 @@ public class RNJWPlayerView extends RelativeLayout implements
761
1059
  mPlayerView.fullScreenOnLandscape = fullScreenOnLandscape;
762
1060
  }
763
1061
 
1062
+ if (prop.hasKey("landscapeOnFullScreen")) {
1063
+ landscapeOnFullScreen = prop.getBoolean("landscapeOnFullScreen");
1064
+ }
1065
+
1066
+ if (prop.hasKey("portraitOnExitFullScreen")) {
1067
+ portraitOnExitFullScreen = prop.getBoolean("fullScreenOnLandscape");
1068
+ }
1069
+
1070
+ if (prop.hasKey("playerInModal")) {
1071
+ playerInModal = prop.getBoolean("playerInModal");
1072
+ }
1073
+
764
1074
  if (prop.hasKey("exitFullScreenOnPortrait")) {
765
1075
  exitFullScreenOnPortrait = prop.getBoolean("exitFullScreenOnPortrait");
766
1076
  mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
767
1077
  }
768
1078
 
769
- mPlayer = mPlayerView.getPlayer();
770
-
771
1079
  if (isJwConfig) {
772
1080
  mPlayer.setup(jwConfig);
773
1081
  } else {
@@ -778,16 +1086,18 @@ public class RNJWPlayerView extends RelativeLayout implements
778
1086
  if (mActivity != null && prop.hasKey("pipEnabled")) {
779
1087
  boolean pipEnabled = prop.getBoolean("pipEnabled");
780
1088
  if (pipEnabled) {
1089
+ registerReceiver();
781
1090
  mPlayer.registerActivityForPip(mActivity, mActivity.getSupportActionBar());
782
1091
  } else {
783
1092
  mPlayer.deregisterActivityForPip();
1093
+ unRegisterReceiver();
784
1094
  }
785
1095
  }
786
1096
 
787
1097
  // Legacy
788
1098
  // This isn't the ideal way to do this on Android. All drawables/colors/themes shoudld
789
1099
  // be targed using styling. See `https://docs.jwplayer.com/players/docs/android-styling-guide`
790
- // for more information on how best to override the JWP styles using XML. If you are unsure of a
1100
+ // for more information on how best to override the JWP styles using XML. If you are unsure of a
791
1101
  // color/drawable/theme, open an `Ask` issue.
792
1102
  if (mColors != null) {
793
1103
  if (mColors.hasKey("backgroundColor")) {
@@ -844,8 +1154,6 @@ public class RNJWPlayerView extends RelativeLayout implements
844
1154
 
845
1155
  if (backgroundAudioEnabled) {
846
1156
  audioManager = (AudioManager) simpleContext.getSystemService(Context.AUDIO_SERVICE);
847
- // Throws a fatal error if using a playlistURL instead of manually created playlist
848
- // Related to SDK-11346
849
1157
  mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
850
1158
  .build();
851
1159
  }
@@ -1103,7 +1411,7 @@ public class RNJWPlayerView extends RelativeLayout implements
1103
1411
  event.putString("message", "onPlayerAdWarning");
1104
1412
  event.putInt("code", adWarningEvent.getCode());
1105
1413
  event.putInt("adErrorCode", adWarningEvent.getAdErrorCode());
1106
- event.putString("error", adWarningEvent.getMessage());
1414
+ event.putString("warning", adWarningEvent.getMessage());
1107
1415
  getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topPlayerAdWarning", event);
1108
1416
  }
1109
1417
 
@@ -1209,6 +1517,38 @@ public class RNJWPlayerView extends RelativeLayout implements
1209
1517
 
1210
1518
  }
1211
1519
 
1520
+ // Captions Events
1521
+
1522
+ @Override
1523
+ public void onCaptionsChanged(CaptionsChangedEvent captionsChangedEvent) {
1524
+ WritableMap event = Arguments.createMap();
1525
+ event.putString("message", "onCaptionsChanged");
1526
+ event.putInt("index", captionsChangedEvent.getCurrentTrack());
1527
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCaptionsChanged", event);
1528
+ }
1529
+
1530
+ @Override
1531
+ public void onCaptionsList(CaptionsListEvent captionsListEvent) {
1532
+ WritableMap event = Arguments.createMap();
1533
+ List<Caption> captionTrackList = captionsListEvent.getCaptions();
1534
+ WritableArray captionTracks = Arguments.createArray();
1535
+ if (captionTrackList != null) {
1536
+ for(int i = 0; i < captionTrackList.size(); i++) {
1537
+ WritableMap captionTrack = Arguments.createMap();
1538
+ Caption track = captionTrackList.get(i);
1539
+ captionTrack.putString("file", track.getFile());
1540
+ captionTrack.putString("label", track.getLabel());
1541
+ captionTrack.putBoolean("default", track.isDefault());
1542
+ captionTracks.pushMap(captionTrack);
1543
+ }
1544
+ }
1545
+ event.putString("message", "onCaptionsList");
1546
+ event.putInt("index", captionsListEvent.getCurrentCaptionIndex());
1547
+ event.putArray("tracks", captionTracks);
1548
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCaptionsList", event);
1549
+
1550
+ }
1551
+
1212
1552
  // Player Events
1213
1553
 
1214
1554
  @Override
@@ -1257,6 +1597,7 @@ public class RNJWPlayerView extends RelativeLayout implements
1257
1597
  if (ex != null) {
1258
1598
  event.putString("error", ex.toString());
1259
1599
  event.putString("description", errorEvent.getMessage());
1600
+ event.putInt("errorCode", errorEvent.getErrorCode());
1260
1601
  }
1261
1602
  getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topPlayerError", event);
1262
1603
 
@@ -1269,6 +1610,13 @@ public class RNJWPlayerView extends RelativeLayout implements
1269
1610
  doBindService();
1270
1611
  requestAudioFocus();
1271
1612
  }
1613
+ WritableMap onFirstFrame = Arguments.createMap();
1614
+ onFirstFrame.putString("message", "onLoaded");
1615
+ onFirstFrame.putDouble("loadTime", firstFrameEvent.getLoadTime());
1616
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
1617
+ getId(),
1618
+ "topFirstFrame",
1619
+ onFirstFrame);
1272
1620
  }
1273
1621
 
1274
1622
  @Override
@@ -1400,6 +1748,8 @@ public class RNJWPlayerView extends RelativeLayout implements
1400
1748
  public void onSetupError(SetupErrorEvent setupErrorEvent) {
1401
1749
  WritableMap event = Arguments.createMap();
1402
1750
  event.putString("message", "onSetupError");
1751
+ event.putString("errorMessage", setupErrorEvent.getMessage());
1752
+ event.putInt("errorCode", setupErrorEvent.getCode());
1403
1753
  getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topSetupPlayerError", event);
1404
1754
 
1405
1755
  updateWakeLock(false);
@@ -1414,16 +1764,6 @@ public class RNJWPlayerView extends RelativeLayout implements
1414
1764
  getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topTime", event);
1415
1765
  }
1416
1766
 
1417
- @Override
1418
- public void onCaptionsChanged(CaptionsChangedEvent captionsChangedEvent) {
1419
-
1420
- }
1421
-
1422
- @Override
1423
- public void onCaptionsList(CaptionsListEvent captionsListEvent) {
1424
-
1425
- }
1426
-
1427
1767
  @Override
1428
1768
  public void onMeta(MetaEvent metaEvent) {
1429
1769
 
@@ -1443,6 +1783,17 @@ public class RNJWPlayerView extends RelativeLayout implements
1443
1783
 
1444
1784
  // Casting events
1445
1785
 
1786
+ private boolean mIsCastActive = false;
1787
+
1788
+ /**
1789
+ * Get if this player-view is currently casting
1790
+ *
1791
+ * @return true if casting
1792
+ */
1793
+ public boolean getIsCastActive() {
1794
+ return mIsCastActive;
1795
+ }
1796
+
1446
1797
  @Override
1447
1798
  public void onCast(CastEvent castEvent) {
1448
1799
  WritableMap event = Arguments.createMap();
@@ -1451,13 +1802,14 @@ public class RNJWPlayerView extends RelativeLayout implements
1451
1802
  event.putBoolean("active", castEvent.isActive());
1452
1803
  event.putBoolean("available", castEvent.isAvailable());
1453
1804
  getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCasting", event);
1805
+ mIsCastActive = castEvent.isActive();
1454
1806
  // stop/start the background audio service if it's running and we're casting
1455
1807
  if (castEvent.isActive()) {
1456
1808
  doUnbindService();
1457
1809
  } else {
1458
1810
  if (backgroundAudioEnabled) {
1459
1811
  mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
1460
- .build();
1812
+ .build();
1461
1813
  doBindService();
1462
1814
  }
1463
1815
  }