@jwplayer/jwplayer-react-native 1.0.0 → 1.0.2

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 (43) hide show
  1. package/.github/CODEOWNERS +1 -1
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +12 -2
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +12 -2
  4. package/.github/ISSUE_TEMPLATE/implement.md +26 -0
  5. package/.github/ISSUE_TEMPLATE/question.md +12 -2
  6. package/README.md +25 -3
  7. package/RNJWPlayer.podspec +2 -2
  8. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  9. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
  12. package/android/build.gradle +2 -2
  13. package/android/src/main/AndroidManifest.xml +2 -0
  14. package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +259 -11
  15. package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +4 -0
  16. package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +24 -3
  17. package/badges/version.svg +1 -1
  18. package/index.d.ts +14 -2
  19. package/index.js +8 -2
  20. package/ios/RNJWPlayer/RNJWPlayerView.swift +202 -196
  21. package/ios/RNJWPlayer/RNJWPlayerViewController.swift +27 -23
  22. package/ios/RNJWPlayer/RNJWPlayerViewManager.m +4 -0
  23. package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +5 -3
  24. package/package.json +3 -3
  25. package/android/.gradle/8.1.1/checksums/checksums.lock +0 -0
  26. package/android/.gradle/8.1.1/dependencies-accessors/dependencies-accessors.lock +0 -0
  27. package/android/.gradle/8.1.1/fileHashes/fileHashes.lock +0 -0
  28. package/android/.gradle/8.2/checksums/checksums.lock +0 -0
  29. package/android/.gradle/8.2/dependencies-accessors/dependencies-accessors.lock +0 -0
  30. package/android/.gradle/8.2/dependencies-accessors/gc.properties +0 -0
  31. package/android/.gradle/8.2/fileChanges/last-build.bin +0 -0
  32. package/android/.gradle/8.2/fileHashes/fileHashes.lock +0 -0
  33. package/android/.gradle/8.2/gc.properties +0 -0
  34. package/android/.gradle/config.properties +0 -2
  35. package/android/.idea/gradle.xml +0 -12
  36. package/android/.idea/migrations.xml +0 -10
  37. package/android/.idea/misc.xml +0 -10
  38. package/android/.idea/vcs.xml +0 -6
  39. package/android/.idea/workspace.xml +0 -54
  40. package/android/local.properties +0 -8
  41. /package/android/.gradle/{8.1.1 → 8.9}/dependencies-accessors/gc.properties +0 -0
  42. /package/android/.gradle/{8.1.1 → 8.9}/fileChanges/last-build.bin +0 -0
  43. /package/android/.gradle/{8.1.1 → 8.9}/gc.properties +0 -0
@@ -1,2 +1,2 @@
1
1
  # SDK Team
2
- @jwplayer/team_sdks
2
+ * @jwplayer/team_sdks
@@ -2,8 +2,8 @@
2
2
  name: Bug report
3
3
  about: Create a report to help us improve
4
4
  title: "[BUG]"
5
- labels: ''
6
- assignees: ''
5
+ labels: "needs-grooming"
6
+ assignees: '@jwplayer/team_sdks'
7
7
 
8
8
  ---
9
9
 
@@ -36,3 +36,13 @@ If you are having a build issue, we would like to know about your machine.
36
36
 
37
37
  **Additional context**
38
38
  Add any other context about the problem here.
39
+
40
+ **JWP Ticketing**
41
+ To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
42
+ * Associates the `Issue` with your company
43
+ * Permits securely share sensitive information related to the bug, request, or question
44
+ * Enhances JWP's ability to track progress and provide timely updates
45
+ * Enables you to track and manage multiple issues through a single platform
46
+
47
+ | ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
48
+ |:---|
@@ -2,8 +2,8 @@
2
2
  name: Feature request
3
3
  about: Suggest an idea for this project
4
4
  title: "[FEAT]"
5
- labels: ''
6
- assignees: ''
5
+ labels: 'needs-grooming'
6
+ assignees: '@jwplayer/team_sdks'
7
7
 
