@capgo/capacitor-video-player 8.1.18 → 8.1.20

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 CHANGED
@@ -115,6 +115,22 @@ await VideoPlayer.initPlayer({
115
115
  - Widevine DRM metadata is forwarded to the Cast media item. DRM-protected streams may still require a receiver that supports your license server and DRM flow.
116
116
  - Request headers used by the Android local player are not automatically available to the Chromecast receiver. Use public URLs, signed URLs, cookies supported by your receiver, or a custom receiver for secured media.
117
117
 
118
+ ## Android Picture in Picture
119
+
120
+ Picture in Picture on Android requires your app activity to declare PiP support in `android/app/src/main/AndroidManifest.xml`:
121
+
122
+ ```xml
123
+ <activity
124
+ android:name=".MainActivity"
125
+ android:supportsPictureInPicture="true"
126
+ android:launchMode="singleTask"
127
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
128
+ ...>
129
+ </activity>
130
+ ```
131
+
132
+ `launchMode="singleTask"` and the `configChanges` flags prevent the player activity from being recreated when entering or leaving PiP.
133
+
118
134
  ## API
119
135
 
120
136
  <docgen-index>
@@ -49,6 +49,7 @@ import com.getcapacitor.JSObject;
49
49
  import com.google.android.exoplayer2.C;
50
50
  import com.google.android.exoplayer2.DefaultLoadControl;
51
51
  import com.google.android.exoplayer2.ExoPlayer;
52
+ import com.google.android.exoplayer2.Format;
52
53
  import com.google.android.exoplayer2.LoadControl;
53
54
  import com.google.android.exoplayer2.MediaItem;
54
55
  import com.google.android.exoplayer2.MediaMetadata;
@@ -132,6 +133,10 @@ public class FullscreenExoPlayerFragment extends Fragment {
132
133
  public JSObject drmOptions;
133
134
 
134
135
  private static final String TAG = FullscreenExoPlayerFragment.class.getName();
136
+ private static final Rational DEFAULT_PIP_ASPECT_RATIO = new Rational(16, 9);
137
+ // Android PiP aspect ratio must stay within [100/239, 239/100].
138
+ private static final Rational MIN_PIP_ASPECT_RATIO = new Rational(100, 239);
139
+ private static final Rational MAX_PIP_ASPECT_RATIO = new Rational(239, 100);
135
140
  public static final long UNKNOWN_TIME = -1L;
136
141
  private final List<String> supportedFormat = Arrays.asList(
137
142
  new String[] { "mp4", "webm", "ogv", "3gp", "flv", "dash", "mpd", "m3u8", "ism", "ytube", "" }
@@ -459,7 +464,9 @@ public class FullscreenExoPlayerFragment extends Fragment {
459
464
  new View.OnClickListener() {
460
465
  @Override
461
466
  public void onClick(View view) {
462
- pictureInPictureMode();
467
+ if (playerReady) {
468
+ pictureInPictureMode();
469
+ }
463
470
  }
464
471
  }
465
472
  );
@@ -595,35 +602,92 @@ public class FullscreenExoPlayerFragment extends Fragment {
595
602
  * Perform pictureInPictureMode Action
596
603
  */
597
604
  private void pictureInPictureMode() {
598
- if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
599
- styledPlayerView.setUseController(false);
600
- styledPlayerView.setControllerAutoShow(false);
601
- linearLayout.setVisibility(View.INVISIBLE);
602
- Log.v(TAG, "PIP break 1");
603
- // require android O or higher
604
- if (
605
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
606
- ) {
607
- pictureInPictureParams = new PictureInPictureParams.Builder();
608
- // setup height and width of the PIP window
609
- Rational aspectRatio = new Rational(player.getVideoFormat().width, player.getVideoFormat().height);
610
- pictureInPictureParams.setAspectRatio(aspectRatio).build();
611
- getActivity().enterPictureInPictureMode(pictureInPictureParams.build());
612
- Log.v(TAG, "PIP break 2");
605
+ Activity activity = getActivity();
606
+ if (
607
+ activity == null ||
608
+ player == null ||
609
+ !playerReady ||
610
+ !pipEnabled ||
611
+ !packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
612
+ ) {
613
+ return;
614
+ }
615
+
616
+ styledPlayerView.setUseController(false);
617
+ styledPlayerView.setControllerAutoShow(false);
618
+ linearLayout.setVisibility(View.INVISIBLE);
619
+
620
+ try {
621
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
622
+ pictureInPictureParams = new PictureInPictureParams.Builder().setAspectRatio(getPipAspectRatio());
623
+ if (!activity.enterPictureInPictureMode(pictureInPictureParams.build())) {
624
+ Log.w(TAG, "pictureInPictureMode: enterPictureInPictureMode returned false");
625
+ restorePlayerUiAfterFailedPip();
626
+ return;
627
+ }
613
628
  } else {
614
- getActivity().enterPictureInPictureMode();
615
- Log.v(TAG, "PIP break 3");
629
+ activity.enterPictureInPictureMode();
616
630
  }
617
- isInPictureInPictureMode = getActivity().isInPictureInPictureMode();
631
+ } catch (IllegalArgumentException | IllegalStateException exception) {
632
+ Log.e(TAG, "pictureInPictureMode failed", exception);
633
+ restorePlayerUiAfterFailedPip();
634
+ Toast.makeText(context, "Unable to start Picture in Picture", Toast.LENGTH_SHORT).show();
635
+ return;
636
+ }
637
+
638
+ isInPictureInPictureMode = activity.isInPictureInPictureMode();
639
+ if (sturi != null) {
640
+ setSubtitle(true);
641
+ }
642
+ play();
643
+ handler.postDelayed(mRunnable, 100);
644
+ }
645
+
646
+ private void restorePlayerUiAfterFailedPip() {
647
+ linearLayout.setVisibility(View.INVISIBLE);
648
+ if (showControls) {
649
+ styledPlayerView.setUseController(true);
650
+ styledPlayerView.setControllerAutoShow(true);
651
+ }
652
+ }
653
+
654
+ private Rational getPipAspectRatio() {
655
+ if (player == null) {
656
+ return DEFAULT_PIP_ASPECT_RATIO;
657
+ }
658
+
659
+ Format videoFormat = player.getVideoFormat();
660
+ if (videoFormat == null || videoFormat.width <= 0 || videoFormat.height <= 0) {
661
+ return DEFAULT_PIP_ASPECT_RATIO;
662
+ }
663
+
664
+ Rational aspectRatio = new Rational(videoFormat.width, videoFormat.height);
665
+ if (aspectRatio.floatValue() < MIN_PIP_ASPECT_RATIO.floatValue()) {
666
+ return MIN_PIP_ASPECT_RATIO;
667
+ }
668
+ if (aspectRatio.floatValue() > MAX_PIP_ASPECT_RATIO.floatValue()) {
669
+ return MAX_PIP_ASPECT_RATIO;
670
+ }
671
+ return aspectRatio;
672
+ }
673
+
674
+ public void handlePictureInPictureModeChanged(boolean inPictureInPictureMode) {
675
+ isInPictureInPictureMode = inPictureInPictureMode;
676
+ if (inPictureInPictureMode) {
677
+ linearLayout.setVisibility(View.INVISIBLE);
678
+ styledPlayerView.setUseController(false);
618
679
  if (sturi != null) {
619
680
  setSubtitle(true);
620
681
  }
621
- if (player != null) play();
682
+ return;
683
+ }
622
684
 
623
- handler.postDelayed(mRunnable, 100);
624
- Log.v(TAG, "PIP break 4");
625
- } else {
626
- Log.v(TAG, "pictureInPictureMode: doesn't support PIP");
685
+ isPIPModeEnabled = true;
686
+ if (showControls) {
687
+ styledPlayerView.setUseController(true);
688
+ }
689
+ if (sturi != null) {
690
+ setSubtitle(false);
627
691
  }
628
692
  }
629
693
 
@@ -662,12 +726,17 @@ public class FullscreenExoPlayerFragment extends Fragment {
662
726
  @Override
663
727
  public void onStop() {
664
728
  super.onStop();
665
- boolean isAppBackground = false;
666
- if (bkModeEnabled) isAppBackground = isApplicationSentToBackground(context);
667
- if (isInPictureInPictureMode) {
729
+ Activity activity = getActivity();
730
+ if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInPictureInPictureMode()) {
731
+ isInPictureInPictureMode = true;
732
+ return;
733
+ }
734
+
735
+ if (isInPictureInPictureMode && activity != null) {
736
+ isInPictureInPictureMode = false;
668
737
  linearLayout.setVisibility(View.VISIBLE);
669
738
  playerExit();
670
- getActivity().finishAndRemoveTask();
739
+ activity.finishAndRemoveTask();
671
740
  }
672
741
  }
673
742
 
@@ -1706,6 +1775,10 @@ public class FullscreenExoPlayerFragment extends Fragment {
1706
1775
  @Override
1707
1776
  public void onConfigurationChanged(Configuration newConfig) {
1708
1777
  super.onConfigurationChanged(newConfig);
1778
+ Activity activity = getActivity();
1779
+ if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
1780
+ handlePictureInPictureModeChanged(activity.isInPictureInPictureMode());
1781
+ }
1709
1782
  adjustAspectRatio();
1710
1783
  }
1711
1784
 
@@ -6,6 +6,7 @@ import android.app.UiModeManager;
6
6
  import android.content.Context;
7
7
  import android.content.pm.ActivityInfo;
8
8
  import android.content.res.Configuration;
9
+ import android.content.res.Configuration;
9
10
  import android.os.Build;
10
11
  import android.util.Log;
11
12
  import android.view.ViewGroup;
@@ -38,7 +39,7 @@ import java.util.Map;
38
39
  )
39
40
  public class VideoPlayerPlugin extends Plugin {
40
41
 
41
- private final String pluginVersion = "8.1.18";
42
+ private final String pluginVersion = "8.1.20";
42
43
 
43
44
  // Permission alias constants
44
45
  private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
@@ -93,6 +94,14 @@ public class VideoPlayerPlugin extends Plugin {
93
94
  this.fragmentUtils = new FragmentUtils(getBridge());
94
95
  }
95
96
 
97
+ @Override
98
+ protected void handleOnConfigurationChanged(Configuration newConfig) {
99
+ super.handleOnConfigurationChanged(newConfig);
100
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && fsFragment != null && getActivity() != null) {
101
+ fsFragment.handlePictureInPictureModeChanged(getActivity().isInPictureInPictureMode());
102
+ }
103
+ }
104
+
96
105
  @PermissionCallback
97
106
  private void permissionsCallback(PluginCall call) {
98
107
  if (!isPermissionsGranted()) {
@@ -8,7 +8,7 @@ import AVKit
8
8
  */
9
9
  @objc(VideoPlayerPlugin)
10
10
  public class VideoPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
11
- private let pluginVersion: String = "8.1.18"
11
+ private let pluginVersion: String = "8.1.20"
12
12
  public let identifier = "VideoPlayerPlugin"
13
13
  public let jsName = "VideoPlayer"
14
14
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-video-player",
3
- "version": "8.1.18",
3
+ "version": "8.1.20",
4
4
  "description": "Capacitor plugin to play video in native player",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -49,8 +49,7 @@
49
49
  "prepublishOnly": "bun run build",
50
50
  "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
51
51
  "example:install": "cd example-app && bun install --frozen-lockfile",
52
- "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
53
- "example:capgo:deploy": "bun run example:build && bun scripts/deploy-example-capgo.mjs"
52
+ "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build"
54
53
  },
55
54
  "devDependencies": {
56
55
  "@capacitor/android": "^8.0.0",