@capgo/capacitor-video-player 8.1.22 → 8.1.24

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
@@ -152,6 +152,8 @@ Picture in Picture on Android requires your app activity to declare PiP support
152
152
  * [`showController()`](#showcontroller)
153
153
  * [`isControllerIsFullyVisible()`](#iscontrollerisfullyvisible)
154
154
  * [`exitPlayer()`](#exitplayer)
155
+ * [`hidePlayer()`](#hideplayer)
156
+ * [`showPlayer()`](#showplayer)
155
157
  * [Interfaces](#interfaces)
156
158
 
157
159
  </docgen-index>
@@ -432,6 +434,34 @@ Exit player
432
434
  --------------------
433
435
 
434
436
 
437
+ ### hidePlayer()
438
+
439
+ ```typescript
440
+ hidePlayer() => Promise<capVideoPlayerResult>
441
+ ```
442
+
443
+ Hide the currently presented player UI without stopping playback (native fullscreen).
444
+
445
+ Call `showPlayer()` to show it again.
446
+
447
+ **Returns:** <code>Promise&lt;<a href="#capvideoplayerresult">capVideoPlayerResult</a>&gt;</code>
448
+
449
+ --------------------
450
+
451
+
452
+ ### showPlayer()
453
+
454
+ ```typescript
455
+ showPlayer() => Promise<capVideoPlayerResult>
456
+ ```
457
+
458
+ Show again a previously hidden player UI (native fullscreen).
459
+
460
+ **Returns:** <code>Promise&lt;<a href="#capvideoplayerresult">capVideoPlayerResult</a>&gt;</code>
461
+
462
+ --------------------
463
+
464
+
435
465
  ### Interfaces
436
466
 
437
467
 
@@ -447,31 +477,33 @@ Exit player
447
477
 
448
478
  #### capVideoPlayerOptions
449
479
 
450
- | Prop | Type | Description |
451
- | --------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
452
- | **`mode`** | <code>string</code> | Player mode - "fullscreen" - "embedded" (Web only) |
453
- | **`url`** | <code>string</code> | The url of the video to play |
454
- | **`subtitle`** | <code>string</code> | The url of subtitle associated with the video |
455
- | **`language`** | <code>string</code> | The language of subtitle see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers |
456
- | **`subtitleOptions`** | <code><a href="#subtitleoptions">SubTitleOptions</a></code> | SubTitle Options |
457
- | **`playerId`** | <code>string</code> | Id of DIV Element parent of the player |
458
- | **`rate`** | <code>number</code> | Initial playing rate |
459
- | **`exitOnEnd`** | <code>boolean</code> | Exit on VideoEnd (iOS, Android) default: true |
460
- | **`loopOnEnd`** | <code>boolean</code> | Loop on VideoEnd when exitOnEnd false (iOS, Android) default: false |
461
- | **`pipEnabled`** | <code>boolean</code> | Picture in Picture Enable (iOS, Android) default: true |
462
- | **`bkmodeEnabled`** | <code>boolean</code> | Background Mode Enable (iOS, Android) default: true |
463
- | **`showControls`** | <code>boolean</code> | Show Controls Enable (iOS, Android) default: true |
464
- | **`displayMode`** | <code>string</code> | Display Mode ["all", "portrait", "landscape"] (iOS, Android) default: "all" |
465
- | **`componentTag`** | <code>string</code> | Component Tag or DOM Element Tag (React app) |
466
- | **`width`** | <code>number</code> | Player Width (mode "embedded" only) |
467
- | **`height`** | <code>number</code> | Player height (mode "embedded" only) |
468
- | **`headers`** | <code>{ [key: string]: string; }</code> | Headers for the request (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
469
- | **`title`** | <code>string</code> | Title shown in the player and Chromecast metadata (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
470
- | **`smallTitle`** | <code>string</code> | Subtitle shown below the title in the player and Chromecast metadata (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
471
- | **`accentColor`** | <code>string</code> | ExoPlayer Progress Bar and Spinner color (Android) by Manuel García Marín (https://github.com/PhantomPainX) Must be a valid hex color code default: #FFFFFF |
472
- | **`chromecast`** | <code>boolean</code> | Chromecast enable/disable (iOS, Android) iOS requires Google Cast SDK setup and local network Info.plist keys. by Manuel García Marín (https://github.com/PhantomPainX) default: true |
473
- | **`artwork`** | <code>string</code> | Artwork url to be shown in Chromecast player (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) default: "" |
474
- | **`drm`** | <code><a href="#drmoptions">DrmOptions</a></code> | DRM configuration for protected content (iOS: FairPlay, Android: Widevine) |
480
+ | Prop | Type | Description |
481
+ | --------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
482
+ | **`mode`** | <code>string</code> | Player mode - "fullscreen" - "embedded" (Web only) |
483
+ | **`url`** | <code>string</code> | The url of the video to play |
484
+ | **`chromecastUrl`** | <code>string</code> | Optional override URL used when casting to Chromecast (iOS, Android). This lets you cast an alternative rendition/manifest (e.g. MPEG-DASH + Widevine) while keeping the on-device playback URL unchanged (e.g. HLS + FairPlay on iOS). |
485
+ | **`subtitle`** | <code>string</code> | The url of subtitle associated with the video |
486
+ | **`language`** | <code>string</code> | The language of subtitle see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers |
487
+ | **`subtitleOptions`** | <code><a href="#subtitleoptions">SubTitleOptions</a></code> | SubTitle Options |
488
+ | **`playerId`** | <code>string</code> | Id of DIV Element parent of the player |
489
+ | **`rate`** | <code>number</code> | Initial playing rate |
490
+ | **`exitOnEnd`** | <code>boolean</code> | Exit on VideoEnd (iOS, Android) default: true |
491
+ | **`loopOnEnd`** | <code>boolean</code> | Loop on VideoEnd when exitOnEnd false (iOS, Android) default: false |
492
+ | **`pipEnabled`** | <code>boolean</code> | Picture in Picture Enable (iOS, Android) default: true |
493
+ | **`bkmodeEnabled`** | <code>boolean</code> | Background Mode Enable (iOS, Android) default: true |
494
+ | **`audioCategory`** | <code>'ambient' \| 'playback' \| 'moviePlayback'</code> | iOS audio session category behavior (iOS). - "ambient": respects the iPhone silent switch (default) - "playback": ignores the iPhone silent switch - "moviePlayback": playback optimized for video and long-form AirPlay default: "ambient" |
495
+ | **`showControls`** | <code>boolean</code> | Show Controls Enable (iOS, Android) default: true |
496
+ | **`displayMode`** | <code>string</code> | Display Mode ["all", "portrait", "landscape"] (iOS, Android) default: "all" |
497
+ | **`componentTag`** | <code>string</code> | Component Tag or DOM Element Tag (React app) |
498
+ | **`width`** | <code>number</code> | Player Width (mode "embedded" only) |
499
+ | **`height`** | <code>number</code> | Player height (mode "embedded" only) |
500
+ | **`headers`** | <code>{ [key: string]: string; }</code> | Headers for the request (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
501
+ | **`title`** | <code>string</code> | Title shown in the player and Chromecast metadata (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
502
+ | **`smallTitle`** | <code>string</code> | Subtitle shown below the title in the player and Chromecast metadata (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
503
+ | **`accentColor`** | <code>string</code> | ExoPlayer Progress Bar and Spinner color (Android) by Manuel García Marín (https://github.com/PhantomPainX) Must be a valid hex color code default: #FFFFFF |
504
+ | **`chromecast`** | <code>boolean</code> | Chromecast enable/disable (iOS, Android) iOS requires Google Cast SDK setup and local network Info.plist keys. by Manuel García Marín (https://github.com/PhantomPainX) default: true |
505
+ | **`artwork`** | <code>string</code> | Artwork url to be shown in Chromecast player (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) default: "" |
506
+ | **`drm`** | <code><a href="#drmoptions">DrmOptions</a></code> | DRM configuration for protected content (iOS: FairPlay, Android: Widevine) When casting, Widevine's `certificateUrl` (license URL) is also forwarded to Chromecast receivers as `media.customData.laurl`. |
475
507
 
476
508
 
477
509
  #### SubTitleOptions
@@ -498,10 +530,11 @@ Exit player
498
530
 
499
531
  #### FairPlayDrmOptions
500
532
 
501
- | Prop | Type | Description |
502
- | ---------------------- | ------------------- | --------------------------------------------------------------------------------- |
503
- | **`certificateUrl`** | <code>string</code> | The URL to fetch the FairPlay certificate |
504
- | **`contentKeySpcUrl`** | <code>string</code> | The URL to send the SPC and receive the CKC license (FairPlay license server URL) |
533
+ | Prop | Type | Description |
534
+ | ---------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
535
+ | **`certificateUrl`** | <code>string</code> | The URL to fetch the FairPlay certificate |
536
+ | **`contentKeySpcUrl`** | <code>string</code> | The URL to send the SPC and receive the CKC license (FairPlay license server URL) |
537
+ | **`assetId`** | <code>string</code> | Optional FairPlay asset identifier used when generating the SPC. Defaults to the `skd://` content identifier from the key request. |
505
538
 
506
539
 
507
540
  #### PlayreadyDrmOptions
@@ -1,3 +1,7 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
- >
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <application>
3
+ <meta-data
4
+ android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
5
+ android:value="com.capgo.videoplayer.VideoPlayerCastOptionsProvider" />
6
+ </application>
3
7
  </manifest>
@@ -55,6 +55,7 @@ import com.google.android.exoplayer2.MediaItem;
55
55
  import com.google.android.exoplayer2.MediaMetadata;
56
56
  import com.google.android.exoplayer2.PlaybackParameters;
57
57
  import com.google.android.exoplayer2.Player;
58
+ import com.google.android.exoplayer2.Tracks;
58
59
  import com.google.android.exoplayer2.audio.AudioAttributes;
59
60
  import com.google.android.exoplayer2.ext.cast.CastPlayer;
60
61
  import com.google.android.exoplayer2.ext.cast.DefaultMediaItemConverter;
@@ -110,6 +111,7 @@ import org.json.JSONObject;
110
111
  public class FullscreenExoPlayerFragment extends Fragment {
111
112
 
112
113
  public String videoPath;
114
+ public String chromecastUrl;
113
115
  public Float videoRate;
114
116
  public String playerId;
115
117
  public String subTitle;
@@ -207,6 +209,10 @@ public class FullscreenExoPlayerFragment extends Fragment {
207
209
  private CastStateListener castStateListener = null;
208
210
  private Boolean playerReady = false;
209
211
 
212
+ private boolean isChromecastEnabled() {
213
+ return Boolean.TRUE.equals(chromecast);
214
+ }
215
+
210
216
  /**
211
217
  * Create Fragment View
212
218
  * @param inflater
@@ -254,7 +260,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
254
260
  styledPlayerView.setUseController(true);
255
261
  }
256
262
 
257
- if (!chromecast) {
263
+ if (!isChromecastEnabled()) {
258
264
  mediaRouteButton.setVisibility(View.GONE);
259
265
  } else {
260
266
  initializeCastService();
@@ -318,6 +324,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
318
324
  }
319
325
  linearLayout.setVisibility(View.INVISIBLE);
320
326
  Log.v(TAG, "**** in ExoPlayer.STATE_READY firstReadyToPlay " + firstReadyToPlay);
327
+ updateSubtitleButtonVisibility(player.getCurrentTracks());
321
328
 
322
329
  if (firstReadyToPlay) {
323
330
  firstReadyToPlay = false;
@@ -377,6 +384,11 @@ public class FullscreenExoPlayerFragment extends Fragment {
377
384
  break;
378
385
  }
379
386
  }
387
+
388
+ @Override
389
+ public void onTracksChanged(Tracks tracks) {
390
+ updateSubtitleButtonVisibility(tracks);
391
+ }
380
392
  };
381
393
 
382
394
  if (isTV) {
@@ -708,7 +720,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
708
720
 
709
721
  if (styledPlayerView != null) {
710
722
  // If cast is playing then it doesn't start the local player once get backs from background
711
- if (castContext != null && chromecast && castPlayer.isCastSessionAvailable()) return;
723
+ if (isChromecastEnabled() && castContext != null && castPlayer != null && castPlayer.isCastSessionAvailable()) return;
712
724
 
713
725
  initializePlayer();
714
726
  if (player.getCurrentPosition() != 0) {
@@ -746,7 +758,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
746
758
  @Override
747
759
  public void onDestroy() {
748
760
  super.onDestroy();
749
- if (chromecast) mRouter.removeCallback(mCallback);
761
+ if (isChromecastEnabled() && mRouter != null) mRouter.removeCallback(mCallback);
750
762
  releasePlayer();
751
763
  }
752
764
 
@@ -756,7 +768,9 @@ public class FullscreenExoPlayerFragment extends Fragment {
756
768
  @Override
757
769
  public void onPause() {
758
770
  super.onPause();
759
- if (chromecast) castContext.removeCastStateListener(castStateListener);
771
+ if (isChromecastEnabled() && castContext != null && castStateListener != null) castContext.removeCastStateListener(
772
+ castStateListener
773
+ );
760
774
  boolean isAppBackground = false;
761
775
  if (bkModeEnabled) isAppBackground = isApplicationSentToBackground(context);
762
776
 
@@ -792,7 +806,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
792
806
  player = null;
793
807
  showSystemUI();
794
808
  resetVariables();
795
- if (chromecast) {
809
+ if (isChromecastEnabled() && castPlayer != null) {
796
810
  castPlayer.release();
797
811
  castPlayer = null;
798
812
  }
@@ -871,6 +885,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
871
885
  }
872
886
 
873
887
  styledPlayerView.setPlayer(player);
888
+ updateSubtitleButtonVisibility(player.getCurrentTracks());
874
889
 
875
890
  MediaSource mediaSource;
876
891
  if (!isInternal) {
@@ -911,6 +926,26 @@ public class FullscreenExoPlayerFragment extends Fragment {
911
926
  NotificationCenter.defaultCenter().postNotification("initializePlayer", info);
912
927
  }
913
928
 
929
+ private void updateSubtitleButtonVisibility(Tracks tracks) {
930
+ if (styledPlayerView == null) return;
931
+ if (sturi != null) {
932
+ styledPlayerView.setShowSubtitleButton(true);
933
+ return;
934
+ }
935
+ styledPlayerView.setShowSubtitleButton(hasSupportedTextTrack(tracks));
936
+ }
937
+
938
+ private boolean hasSupportedTextTrack(Tracks tracks) {
939
+ if (tracks == null) return false;
940
+ for (Tracks.Group group : tracks.getGroups()) {
941
+ if (group.getType() != C.TRACK_TYPE_TEXT) continue;
942
+ for (int i = 0; i < group.length; i++) {
943
+ if (group.isTrackSupported(i)) return true;
944
+ }
945
+ }
946
+ return false;
947
+ }
948
+
914
949
  private void setSubtitle(boolean transparent) {
915
950
  int foreground;
916
951
  int background;
@@ -1508,7 +1543,10 @@ public class FullscreenExoPlayerFragment extends Fragment {
1508
1543
  mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
1509
1544
  } else {
1510
1545
  Exception e = task.getException();
1511
- e.printStackTrace();
1546
+ Log.e(TAG, "Failed to initialize Chromecast", e);
1547
+ if (mediaRouteButton != null) {
1548
+ mediaRouteButton.setVisibility(View.GONE);
1549
+ }
1512
1550
  }
1513
1551
  }
1514
1552
  }
@@ -1518,6 +1556,9 @@ public class FullscreenExoPlayerFragment extends Fragment {
1518
1556
  private final class EmptyCallback extends MediaRouter.Callback {}
1519
1557
 
1520
1558
  private Uri getCastUri() {
1559
+ if (chromecastUrl != null && !chromecastUrl.isEmpty()) {
1560
+ return Uri.parse(chromecastUrl);
1561
+ }
1521
1562
  return uri != null ? uri : Uri.parse(videoPath);
1522
1563
  }
1523
1564
 
@@ -1594,6 +1635,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
1594
1635
 
1595
1636
  private static final String KEY_MEDIA_ITEM = "mediaItem";
1596
1637
  private static final String KEY_PLAYER_CONFIG = "exoPlayerConfig";
1638
+ private static final String KEY_WIDEVINE_LICENSE_URL = "laurl";
1597
1639
  private static final String KEY_MEDIA_ID = "mediaId";
1598
1640
  private static final String KEY_URI = "uri";
1599
1641
  private static final String KEY_TITLE = "title";
@@ -1712,6 +1754,11 @@ public class FullscreenExoPlayerFragment extends Fragment {
1712
1754
  if (playerConfig != null) {
1713
1755
  customData.put(KEY_PLAYER_CONFIG, playerConfig);
1714
1756
  }
1757
+ MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
1758
+ MediaItem.DrmConfiguration drmConfiguration = localConfiguration != null ? localConfiguration.drmConfiguration : null;
1759
+ if (drmConfiguration != null && C.WIDEVINE_UUID.equals(drmConfiguration.scheme) && drmConfiguration.licenseUri != null) {
1760
+ customData.put(KEY_WIDEVINE_LICENSE_URL, drmConfiguration.licenseUri.toString());
1761
+ }
1715
1762
  return customData;
1716
1763
  } catch (JSONException e) {
1717
1764
  throw new RuntimeException(e);
@@ -14,6 +14,7 @@ public class VideoPlayer {
14
14
 
15
15
  public FullscreenExoPlayerFragment createFullScreenFragment(
16
16
  String videoPath,
17
+ String chromecastUrl,
17
18
  Float videoRate,
18
19
  Boolean exitOnEnd,
19
20
  Boolean loopOnEnd,
@@ -39,6 +40,7 @@ public class VideoPlayer {
39
40
  FullscreenExoPlayerFragment fsFragment = new FullscreenExoPlayerFragment();
40
41
 
41
42
  fsFragment.videoPath = videoPath;
43
+ fsFragment.chromecastUrl = chromecastUrl;
42
44
  fsFragment.videoRate = videoRate;
43
45
  fsFragment.exitOnEnd = exitOnEnd;
44
46
  fsFragment.loopOnEnd = loopOnEnd;
@@ -0,0 +1,26 @@
1
+ package com.capgo.videoplayer;
2
+
3
+ import android.content.Context;
4
+ import com.google.android.gms.cast.CastMediaControlIntent;
5
+ import com.google.android.gms.cast.framework.CastOptions;
6
+ import com.google.android.gms.cast.framework.OptionsProvider;
7
+ import com.google.android.gms.cast.framework.SessionProvider;
8
+ import com.google.android.gms.cast.framework.media.CastMediaOptions;
9
+ import com.google.android.gms.cast.framework.media.NotificationOptions;
10
+ import java.util.List;
11
+
12
+ public class VideoPlayerCastOptionsProvider implements OptionsProvider {
13
+
14
+ @Override
15
+ public CastOptions getCastOptions(Context context) {
16
+ return new CastOptions.Builder()
17
+ .setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
18
+ .setCastMediaOptions(new CastMediaOptions.Builder().setNotificationOptions(new NotificationOptions.Builder().build()).build())
19
+ .build();
20
+ }
21
+
22
+ @Override
23
+ public List<SessionProvider> getAdditionalSessionProviders(Context context) {
24
+ return null;
25
+ }
26
+ }
@@ -9,7 +9,9 @@ import android.content.res.Configuration;
9
9
  import android.content.res.Configuration;
10
10
  import android.os.Build;
11
11
  import android.util.Log;
12
+ import android.view.View;
12
13
  import android.view.ViewGroup;
14
+ import android.view.WindowManager;
13
15
  import android.widget.FrameLayout;
14
16
  import android.widget.Toast;
15
17
  import androidx.fragment.app.Fragment;
@@ -39,7 +41,7 @@ import java.util.Map;
39
41
  )
40
42
  public class VideoPlayerPlugin extends Plugin {
41
43
 
42
- private final String pluginVersion = "8.1.22";
44
+ private final String pluginVersion = "8.1.24";
43
45
 
44
46
  // Permission alias constants
45
47
  private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
@@ -78,6 +80,7 @@ public class VideoPlayerPlugin extends Plugin {
78
80
  private Boolean chromecast = true;
79
81
  private String artwork;
80
82
  private String url;
83
+ private String chromecastUrl;
81
84
  private String playerId;
82
85
  private String subtitle = "";
83
86
  private String language = "";
@@ -187,6 +190,11 @@ public class VideoPlayerPlugin extends Plugin {
187
190
  call.resolve(ret);
188
191
  return;
189
192
  }
193
+ String _chromecastUrl = null;
194
+ if (call.getData().has("chromecastUrl")) {
195
+ _chromecastUrl = call.getString("chromecastUrl");
196
+ }
197
+ chromecastUrl = _chromecastUrl;
190
198
  if (call.getData().has("subtitle")) {
191
199
  subtitle = call.getString("subtitle");
192
200
  }
@@ -898,6 +906,76 @@ public class VideoPlayerPlugin extends Plugin {
898
906
  );
899
907
  }
900
908
 
909
+ @PluginMethod
910
+ public void hidePlayer(PluginCall call) {
911
+ this.call = call;
912
+ bridge
913
+ .getActivity()
914
+ .runOnUiThread(
915
+ new Runnable() {
916
+ @Override
917
+ public void run() {
918
+ JSObject ret = new JSObject();
919
+ ret.put("method", "hidePlayer");
920
+ FrameLayout frameLayoutView = getBridge().getActivity().findViewById(frameLayoutViewId);
921
+ if (frameLayoutView != null && fsFragment != null) {
922
+ frameLayoutView.setVisibility(View.GONE);
923
+ Activity activity = getBridge().getActivity();
924
+ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
925
+ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
926
+ activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
927
+ ret.put("result", true);
928
+ ret.put("value", true);
929
+ } else {
930
+ ret.put("result", false);
931
+ ret.put("message", "Fullscreen player is not defined");
932
+ }
933
+ call.resolve(ret);
934
+ }
935
+ }
936
+ );
937
+ }
938
+
939
+ @PluginMethod
940
+ public void showPlayer(PluginCall call) {
941
+ this.call = call;
942
+ bridge
943
+ .getActivity()
944
+ .runOnUiThread(
945
+ new Runnable() {
946
+ @Override
947
+ public void run() {
948
+ JSObject ret = new JSObject();
949
+ ret.put("method", "showPlayer");
950
+ FrameLayout frameLayoutView = getBridge().getActivity().findViewById(frameLayoutViewId);
951
+ if (frameLayoutView != null && fsFragment != null) {
952
+ frameLayoutView.setVisibility(View.VISIBLE);
953
+ Activity activity = getBridge().getActivity();
954
+ activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
955
+ activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
956
+ activity
957
+ .getWindow()
958
+ .getDecorView()
959
+ .setSystemUiVisibility(
960
+ View.SYSTEM_UI_FLAG_LOW_PROFILE |
961
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
962
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
963
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
964
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
965
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
966
+ );
967
+ ret.put("result", true);
968
+ ret.put("value", true);
969
+ } else {
970
+ ret.put("result", false);
971
+ ret.put("message", "Fullscreen player is not defined");
972
+ }
973
+ call.resolve(ret);
974
+ }
975
+ }
976
+ );
977
+ }
978
+
901
979
  public boolean isDeviceTV(Context context) {
902
980
  UiModeManager uiManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
903
981
  return uiManager != null && uiManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
@@ -922,6 +1000,7 @@ public class VideoPlayerPlugin extends Plugin {
922
1000
  createFullScreenFragment(
923
1001
  call,
924
1002
  videoPath,
1003
+ chromecastUrl,
925
1004
  videoRate,
926
1005
  exitOnEnd,
927
1006
  loopOnEnd,
@@ -1088,6 +1167,7 @@ public class VideoPlayerPlugin extends Plugin {
1088
1167
  createFullScreenFragment(
1089
1168
  call,
1090
1169
  videoPath,
1170
+ chromecastUrl,
1091
1171
  videoRate,
1092
1172
  exitOnEnd,
1093
1173
  loopOnEnd,
@@ -1128,6 +1208,7 @@ public class VideoPlayerPlugin extends Plugin {
1128
1208
  private void createFullScreenFragment(
1129
1209
  final PluginCall call,
1130
1210
  String videoPath,
1211
+ String chromecastUrl,
1131
1212
  Float videoRate,
1132
1213
  Boolean exitOnEnd,
1133
1214
  Boolean loopOnEnd,
@@ -1154,6 +1235,7 @@ public class VideoPlayerPlugin extends Plugin {
1154
1235
 
1155
1236
  fsFragment = implementation.createFullScreenFragment(
1156
1237
  videoPath,
1238
+ chromecastUrl,
1157
1239
  videoRate,
1158
1240
  exitOnEnd,
1159
1241
  loopOnEnd,
package/dist/docs.json CHANGED
@@ -299,6 +299,30 @@
299
299
  "capVideoPlayerResult"
300
300
  ],
301
301
  "slug": "exitplayer"
302
+ },
303
+ {
304
+ "name": "hidePlayer",
305
+ "signature": "() => Promise<capVideoPlayerResult>",
306
+ "parameters": [],
307
+ "returns": "Promise<capVideoPlayerResult>",
308
+ "tags": [],
309
+ "docs": "Hide the currently presented player UI without stopping playback (native fullscreen).\n\nCall `showPlayer()` to show it again.",
310
+ "complexTypes": [
311
+ "capVideoPlayerResult"
312
+ ],
313
+ "slug": "hideplayer"
314
+ },
315
+ {
316
+ "name": "showPlayer",
317
+ "signature": "() => Promise<capVideoPlayerResult>",
318
+ "parameters": [],
319
+ "returns": "Promise<capVideoPlayerResult>",
320
+ "tags": [],
321
+ "docs": "Show again a previously hidden player UI (native fullscreen).",
322
+ "complexTypes": [
323
+ "capVideoPlayerResult"
324
+ ],
325
+ "slug": "showplayer"
302
326
  }
303
327
  ],
304
328
  "properties": []
@@ -362,6 +386,13 @@
362
386
  "complexTypes": [],
363
387
  "type": "string | undefined"
364
388
  },
389
+ {
390
+ "name": "chromecastUrl",
391
+ "tags": [],
392
+ "docs": "Optional override URL used when casting to Chromecast (iOS, Android).\n\nThis lets you cast an alternative rendition/manifest (e.g. MPEG-DASH + Widevine)\nwhile keeping the on-device playback URL unchanged (e.g. HLS + FairPlay on iOS).",
393
+ "complexTypes": [],
394
+ "type": "string | undefined"
395
+ },
365
396
  {
366
397
  "name": "subtitle",
367
398
  "tags": [],
@@ -427,6 +458,13 @@
427
458
  "complexTypes": [],
428
459
  "type": "boolean | undefined"
429
460
  },
461
+ {
462
+ "name": "audioCategory",
463
+ "tags": [],
464
+ "docs": "iOS audio session category behavior (iOS).\n- \"ambient\": respects the iPhone silent switch (default)\n- \"playback\": ignores the iPhone silent switch\n- \"moviePlayback\": playback optimized for video and long-form AirPlay\ndefault: \"ambient\"",
465
+ "complexTypes": [],
466
+ "type": "'ambient' | 'playback' | 'moviePlayback' | undefined"
467
+ },
430
468
  {
431
469
  "name": "showControls",
432
470
  "tags": [],
@@ -507,7 +545,7 @@
507
545
  {
508
546
  "name": "drm",
509
547
  "tags": [],
510
- "docs": "DRM configuration for protected content (iOS: FairPlay, Android: Widevine)",
548
+ "docs": "DRM configuration for protected content (iOS: FairPlay, Android: Widevine)\n\nWhen casting, Widevine's `certificateUrl` (license URL) is also forwarded to Chromecast\nreceivers as `media.customData.laurl`.",
511
549
  "complexTypes": [
512
550
  "DrmOptions"
513
551
  ],
@@ -621,6 +659,13 @@
621
659
  "docs": "The URL to send the SPC and receive the CKC license (FairPlay license server URL)",
622
660
  "complexTypes": [],
623
661
  "type": "string | undefined"
662
+ },
663
+ {
664
+ "name": "assetId",
665
+ "tags": [],
666
+ "docs": "Optional FairPlay asset identifier used when generating the SPC.\nDefaults to the `skd://` content identifier from the key request.",
667
+ "complexTypes": [],
668
+ "type": "string | undefined"
624
669
  }
625
670
  ]
626
671
  },
@@ -84,6 +84,16 @@ export interface VideoPlayerPlugin {
84
84
  *
85
85
  */
86
86
  exitPlayer(): Promise<capVideoPlayerResult>;
87
+ /**
88
+ * Hide the currently presented player UI without stopping playback (native fullscreen).
89
+ *
90
+ * Call `showPlayer()` to show it again.
91
+ */
92
+ hidePlayer(): Promise<capVideoPlayerResult>;
93
+ /**
94
+ * Show again a previously hidden player UI (native fullscreen).
95
+ */
96
+ showPlayer(): Promise<capVideoPlayerResult>;
87
97
  }
88
98
  export interface capEchoOptions {
89
99
  /**
@@ -102,6 +112,13 @@ export interface capVideoPlayerOptions {
102
112
  * The url of the video to play
103
113
  */
104
114
  url?: string;
115
+ /**
116
+ * Optional override URL used when casting to Chromecast (iOS, Android).
117
+ *
118
+ * This lets you cast an alternative rendition/manifest (e.g. MPEG-DASH + Widevine)
119
+ * while keeping the on-device playback URL unchanged (e.g. HLS + FairPlay on iOS).
120
+ */
121
+ chromecastUrl?: string;
105
122
  /**
106
123
  * The url of subtitle associated with the video
107
124
  */
@@ -143,6 +160,14 @@ export interface capVideoPlayerOptions {
143
160
  * default: true
144
161
  */
145
162
  bkmodeEnabled?: boolean;
163
+ /**
164
+ * iOS audio session category behavior (iOS).
165
+ * - "ambient": respects the iPhone silent switch (default)
166
+ * - "playback": ignores the iPhone silent switch
167
+ * - "moviePlayback": playback optimized for video and long-form AirPlay
168
+ * default: "ambient"
169
+ */
170
+ audioCategory?: 'ambient' | 'playback' | 'moviePlayback';
146
171
  /**
147
172
  * Show Controls Enable (iOS, Android)
148
173
  * default: true
@@ -204,6 +229,9 @@ export interface capVideoPlayerOptions {
204
229
  artwork?: string;
205
230
  /**
206
231
  * DRM configuration for protected content (iOS: FairPlay, Android: Widevine)
232
+ *
233
+ * When casting, Widevine's `certificateUrl` (license URL) is also forwarded to Chromecast
234
+ * receivers as `media.customData.laurl`.
207
235
  */
208
236
  drm?: DrmOptions;
209
237
  }
@@ -323,6 +351,11 @@ export interface FairPlayDrmOptions {
323
351
  * The URL to send the SPC and receive the CKC license (FairPlay license server URL)
324
352
  */
325
353
  contentKeySpcUrl?: string;
354
+ /**
355
+ * Optional FairPlay asset identifier used when generating the SPC.
356
+ * Defaults to the `skd://` content identifier from the key request.
357
+ */
358
+ assetId?: string;
326
359
  }
327
360
  export interface PlayreadyDrmOptions {
328
361
  /**