8
8
  ---
9
9
 
@@ -18,3 +18,13 @@ A clear and concise description of any alternative solutions or features you've
18
18
 
19
19
  **Additional context**
20
20
  Add any other context or screenshots about the feature request here.
21
+
22
+ **JWP Ticketing**
23
+ To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
24
+ * Associates the `Issue` with your company
25
+ * Permits securely share sensitive information related to the bug, request, or question
26
+ * Enhances JWP's ability to track progress and provide timely updates
27
+ * Enables you to track and manage multiple issues through a single platform
28
+
29
+ | ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
30
+ |:---|
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: Implementation
3
+ about: Request an implementation of a native feature or upgrade of SDK
4
+ title: "[IMPLEMENT]"
5
+ labels: 'needs-grooming'
6
+ assignees: '@jwplayer/team_sdks'
7
+
8
+ ---
9
+
10
+ **What needs to be implemented**
11
+
12
+ **How should it be implemented**
13
+
14
+ **Acceptance Criteria**
15
+
16
+ **Additional context**
17
+
18
+ **JWP Ticketing**
19
+ To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
20
+ * Associates the `Issue` with your company
21
+ * Permits securely share sensitive information related to the bug, request, or question
22
+ * Enhances JWP's ability to track progress and provide timely updates
23
+ * Enables you to track and manage multiple issues through a single platform
24
+
25
+ | ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
26
+ |:---|
@@ -2,10 +2,20 @@
2
2
  name: Question
3
3
  about: Ask a question about the project or product
4
4
  title: "[ASK]"
5
- labels: ''
6
- assignees: ''
5
+ labels: 'needs-grooming'
6
+ assignees: '@jwplayer/team_sdks'
7
7
 
8
8
  ---
9
9
 
10
10
  **What isn't clear?**
11
11
  What information are we not providing that you think we should?
12
+
13
+ **JWP Ticketing**
14
+ To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
15
+ * Associates the `Issue` with your company
16
+ * Permits securely share sensitive information related to the bug, request, or question
17
+ * Enhances JWP's ability to track progress and provide timely updates
18
+ * Enables you to track and manage multiple issues through a single platform
19
+
20
+ | ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
21
+ |:---|
package/README.md CHANGED
@@ -49,8 +49,9 @@ Follow these steps to add the library to your Android project:
49
49
  }
50
50
  ```
51
51
 
52
+ For more details and guidance regarding configuration and requirements, see the [JWP Android SDK documentation](https://docs.jwplayer.com/players/docs/android-overview#requirements).
52
53
 
53
- <br /><br />
54
+ <br />
54
55
 
55
56
  ### iOS
56
57
 
@@ -71,6 +72,8 @@ Follow these steps to add the library to your iOS project:
71
72
  pod install
72
73
  ```
73
74
 
75
+ For more details and guidance regarding configuration and requirements, see the [JWP iOS SDK documentation](https://docs.jwplayer.com/players/docs/ios-overview#requirements).
76
+
74
77
  <br /><br />
75
78
 
76
79
  ## Usage
@@ -138,7 +141,7 @@ Follow these steps to configure the media playback experience in your app:
138
141
  : 'YOUR_IOS_SDK_KEY',
139
142
  backgroundAudioEnabled: true,
140
143
  autostart: true,
141
- styling: {
144
+ styling: { // only (mostly) compatible with iOS
142
145
  colors: {
143
146
  timeslider: {
144
147
  rail: "0000FF",
@@ -175,6 +178,7 @@ Follow these steps to configure the media playback experience in your app:
175
178
  onPlayerError={event => this.onPlayerError(event)}
176
179
  onBuffer={() => this.onBuffer()}
177
180
  onTime={event => this.onTime(event)}
181
+ onLoaded={event => this.onLoaded(event)}
178
182
  onFullScreen={() => this.onFullScreen()}
179
183
  onFullScreenExit={() => this.onFullScreenExit()}
180
184
  />
@@ -238,7 +242,7 @@ Follow these steps to run the example project:
238
242
 
239
243
  ## Advanced Topics
240
244
 
241
- [Advertising](#advertising) | [Background Audio](#background-audio) | [Casting](#casting) | [DRM](#drm) | [Picture in Picture (PiP)](#picture-in-picture-pip)
245
+ [Advertising](#advertising) | [Background Audio](#background-audio) | [Casting](#casting) | [DRM](#drm) | [Picture in Picture (PiP)](#picture-in-picture-pip) | [Styling](#styling)
242
246
 
243
247
  <br />
244
248
 
@@ -408,6 +412,24 @@ If you use a different provider for DRM or this does not work for your use case,
408
412
 
409
413
  <br /><br />
410
414
 
415
+ ### Styling
416
+
417
+ [Android](#android-styling) | [iOS](#ios-styling)
418
+
419
+ #### Android Styling
420
+ The `styling` prop will not work when using the modern prop convention that matches the JWP Delivery API. Even when using the `forceLegacyConfig` prop, Android may not respect your choices.
421
+
422
+ Android styling is best handled through overring JWP IDs in your apps resources files. See the documentation [here](https://docs.jwplayer.com/players/docs/android-styling-guide).
423
+
424
+ A sample of overring a color via XML can be seen in this [colors file](Example/android/app/src/main/res/values/colors.xml). The color specified here is the default, but if you wish to change it, the color will be updated on the player.
425
+
426
+ <br />
427
+
428
+ #### iOS Styling
429
+ You can use the styling elements as defined in the [Legacy Readme](/docs/legacy_readme.md#styling).
430
+
431
+ <br /><br />
432
+
411
433
  ## Contributing
412
434
 
413
435
  - All contributions should correlate to an open issue. We hope to avoid doubling the work between contributors.
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
12
12
  s.platform = :ios, "14.0"
13
13
  s.source = { :git => "https://github.com/jwplayer/jwplayer-react-native.git", :tag => "v#{s.version}" }
14
14
  s.source_files = "ios/RNJWPlayer/*.{h,m,swift}"
15
- s.dependency 'JWPlayerKit', '~> 4.18.0'
15
+ s.dependency 'JWPlayerKit', '>= 4.19.0'
16
16
  s.dependency 'React-Core'
17
17
  s.static_framework = true
18
18
  s.info_plist = {
@@ -35,7 +35,7 @@ Pod::Spec.new do |s|
35
35
  end
36
36
  if defined?($RNJWPlayerUseGoogleIMA)
37
37
  Pod::UI.puts "RNJWPlayer: enable IMA SDK"
38
- s.dependency 'GoogleAds-IMA-iOS-SDK', '~>3.19.1'
38
+ s.dependency 'GoogleAds-IMA-iOS-SDK', '~> 3.22'
39
39
  s.pod_target_xcconfig = {
40
40
  'OTHER_SWIFT_FLAGS' => '$(inherited) -D USE_GOOGLE_IMA'
41
41
  }
@@ -1,2 +1,2 @@
1
- #Fri Apr 19 13:27:57 PDT 2024
2
- gradle.version=8.1.1
1
+ #Tue Aug 06 14:41:55 PDT 2024
2
+ gradle.version=8.9
@@ -66,7 +66,7 @@ allprojects {
66
66
  }
67
67
  }
68
68
 
69
- def jwPlayerVersion = "4.16.2"
69
+ def jwPlayerVersion = "4.18.3"
70
70
  def exoplayerVersion = "2.18.7" // Deprecated. Use Media3 when targeting JW SDK > 4.16.0
71
71
  def media3ExoVersion = "1.1.1"
72
72
 
@@ -82,7 +82,7 @@ dependencies {
82
82
 
83
83
  // Ad dependencies
84
84
  if (useIMA) {
85
- implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.31.0'
85
+ implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.33.0'
86
86
  implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
87
87
  }
88
88
 
@@ -9,11 +9,13 @@
9
9
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
10
10
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
11
11
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
12
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
12
13
  <!-- READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READ_MEDIA_AUDIO.-->
13
14
 
14
15
  <application>
15
16
  <service
16
17
  android:name="com.jwplayer.pub.api.background.MediaService"
18
+ android:foregroundServiceType="mediaPlayback"
17
19
  android:exported="false">
18
20
  <intent-filter>
19
21
  <action android:name="android.intent.action.MEDIA_BUTTON" />
@@ -2,7 +2,10 @@ package com.jwplayer.rnjwplayer;
2
2
 
3
3
 
4
4
  import android.app.Activity;
5
+ import android.content.BroadcastReceiver;
5
6
  import android.content.Context;
7
+ import android.content.Intent;
8
+ import android.content.IntentFilter;
6
9
  import android.content.pm.ActivityInfo;
7
10
  import android.graphics.Color;
8
11
  import android.graphics.PorterDuff;
@@ -12,11 +15,15 @@ import android.media.AudioAttributes;
12
15
  import android.media.AudioFocusRequest;
13
16
  import android.media.AudioManager;
14
17
  import android.os.Build;
18
+ import android.os.Handler;
19
+ import android.os.Looper;
15
20
  import android.util.Log;
21
+ import android.view.Choreographer;
16
22
  import android.view.View;
17
23
  import android.view.ViewGroup;
18
24
  import android.view.Window;
19
25
  import android.view.WindowManager;
26
+ import android.widget.FrameLayout;
20
27
  import android.widget.LinearLayout;
21
28
  import android.widget.RelativeLayout;
22
29
 
@@ -96,7 +103,12 @@ import com.jwplayer.pub.api.events.listeners.AdvertisingEvents;
96
103
  import com.jwplayer.pub.api.events.listeners.CastingEvents;
97
104
  import com.jwplayer.pub.api.events.listeners.PipPluginEvents;
98
105
  import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents;
106
+ import com.jwplayer.pub.api.fullscreen.ExtensibleFullscreenHandler;
107
+ import com.jwplayer.pub.api.fullscreen.FullscreenDialog;
99
108
  import com.jwplayer.pub.api.fullscreen.FullscreenHandler;
109
+ import com.jwplayer.pub.api.fullscreen.delegates.DeviceOrientationDelegate;
110
+ import com.jwplayer.pub.api.fullscreen.delegates.DialogLayoutDelegate;
111
+ import com.jwplayer.pub.api.fullscreen.delegates.SystemUiDelegate;
100
112
  import com.jwplayer.pub.api.license.LicenseUtil;
101
113
  import com.jwplayer.pub.api.media.playlists.PlaylistItem;
102
114
  import com.jwplayer.ui.views.CueMarkerSeekbar;
@@ -106,6 +118,7 @@ import java.util.Arrays;
106
118
  import java.util.HashMap;
107
119
  import java.util.List;
108
120
  import java.util.Map;
121
+ import java.util.Objects;
109
122
 
110
123
  import org.json.JSONObject;
111
124
 
@@ -183,6 +196,7 @@ public class RNJWPlayerView extends RelativeLayout implements
183
196
  Boolean fullScreenOnLandscape = false;
184
197
  Boolean portraitOnExitFullScreen = false;
185
198
  Boolean exitFullScreenOnPortrait = false;
199
+ Boolean playerInModal = false;
186
200
 
187
201
  Number currentPlayingIndex;
188
202
 
@@ -212,6 +226,7 @@ public class RNJWPlayerView extends RelativeLayout implements
212
226
  private ThemedReactContext mThemedReactContext;
213
227
 
214
228
  private MediaServiceController mMediaServiceController;
229
+ private PipHandlerReceiver mReceiver = null;
215
230
 
216
231
  private void doBindService() {
217
232
  if (mMediaServiceController != null) {
@@ -233,7 +248,7 @@ public class RNJWPlayerView extends RelativeLayout implements
233
248
  }
234
249
 
235
250
  private static Context getNonBuggyContext(ThemedReactContext reactContext,
236
- ReactApplicationContext appContext) {
251
+ ReactApplicationContext appContext) {
237
252
  Context superContext = reactContext;
238
253
  if (!contextHasBug(appContext.getCurrentActivity())) {
239
254
  superContext = appContext.getCurrentActivity();
@@ -290,6 +305,8 @@ public class RNJWPlayerView extends RelativeLayout implements
290
305
 
291
306
  public void destroyPlayer() {
292
307
  if (mPlayer != null) {
308
+ unRegisterReceiver();
309
+ mPlayer.deregisterActivityForPip();
293
310
  mPlayer.stop();
294
311
 
295
312
  mPlayer.removeListeners(this,
@@ -348,7 +365,7 @@ public class RNJWPlayerView extends RelativeLayout implements
348
365
  EventType.PIP_OPEN
349
366
  );
350
367
 
351
- mPlayer = null;
368
+ mPlayer = null;
352
369
  mPlayerView = null;
353
370
 
354
371
  getReactContext().removeLifecycleEventListener(this);
@@ -427,12 +444,116 @@ public class RNJWPlayerView extends RelativeLayout implements
427
444
  EventType.PIP_OPEN
428
445
  );
429
446
 
430
- mPlayer.setFullscreenHandler(new fullscreenHandler());
447
+ if (playerInModal) {
448
+ mPlayer.setFullscreenHandler(createModalFullscreenHandler());
449
+ } else {
450
+ mPlayer.setFullscreenHandler(new fullscreenHandler());
451
+ }
431
452
 
432
453
  mPlayer.allowBackgroundAudio(backgroundAudioEnabled);
433
454
  }
434
455
  }
435
456
 
457
+ /**
458
+ * Helper to build the a generic `ExtensibleFullscreenHandler` with small tweaks to play nice with Modals
459
+ * @return {@link ExtensibleFullscreenHandler}
460
+ */
461
+ private ExtensibleFullscreenHandler createModalFullscreenHandler() {
462
+ DeviceOrientationDelegate delegate = getDeviceOrientationDelegate();
463
+ FullscreenDialog dialog = new FullscreenDialog(
464
+ mActivity,
465
+ mActivity,
466
+ android.R.style.Theme_Black_NoTitleBar_Fullscreen
467
+ );
468
+
469
+ return new ExtensibleFullscreenHandler(
470
+ new DialogLayoutDelegate(
471
+ mPlayerView,
472
+ dialog
473
+ ),
474
+ delegate,
475
+ new SystemUiDelegate(
476
+ mActivity,
477
+ mActivity.getLifecycle(),
478
+ new Handler(),
479
+ dialog.getWindow().getDecorView()
480
+ )
481
+ ) {
482
+ @Override
483
+ public void onFullscreenRequested() {
484
+ // if landscape is priorty we have to turn off full-screen portrait before allowing
485
+ // the default call for full-screen
486
+ mPlayer.allowFullscreenPortrait(!landscapeOnFullScreen);
487
+ super.onFullscreenRequested();
488
+ // safely set it back on UI thread after work can be finished
489
+ final Handler handler = new Handler(Looper.getMainLooper());
490
+ handler.postDelayed(() -> {
491
+ if (mPlayer != null) {
492
+ mPlayer.allowFullscreenPortrait(true);
493
+ }
494
+ }, 100);
495
+ WritableMap eventEnterFullscreen = Arguments.createMap();
496
+ eventEnterFullscreen.putString("message", "onFullscreenRequested");
497
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
498
+ getId(),
499
+ "topFullScreenRequested",
500
+ eventEnterFullscreen);
501
+ }
502
+
503
+ @Override
504
+ public void onFullscreenExitRequested() {
505
+ super.onFullscreenExitRequested();
506
+
507
+ WritableMap eventExitFullscreen = Arguments.createMap();
508
+ eventExitFullscreen.putString("message", "onFullscreenExitRequested");
509
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
510
+ getId(),
511
+ "topFullScreenExitRequested",
512
+ eventExitFullscreen);
513
+ }
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Add logic here for your custom orientation implementation
519
+ *
520
+ * @return Default {@link DeviceOrientationDelegate}
521
+ */
522
+ private DeviceOrientationDelegate getDeviceOrientationDelegate() {
523
+ DeviceOrientationDelegate delegate = new DeviceOrientationDelegate(
524
+ mActivity,
525
+ mActivity.getLifecycle(),
526
+ new Handler()
527
+ ) {
528
+ @Override
529
+ public void setFullscreen(boolean fullscreen) {
530
+ super.setFullscreen(fullscreen);
531
+ }
532
+
533
+ @Override
534
+ public void onAllowRotationChanged(boolean allowRotation) {
535
+ super.onAllowRotationChanged(allowRotation);
536
+ }
537
+
538
+ @Override
539
+ protected void doRotation(boolean fullscreen, boolean allowFullscreenPortrait) {
540
+ super.doRotation(fullscreen, allowFullscreenPortrait);
541
+ }
542
+
543
+ @Override
544
+ protected void doRotationListener() {
545
+ super.doRotationListener();
546
+ }
547
+
548
+ @Override
549
+ public void onAllowFullscreenPortrait(boolean allowFullscreenPortrait) {
550
+ super.onAllowFullscreenPortrait(allowFullscreenPortrait);
551
+ }
552
+ };
553
+ delegate.onAllowRotationChanged(true);
554
+ return delegate;
555
+ }
556
+
436
557
  private class fullscreenHandler implements FullscreenHandler {
437
558
  ViewGroup mPlayerViewContainer = (ViewGroup) mPlayerView.getParent();
438
559
  private View mDecorView;
@@ -504,8 +625,15 @@ public class RNJWPlayerView extends RelativeLayout implements
504
625
  mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
505
626
  ViewGroup.LayoutParams.MATCH_PARENT,
506
627
  ViewGroup.LayoutParams.MATCH_PARENT));
507
- mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
508
- mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
628
+ // returning from full-screen portrait requires a different measure
629
+ if (mActivity.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
630
+ ) {
631
+ mPlayerView.layout(mPlayerView.getLeft(), mPlayerViewContainer.getTop(),
632
+ mPlayerViewContainer.getMeasuredWidth(), mPlayerViewContainer.getBottom());
633
+ } else {
634
+ mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
635
+ mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
636
+ }
509
637
  }
510
638
  });
511
639
 
@@ -519,12 +647,12 @@ public class RNJWPlayerView extends RelativeLayout implements
519
647
 
520
648
  @Override
521
649
  public void onAllowRotationChanged(boolean b) {
522
- Log.e(TAG, "onAllowRotationChanged: " + b );
650
+ Log.e(TAG, "onAllowRotationChanged: " + b);
523
651
  }
524
652
 
525
653
  @Override
526
654
  public void onAllowFullscreenPortraitChanged(boolean allowFullscreenPortrait) {
527
- Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait );
655
+ Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait);
528
656
  }
529
657
 
530
658
  @Override
@@ -540,10 +668,96 @@ public class RNJWPlayerView extends RelativeLayout implements
540
668
  }
541
669
  }
542
670
 
671
+ private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
672
+
673
+ private class PipHandlerReceiver extends BroadcastReceiver {
674
+
675
+ @Override
676
+ public void onReceive(Context context, Intent intent) {
677
+ if (intent == null) {
678
+ return;
679
+ }
680
+ if (Objects.equals(intent.getAction(), "onPictureInPictureModeChanged")) {
681
+ if (intent.hasExtra("newConfig") && intent.hasExtra("isInPictureInPictureMode")) {
682
+ // Tell the JWP SDK we are toggling so it can handle toolbar / internal setup
683
+ mPlayer.onPictureInPictureModeChanged(intent.getBooleanExtra("isInPictureInPictureMode", false), intent.getParcelableExtra("newConfig"));
684
+
685
+ View decorView = mActivity.getWindow().getDecorView();
686
+ ViewGroup rootView = decorView.findViewById(android.R.id.content);
687
+
688
+ ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
689
+ ViewGroup.LayoutParams.MATCH_PARENT,
690
+ ViewGroup.LayoutParams.MATCH_PARENT);
691
+
692
+ if (intent.getBooleanExtra("isInPictureInPictureMode", false)) {
693
+ // Going into Picture in Picture
694
+ ViewGroup parent = (ViewGroup) mPlayerView.getParent();
695
+
696
+ // Remove the player view temporarily
697
+ if (parent != null) {
698
+ parent.removeView(mPlayerView);
699
+ }
700
+
701
+ // Hide all views but player view and keep a handle on them for later
702
+ for (int i = 0; i < rootView.getChildCount(); i++) {
703
+ if (rootView.getChildAt(i) != mPlayerView) {
704
+ rootViewChildrenOriginalVisibility.add(rootView.getChildAt(i).getVisibility());
705
+ rootView.getChildAt(i).setVisibility(View.GONE);
706
+ }
707
+ }
708
+ // Add player view back (This is safe since the JWP SDK has already calculated the PiP size/aspect off the View)
709
+ rootView.addView(mPlayerView, layoutParams);
710
+ } else {
711
+ // Exiting Picture in Picture
712
+
713
+ // Toggle controls to ensure we don't lose them -- weird UX bug fix where controls got lost
714
+ mPlayer.setForceControlsVisibility(true);
715
+ mPlayer.setForceControlsVisibility(false);
716
+
717
+ // Strip player view
718
+ rootView.removeView(mPlayerView);
719
+
720
+ // Add visibility back to any other controls
721
+ for (int i = 0; i < rootView.getChildCount(); i++) {
722
+ rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i));
723
+ }
724
+ // Clear our list of views
725
+ rootViewChildrenOriginalVisibility.clear();
726
+ // Add player view back in main spot
727
+ addView(mPlayerView, 0, layoutParams);
728
+ }
729
+ }
730
+ }
731
+ }
732
+ }
733
+
734
+ private void registerReceiver() {
735
+ mReceiver = new PipHandlerReceiver();
736
+ IntentFilter intentFilter = new IntentFilter("onPictureInPictureModeChanged");
737
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
738
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
739
+ // Must be Exported to get intents
740
+ mActivity.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
741
+ } else {
742
+ mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
743
+ }
744
+ } else {
745
+ mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
746
+ }
747
+ }
748
+
749
+ private void unRegisterReceiver() {
750
+ if (mReceiver != null) {
751
+ mActivity.unregisterReceiver(mReceiver);
752
+ mReceiver = null;
753
+ }
754
+
755
+ }
756
+
543
757
  public void setConfig(ReadableMap prop) {
544
758
  if (mConfig == null || !mConfig.equals(prop)) {
545
759
  if (mConfig != null && isOnlyDiff(prop, "playlist") && mPlayer != null) { // still safe check, even with JW
546
- // JSON change
760
+ // JSON change
547
761
  PlayerConfig oldConfig = mPlayer.getConfig();
548
762
  PlayerConfig config = new PlayerConfig.Builder()
549
763
  .autostart(oldConfig.getAutostart())
@@ -610,7 +824,7 @@ public class RNJWPlayerView extends RelativeLayout implements
610
824
 
611
825
  boolean playlistNotTheSame(ReadableMap prop) {
612
826
  return prop.hasKey("playlist") && mPlaylistProp != prop.getArray("playlist") && !Arrays
613
- .deepEquals(new ReadableArray[] { mPlaylistProp }, new ReadableArray[] { prop.getArray("playlist") });
827
+ .deepEquals(new ReadableArray[]{mPlaylistProp}, new ReadableArray[]{prop.getArray("playlist")});
614
828
  }
615
829
 
616
830
  private void setupPlayer(ReadableMap prop) {
@@ -621,7 +835,7 @@ public class RNJWPlayerView extends RelativeLayout implements
621
835
  PlayerConfig jwConfig = null;
622
836
  Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
623
837
  Boolean isJwConfig = false;
624
- if(!forceLegacy){
838
+ if (!forceLegacy) {
625
839
  try {
626
840
  obj = MapUtil.toJSONObject(prop);
627
841
  jwConfig = JsonHelper.parseConfigJson(obj);
@@ -761,6 +975,18 @@ public class RNJWPlayerView extends RelativeLayout implements
761
975
  mPlayerView.fullScreenOnLandscape = fullScreenOnLandscape;
762
976
  }
763
977
 
978
+ if (prop.hasKey("landscapeOnFullScreen")) {
979
+ landscapeOnFullScreen = prop.getBoolean("landscapeOnFullScreen");
980
+ }
981
+
982
+ if (prop.hasKey("portraitOnExitFullScreen")) {
983
+ portraitOnExitFullScreen = prop.getBoolean("fullScreenOnLandscape");
984
+ }
985
+
986
+ if (prop.hasKey("playerInModal")) {
987
+ playerInModal = prop.getBoolean("playerInModal");
988
+ }
989
+
764
990
  if (prop.hasKey("exitFullScreenOnPortrait")) {
765
991
  exitFullScreenOnPortrait = prop.getBoolean("exitFullScreenOnPortrait");
766
992
  mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
@@ -778,14 +1004,19 @@ public class RNJWPlayerView extends RelativeLayout implements
778
1004
  if (mActivity != null && prop.hasKey("pipEnabled")) {
779
1005
  boolean pipEnabled = prop.getBoolean("pipEnabled");
780
1006
  if (pipEnabled) {
1007
+ registerReceiver();
781
1008
  mPlayer.registerActivityForPip(mActivity, mActivity.getSupportActionBar());
782
1009
  } else {
783
1010
  mPlayer.deregisterActivityForPip();
1011
+ unRegisterReceiver();
784
1012
  }
785
1013
  }
786
1014
 
787
1015
  // Legacy
788
- // This isn't the ideal way to do this on Android
1016
+ // This isn't the ideal way to do this on Android. All drawables/colors/themes shoudld
1017
+ // be targed using styling. See `https://docs.jwplayer.com/players/docs/android-styling-guide`
1018
+ // for more information on how best to override the JWP styles using XML. If you are unsure of a
1019
+ // color/drawable/theme, open an `Ask` issue.
789
1020
  if (mColors != null) {
790
1021
  if (mColors.hasKey("backgroundColor")) {
791
1022
  mPlayerView.setBackgroundColor(Color.parseColor("#" + mColors.getString("backgroundColor")));
@@ -1266,6 +1497,13 @@ public class RNJWPlayerView extends RelativeLayout implements
1266
1497
  doBindService();
1267
1498
  requestAudioFocus();
1268
1499
  }
1500
+ WritableMap onFirstFrame = Arguments.createMap();
1501
+ onFirstFrame.putString("message", "onLoaded");
1502
+ onFirstFrame.putDouble("loadTime", firstFrameEvent.getLoadTime());
1503
+ getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
1504
+ getId(),
1505
+ "topFirstFrame",
1506
+ onFirstFrame);
1269
1507
  }
1270
1508
 
1271
1509
  @Override
@@ -1448,6 +1686,16 @@ public class RNJWPlayerView extends RelativeLayout implements
1448
1686
  event.putBoolean("active", castEvent.isActive());
1449
1687
  event.putBoolean("available", castEvent.isAvailable());
1450
1688
  getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCasting", event);
1689
+ // stop/start the background audio service if it's running and we're casting
1690
+ if (castEvent.isActive()) {
1691
+ doUnbindService();
1692
+ } else {
1693
+ if (backgroundAudioEnabled) {
1694
+ mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
1695
+ .build();
1696
+ doBindService();
1697
+ }
1698
+ }
1451
1699
  }
1452
1700
 
1453
1701
  // LifecycleEventListener