@capgo/capacitor-video-player 8.2.14 → 8.2.15
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 +44 -28
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +1 -1
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +5 -3
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/VideoPlayerPlugin/FullscreenVideoPlayer.swift +28 -33
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerAudioSessionCoordinator.swift +101 -0
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +3 -1
- package/ios/Tests/VideoPlayerPluginTests/VideoPlayerAudioSessionCategoryPolicyTests.swift +52 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,6 +149,22 @@ Picture in Picture on Android requires your app activity to declare PiP support
|
|
|
149
149
|
|
|
150
150
|
`launchMode="singleTask"` and the `configChanges` flags prevent the player activity from being recreated when entering or leaving PiP.
|
|
151
151
|
|
|
152
|
+
## iOS Picture in Picture and background audio
|
|
153
|
+
|
|
154
|
+
On iOS the native player configures `AVAudioSession` for PiP and background playback when `pipEnabled` or `bkmodeEnabled` is true (both default to true). Your app still needs the right plist and capability entries:
|
|
155
|
+
|
|
156
|
+
1. In Xcode, add the **Background Modes** capability and enable **Audio, AirPlay, and Picture in Picture**.
|
|
157
|
+
2. In `ios/App/App/Info.plist`, include the audio background mode:
|
|
158
|
+
|
|
159
|
+
```xml
|
|
160
|
+
<key>UIBackgroundModes</key>
|
|
161
|
+
<array>
|
|
162
|
+
<string>audio</string>
|
|
163
|
+
</array>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Test PiP on a physical iPhone (the Simulator does not support Picture in Picture). Setting `audioCategory: 'ambient'` respects the silent switch but silences audio on screen lock; leave it unset or use `playback` for audible background playback.
|
|
167
|
+
|
|
152
168
|
## API
|
|
153
169
|
|
|
154
170
|
<docgen-index>
|
|
@@ -510,34 +526,34 @@ On web, the implementation returns `"web"`.
|
|
|
510
526
|
|
|
511
527
|
#### capVideoPlayerOptions
|
|
512
528
|
|
|
513
|
-
| Prop | Type | Description
|
|
514
|
-
| --------------------- | ----------------------------------------------------------- |
|
|
515
|
-
| **`mode`** | <code>string</code> | Player mode - "fullscreen" - "embedded" (Web only)
|
|
516
|
-
| **`url`** | <code>string</code> | The url of the video to play
|
|
517
|
-
| **`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).
|
|
518
|
-
| **`subtitle`** | <code>string</code> | The url of subtitle associated with the video Prefer `subtitles` when providing more than one track. Kept for backward compatibility.
|
|
519
|
-
| **`language`** | <code>string</code> | The language of subtitle see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers Prefer `subtitles` when providing more than one track. Kept for backward compatibility.
|
|
520
|
-
| **`subtitles`** | <code>VideoSubtitle[]</code> | Multiple subtitle tracks (iOS, Android). When provided, takes precedence over `subtitle` / `language`.
|
|
521
|
-
| **`subtitleOptions`** | <code><a href="#subtitleoptions">SubTitleOptions</a></code> | SubTitle Options
|
|
522
|
-
| **`playerId`** | <code>string</code> | Id of DIV Element parent of the player
|
|
523
|
-
| **`rate`** | <code>number</code> | Initial playing rate
|
|
524
|
-
| **`exitOnEnd`** | <code>boolean</code> | Exit on VideoEnd (iOS, Android) default: true
|
|
525
|
-
| **`loopOnEnd`** | <code>boolean</code> | Loop on VideoEnd when exitOnEnd false (iOS, Android) default: false
|
|
526
|
-
| **`pipEnabled`** | <code>boolean</code> | Picture in Picture Enable (iOS, Android) default: true
|
|
527
|
-
| **`bkmodeEnabled`** | <code>boolean</code> | Background Mode Enable (iOS, Android) default: true
|
|
528
|
-
| **`audioCategory`** | <code>'ambient' \| 'playback' \| 'moviePlayback'</code> | iOS audio session category behavior (iOS). - "ambient": respects the iPhone silent switch
|
|
529
|
-
| **`showControls`** | <code>boolean</code> | Show Controls Enable (iOS, Android) default: true
|
|
530
|
-
| **`displayMode`** | <code>string</code> | Display Mode ["all", "portrait", "landscape"] (iOS, Android) default: "all"
|
|
531
|
-
| **`componentTag`** | <code>string</code> | Component Tag or DOM Element Tag (React app)
|
|
532
|
-
| **`width`** | <code>number</code> | Player Width (mode "embedded" only)
|
|
533
|
-
| **`height`** | <code>number</code> | Player height (mode "embedded" only)
|
|
534
|
-
| **`headers`** | <code>{ [key: string]: string; }</code> | Headers for the request (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX)
|
|
535
|
-
| **`title`** | <code>string</code> | Title shown in the player and Chromecast metadata (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX)
|
|
536
|
-
| **`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)
|
|
537
|
-
| **`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
|
|
538
|
-
| **`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
|
|
539
|
-
| **`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: ""
|
|
540
|
-
| **`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`.
|
|
529
|
+
| Prop | Type | Description |
|
|
530
|
+
| --------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
531
|
+
| **`mode`** | <code>string</code> | Player mode - "fullscreen" - "embedded" (Web only) |
|
|
532
|
+
| **`url`** | <code>string</code> | The url of the video to play |
|
|
533
|
+
| **`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). |
|
|
534
|
+
| **`subtitle`** | <code>string</code> | The url of subtitle associated with the video Prefer `subtitles` when providing more than one track. Kept for backward compatibility. |
|
|
535
|
+
| **`language`** | <code>string</code> | The language of subtitle see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers Prefer `subtitles` when providing more than one track. Kept for backward compatibility. |
|
|
536
|
+
| **`subtitles`** | <code>VideoSubtitle[]</code> | Multiple subtitle tracks (iOS, Android). When provided, takes precedence over `subtitle` / `language`. |
|
|
537
|
+
| **`subtitleOptions`** | <code><a href="#subtitleoptions">SubTitleOptions</a></code> | SubTitle Options |
|
|
538
|
+
| **`playerId`** | <code>string</code> | Id of DIV Element parent of the player |
|
|
539
|
+
| **`rate`** | <code>number</code> | Initial playing rate |
|
|
540
|
+
| **`exitOnEnd`** | <code>boolean</code> | Exit on VideoEnd (iOS, Android) default: true |
|
|
541
|
+
| **`loopOnEnd`** | <code>boolean</code> | Loop on VideoEnd when exitOnEnd false (iOS, Android) default: false |
|
|
542
|
+
| **`pipEnabled`** | <code>boolean</code> | Picture in Picture Enable (iOS, Android) default: true |
|
|
543
|
+
| **`bkmodeEnabled`** | <code>boolean</code> | Background Mode Enable (iOS, Android) default: true |
|
|
544
|
+
| **`audioCategory`** | <code>'ambient' \| 'playback' \| 'moviePlayback'</code> | iOS audio session category behavior (iOS). - "ambient": respects the iPhone silent switch - "playback": ignores the iPhone silent switch - "moviePlayback": playback optimized for video and long-form AirPlay. When omitted, the plugin resolves the category from other options: `"playback"` if `pipEnabled` or `bkmodeEnabled` is true (both default to true), otherwise `"ambient"`. |
|
|
545
|
+
| **`showControls`** | <code>boolean</code> | Show Controls Enable (iOS, Android) default: true |
|
|
546
|
+
| **`displayMode`** | <code>string</code> | Display Mode ["all", "portrait", "landscape"] (iOS, Android) default: "all" |
|
|
547
|
+
| **`componentTag`** | <code>string</code> | Component Tag or DOM Element Tag (React app) |
|
|
548
|
+
| **`width`** | <code>number</code> | Player Width (mode "embedded" only) |
|
|
549
|
+
| **`height`** | <code>number</code> | Player height (mode "embedded" only) |
|
|
550
|
+
| **`headers`** | <code>{ [key: string]: string; }</code> | Headers for the request (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
|
|
551
|
+
| **`title`** | <code>string</code> | Title shown in the player and Chromecast metadata (iOS, Android) by Manuel García Marín (https://github.com/PhantomPainX) |
|
|
552
|
+
| **`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) |
|
|
553
|
+
| **`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 |
|
|
554
|
+
| **`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 |
|
|
555
|
+
| **`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: "" |
|
|
556
|
+
| **`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`. |
|
|
541
557
|
|
|
542
558
|
|
|
543
559
|
#### VideoSubtitle
|
|
@@ -46,7 +46,7 @@ import org.json.JSONObject;
|
|
|
46
46
|
)
|
|
47
47
|
public class VideoPlayerPlugin extends Plugin {
|
|
48
48
|
|
|
49
|
-
private final String pluginVersion = "8.2.
|
|
49
|
+
private final String pluginVersion = "8.2.15";
|
|
50
50
|
|
|
51
51
|
// Permission alias constants
|
|
52
52
|
private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
|
package/dist/docs.json
CHANGED
|
@@ -485,7 +485,7 @@
|
|
|
485
485
|
{
|
|
486
486
|
"name": "audioCategory",
|
|
487
487
|
"tags": [],
|
|
488
|
-
"docs": "iOS audio session category behavior (iOS).\n- \"ambient\": respects the iPhone silent switch
|
|
488
|
+
"docs": "iOS audio session category behavior (iOS).\n- \"ambient\": respects the iPhone silent switch\n- \"playback\": ignores the iPhone silent switch\n- \"moviePlayback\": playback optimized for video and long-form AirPlay.\n\nWhen omitted, the plugin resolves the category from other options: `\"playback\"` if\n`pipEnabled` or `bkmodeEnabled` is true (both default to true), otherwise `\"ambient\"`.",
|
|
489
489
|
"complexTypes": [],
|
|
490
490
|
"type": "'ambient' | 'playback' | 'moviePlayback' | undefined"
|
|
491
491
|
},
|
|
@@ -182,10 +182,12 @@ export interface capVideoPlayerOptions {
|
|
|
182
182
|
bkmodeEnabled?: boolean;
|
|
183
183
|
/**
|
|
184
184
|
* iOS audio session category behavior (iOS).
|
|
185
|
-
* - "ambient": respects the iPhone silent switch
|
|
185
|
+
* - "ambient": respects the iPhone silent switch
|
|
186
186
|
* - "playback": ignores the iPhone silent switch
|
|
187
|
-
* - "moviePlayback": playback optimized for video and long-form AirPlay
|
|
188
|
-
*
|
|
187
|
+
* - "moviePlayback": playback optimized for video and long-form AirPlay.
|
|
188
|
+
*
|
|
189
|
+
* When omitted, the plugin resolves the category from other options: `"playback"` if
|
|
190
|
+
* `pipEnabled` or `bkmodeEnabled` is true (both default to true), otherwise `"ambient"`.
|
|
189
191
|
*/
|
|
190
192
|
audioCategory?: 'ambient' | 'playback' | 'moviePlayback';
|
|
191
193
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface VideoPlayerPlugin {\n /**\n * Initialize a video player\n *\n */\n initPlayer(options: capVideoPlayerOptions): Promise<capVideoPlayerResult>;\n /**\n * Return if a given playerId is playing\n *\n */\n isPlaying(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Play the current video from a given playerId\n *\n */\n play(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Pause the current video from a given playerId\n *\n */\n pause(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the duration of the current video from a given playerId\n *\n */\n getDuration(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the current time of the current video from a given playerId\n *\n */\n getCurrentTime(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the current time to seek the current video to from a given playerId\n *\n */\n setCurrentTime(options: capVideoTimeOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the volume of the current video from a given playerId\n *\n */\n getVolume(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the volume of the current video to from a given playerId\n *\n */\n setVolume(options: capVideoVolumeOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the muted of the current video from a given playerId\n *\n */\n getMuted(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the muted of the current video to from a given playerId\n *\n */\n setMuted(options: capVideoMutedOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the rate of the current video from a given playerId\n *\n */\n setRate(options: capVideoRateOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the rate of the current video from a given playerId\n *\n */\n getRate(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Stop all players playing\n *\n */\n stopAllPlayers(): Promise<capVideoPlayerResult>;\n /**\n * Show controller\n *\n */\n showController(): Promise<capVideoPlayerResult>;\n /**\n * isControllerIsFullyVisible\n *\n */\n isControllerIsFullyVisible(): Promise<capVideoPlayerResult>;\n /**\n * Exit player\n *\n */\n exitPlayer(): Promise<capVideoPlayerResult>;\n /**\n * Hide the currently presented player UI without stopping playback (native fullscreen).\n *\n * Call `showPlayer()` to show it again.\n */\n hidePlayer(): Promise<capVideoPlayerResult>;\n /**\n * Show again a previously hidden player UI (native fullscreen).\n */\n showPlayer(): Promise<capVideoPlayerResult>;\n /**\n * Get the native Capacitor plugin version.\n * On web, the implementation returns `\"web\"`.\n *\n * @returns Promise resolving to the plugin version string for this build (`\"web\"` on web)\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\nexport interface capEchoOptions {\n /**\n * String to be echoed\n */\n\n value?: string;\n}\nexport interface capVideoPlayerOptions {\n /**\n * Player mode\n * - \"fullscreen\"\n * - \"embedded\" (Web only)\n */\n mode?: string;\n /**\n * The url of the video to play\n */\n url?: string;\n /**\n * Optional override URL used when casting to Chromecast (iOS, Android).\n *\n * This lets you cast an alternative rendition/manifest (e.g. MPEG-DASH + Widevine)\n * while keeping the on-device playback URL unchanged (e.g. HLS + FairPlay on iOS).\n */\n chromecastUrl?: string;\n /**\n * The url of subtitle associated with the video\n *\n * Prefer `subtitles` when providing more than one track.\n * Kept for backward compatibility.\n */\n subtitle?: string;\n /**\n * The language of subtitle\n * see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers\n *\n * Prefer `subtitles` when providing more than one track.\n * Kept for backward compatibility.\n */\n language?: string;\n /**\n * Multiple subtitle tracks (iOS, Android).\n * When provided, takes precedence over `subtitle` / `language`.\n */\n subtitles?: VideoSubtitle[];\n /**\n * SubTitle Options\n */\n subtitleOptions?: SubTitleOptions;\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Initial playing rate\n */\n rate?: number;\n /**\n * Exit on VideoEnd (iOS, Android)\n * default: true\n */\n exitOnEnd?: boolean;\n /**\n * Loop on VideoEnd when exitOnEnd false (iOS, Android)\n * default: false\n */\n loopOnEnd?: boolean;\n /**\n * Picture in Picture Enable (iOS, Android)\n * default: true\n */\n pipEnabled?: boolean;\n /**\n * Background Mode Enable (iOS, Android)\n * default: true\n */\n bkmodeEnabled?: boolean;\n /**\n * 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\n * default: \"ambient\"\n */\n audioCategory?: 'ambient' | 'playback' | 'moviePlayback';\n /**\n * Show Controls Enable (iOS, Android)\n * default: true\n */\n showControls?: boolean;\n /**\n * Display Mode [\"all\", \"portrait\", \"landscape\"] (iOS, Android)\n * default: \"all\"\n */\n displayMode?: string;\n /**\n * Component Tag or DOM Element Tag (React app)\n */\n componentTag?: string;\n /**\n * Player Width (mode \"embedded\" only)\n */\n width?: number;\n /**\n * Player height (mode \"embedded\" only)\n */\n height?: number;\n /**\n * Headers for the request (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n */\n headers?: {\n [key: string]: string;\n };\n /**\n * Title shown in the player and Chromecast metadata (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n */\n title?: string;\n /**\n * Subtitle shown below the title in the player and Chromecast metadata (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n */\n smallTitle?: string;\n /**\n * ExoPlayer Progress Bar and Spinner color (Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n * Must be a valid hex color code\n * default: #FFFFFF\n */\n accentColor?: string;\n /**\n * Chromecast enable/disable (iOS, Android)\n * iOS requires Google Cast SDK setup and local network Info.plist keys.\n * by Manuel García Marín (https://github.com/PhantomPainX)\n * default: true\n */\n chromecast?: boolean;\n /**\n * Artwork url to be shown in Chromecast player (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n * default: \"\"\n */\n artwork?: string;\n /**\n * DRM configuration for protected content (iOS: FairPlay, Android: Widevine)\n *\n * When casting, Widevine's `certificateUrl` (license URL) is also forwarded to Chromecast\n * receivers as `media.customData.laurl`.\n */\n drm?: DrmOptions;\n}\nexport interface capVideoPlayerIdOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n}\nexport interface capVideoRateOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Rate value\n */\n rate?: number;\n}\nexport interface capVideoVolumeOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Volume value between [0 - 1]\n */\n volume?: number;\n}\nexport interface capVideoTimeOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Video time value you want to seek to\n */\n seektime?: number;\n}\nexport interface capVideoMutedOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Muted value true or false\n */\n muted?: boolean;\n}\nexport interface capVideoListener {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Video current time when listener triggered\n */\n currentTime?: number;\n}\nexport interface capExitListener {\n /**\n * Dismiss value true or false\n */\n dismiss?: boolean;\n /**\n * Video current time when listener triggered\n */\n currentTime?: number;\n}\nexport interface capVideoPlayerResult {\n /**\n * result set to true when successful else false\n */\n result?: boolean;\n /**\n * method name\n */\n method?: string;\n /**\n * value returned\n */\n value?: any;\n /**\n * message string\n */\n message?: string;\n}\nexport interface VideoSubtitle {\n /**\n * The url of the subtitle file (vtt, srt, ...)\n */\n subtitle: string;\n /**\n * The language of the subtitle\n * see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers\n */\n language?: string;\n}\nexport interface SubTitleOptions {\n /**\n * Foreground Color in RGBA (default rgba(255,255,255,1)\n */\n foregroundColor?: string;\n /**\n * Background Color in RGBA (default rgba(0,0,0,1)\n */\n backgroundColor?: string;\n /**\n * Font Size in pixels (default 16)\n */\n fontSize?: number;\n}\nexport interface FairPlayDrmOptions {\n /**\n * The URL to fetch the FairPlay certificate\n */\n certificateUrl?: string;\n /**\n * The URL to send the SPC and receive the CKC license (FairPlay license server URL)\n */\n contentKeySpcUrl?: string;\n /**\n * Optional FairPlay asset identifier used when generating the SPC.\n * Defaults to the `skd://` content identifier from the key request.\n */\n assetId?: string;\n}\nexport interface PlayreadyDrmOptions {\n /**\n * The URL to fetch the PlayReady license\n */\n certificateUrl?: string;\n}\nexport interface WidevineDrmOptions {\n /**\n * The URL to fetch the Widevine license\n */\n certificateUrl?: string;\n}\nexport interface DrmOptions {\n /**\n * FairPlay DRM configuration (iOS)\n */\n fairplay?: FairPlayDrmOptions;\n /**\n * PlayReady DRM configuration\n */\n playready?: PlayreadyDrmOptions;\n /**\n * Widevine DRM configuration (Android)\n */\n widevine?: WidevineDrmOptions;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface VideoPlayerPlugin {\n /**\n * Initialize a video player\n *\n */\n initPlayer(options: capVideoPlayerOptions): Promise<capVideoPlayerResult>;\n /**\n * Return if a given playerId is playing\n *\n */\n isPlaying(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Play the current video from a given playerId\n *\n */\n play(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Pause the current video from a given playerId\n *\n */\n pause(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the duration of the current video from a given playerId\n *\n */\n getDuration(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the current time of the current video from a given playerId\n *\n */\n getCurrentTime(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the current time to seek the current video to from a given playerId\n *\n */\n setCurrentTime(options: capVideoTimeOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the volume of the current video from a given playerId\n *\n */\n getVolume(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the volume of the current video to from a given playerId\n *\n */\n setVolume(options: capVideoVolumeOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the muted of the current video from a given playerId\n *\n */\n getMuted(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the muted of the current video to from a given playerId\n *\n */\n setMuted(options: capVideoMutedOptions): Promise<capVideoPlayerResult>;\n /**\n * Set the rate of the current video from a given playerId\n *\n */\n setRate(options: capVideoRateOptions): Promise<capVideoPlayerResult>;\n /**\n * Get the rate of the current video from a given playerId\n *\n */\n getRate(options: capVideoPlayerIdOptions): Promise<capVideoPlayerResult>;\n /**\n * Stop all players playing\n *\n */\n stopAllPlayers(): Promise<capVideoPlayerResult>;\n /**\n * Show controller\n *\n */\n showController(): Promise<capVideoPlayerResult>;\n /**\n * isControllerIsFullyVisible\n *\n */\n isControllerIsFullyVisible(): Promise<capVideoPlayerResult>;\n /**\n * Exit player\n *\n */\n exitPlayer(): Promise<capVideoPlayerResult>;\n /**\n * Hide the currently presented player UI without stopping playback (native fullscreen).\n *\n * Call `showPlayer()` to show it again.\n */\n hidePlayer(): Promise<capVideoPlayerResult>;\n /**\n * Show again a previously hidden player UI (native fullscreen).\n */\n showPlayer(): Promise<capVideoPlayerResult>;\n /**\n * Get the native Capacitor plugin version.\n * On web, the implementation returns `\"web\"`.\n *\n * @returns Promise resolving to the plugin version string for this build (`\"web\"` on web)\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\nexport interface capEchoOptions {\n /**\n * String to be echoed\n */\n\n value?: string;\n}\nexport interface capVideoPlayerOptions {\n /**\n * Player mode\n * - \"fullscreen\"\n * - \"embedded\" (Web only)\n */\n mode?: string;\n /**\n * The url of the video to play\n */\n url?: string;\n /**\n * Optional override URL used when casting to Chromecast (iOS, Android).\n *\n * This lets you cast an alternative rendition/manifest (e.g. MPEG-DASH + Widevine)\n * while keeping the on-device playback URL unchanged (e.g. HLS + FairPlay on iOS).\n */\n chromecastUrl?: string;\n /**\n * The url of subtitle associated with the video\n *\n * Prefer `subtitles` when providing more than one track.\n * Kept for backward compatibility.\n */\n subtitle?: string;\n /**\n * The language of subtitle\n * see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers\n *\n * Prefer `subtitles` when providing more than one track.\n * Kept for backward compatibility.\n */\n language?: string;\n /**\n * Multiple subtitle tracks (iOS, Android).\n * When provided, takes precedence over `subtitle` / `language`.\n */\n subtitles?: VideoSubtitle[];\n /**\n * SubTitle Options\n */\n subtitleOptions?: SubTitleOptions;\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Initial playing rate\n */\n rate?: number;\n /**\n * Exit on VideoEnd (iOS, Android)\n * default: true\n */\n exitOnEnd?: boolean;\n /**\n * Loop on VideoEnd when exitOnEnd false (iOS, Android)\n * default: false\n */\n loopOnEnd?: boolean;\n /**\n * Picture in Picture Enable (iOS, Android)\n * default: true\n */\n pipEnabled?: boolean;\n /**\n * Background Mode Enable (iOS, Android)\n * default: true\n */\n bkmodeEnabled?: boolean;\n /**\n * iOS audio session category behavior (iOS).\n * - \"ambient\": respects the iPhone silent switch\n * - \"playback\": ignores the iPhone silent switch\n * - \"moviePlayback\": playback optimized for video and long-form AirPlay.\n *\n * When omitted, the plugin resolves the category from other options: `\"playback\"` if\n * `pipEnabled` or `bkmodeEnabled` is true (both default to true), otherwise `\"ambient\"`.\n */\n audioCategory?: 'ambient' | 'playback' | 'moviePlayback';\n /**\n * Show Controls Enable (iOS, Android)\n * default: true\n */\n showControls?: boolean;\n /**\n * Display Mode [\"all\", \"portrait\", \"landscape\"] (iOS, Android)\n * default: \"all\"\n */\n displayMode?: string;\n /**\n * Component Tag or DOM Element Tag (React app)\n */\n componentTag?: string;\n /**\n * Player Width (mode \"embedded\" only)\n */\n width?: number;\n /**\n * Player height (mode \"embedded\" only)\n */\n height?: number;\n /**\n * Headers for the request (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n */\n headers?: {\n [key: string]: string;\n };\n /**\n * Title shown in the player and Chromecast metadata (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n */\n title?: string;\n /**\n * Subtitle shown below the title in the player and Chromecast metadata (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n */\n smallTitle?: string;\n /**\n * ExoPlayer Progress Bar and Spinner color (Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n * Must be a valid hex color code\n * default: #FFFFFF\n */\n accentColor?: string;\n /**\n * Chromecast enable/disable (iOS, Android)\n * iOS requires Google Cast SDK setup and local network Info.plist keys.\n * by Manuel García Marín (https://github.com/PhantomPainX)\n * default: true\n */\n chromecast?: boolean;\n /**\n * Artwork url to be shown in Chromecast player (iOS, Android)\n * by Manuel García Marín (https://github.com/PhantomPainX)\n * default: \"\"\n */\n artwork?: string;\n /**\n * DRM configuration for protected content (iOS: FairPlay, Android: Widevine)\n *\n * When casting, Widevine's `certificateUrl` (license URL) is also forwarded to Chromecast\n * receivers as `media.customData.laurl`.\n */\n drm?: DrmOptions;\n}\nexport interface capVideoPlayerIdOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n}\nexport interface capVideoRateOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Rate value\n */\n rate?: number;\n}\nexport interface capVideoVolumeOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Volume value between [0 - 1]\n */\n volume?: number;\n}\nexport interface capVideoTimeOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Video time value you want to seek to\n */\n seektime?: number;\n}\nexport interface capVideoMutedOptions {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Muted value true or false\n */\n muted?: boolean;\n}\nexport interface capVideoListener {\n /**\n * Id of DIV Element parent of the player\n */\n playerId?: string;\n /**\n * Video current time when listener triggered\n */\n currentTime?: number;\n}\nexport interface capExitListener {\n /**\n * Dismiss value true or false\n */\n dismiss?: boolean;\n /**\n * Video current time when listener triggered\n */\n currentTime?: number;\n}\nexport interface capVideoPlayerResult {\n /**\n * result set to true when successful else false\n */\n result?: boolean;\n /**\n * method name\n */\n method?: string;\n /**\n * value returned\n */\n value?: any;\n /**\n * message string\n */\n message?: string;\n}\nexport interface VideoSubtitle {\n /**\n * The url of the subtitle file (vtt, srt, ...)\n */\n subtitle: string;\n /**\n * The language of the subtitle\n * see https://github.com/libyal/libfwnt/wiki/Language-Code-identifiers\n */\n language?: string;\n}\nexport interface SubTitleOptions {\n /**\n * Foreground Color in RGBA (default rgba(255,255,255,1)\n */\n foregroundColor?: string;\n /**\n * Background Color in RGBA (default rgba(0,0,0,1)\n */\n backgroundColor?: string;\n /**\n * Font Size in pixels (default 16)\n */\n fontSize?: number;\n}\nexport interface FairPlayDrmOptions {\n /**\n * The URL to fetch the FairPlay certificate\n */\n certificateUrl?: string;\n /**\n * The URL to send the SPC and receive the CKC license (FairPlay license server URL)\n */\n contentKeySpcUrl?: string;\n /**\n * Optional FairPlay asset identifier used when generating the SPC.\n * Defaults to the `skd://` content identifier from the key request.\n */\n assetId?: string;\n}\nexport interface PlayreadyDrmOptions {\n /**\n * The URL to fetch the PlayReady license\n */\n certificateUrl?: string;\n}\nexport interface WidevineDrmOptions {\n /**\n * The URL to fetch the Widevine license\n */\n certificateUrl?: string;\n}\nexport interface DrmOptions {\n /**\n * FairPlay DRM configuration (iOS)\n */\n fairplay?: FairPlayDrmOptions;\n /**\n * PlayReady DRM configuration\n */\n playready?: PlayreadyDrmOptions;\n /**\n * Widevine DRM configuration (Android)\n */\n widevine?: WidevineDrmOptions;\n}\n"]}
|
|
@@ -13,6 +13,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
13
13
|
private var exitOnEnd: Bool
|
|
14
14
|
private var loopOnEnd: Bool
|
|
15
15
|
private var pipEnabled: Bool
|
|
16
|
+
private var bkmodeEnabled: Bool
|
|
16
17
|
private var showControls: Bool
|
|
17
18
|
private var chromecast: Bool
|
|
18
19
|
private var chromecastUrl: String?
|
|
@@ -21,7 +22,8 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
21
22
|
private var artwork: String?
|
|
22
23
|
private var rate: Float
|
|
23
24
|
private var audioCategory: String?
|
|
24
|
-
private
|
|
25
|
+
private let audioSessionRegistrationId = UUID()
|
|
26
|
+
private var holdsSharedAudioSession = false
|
|
25
27
|
private var didEmitReady: Bool = false
|
|
26
28
|
private var exitEmission = ExitEmissionGuard()
|
|
27
29
|
private var isTransitioningToPictureInPicture = false
|
|
@@ -54,6 +56,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
54
56
|
exitOnEnd: Bool,
|
|
55
57
|
loopOnEnd: Bool,
|
|
56
58
|
pipEnabled: Bool,
|
|
59
|
+
bkmodeEnabled: Bool = true,
|
|
57
60
|
showControls: Bool,
|
|
58
61
|
chromecast: Bool,
|
|
59
62
|
chromecastUrl: String? = nil,
|
|
@@ -73,6 +76,7 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
73
76
|
self.exitOnEnd = exitOnEnd
|
|
74
77
|
self.loopOnEnd = loopOnEnd
|
|
75
78
|
self.pipEnabled = pipEnabled
|
|
79
|
+
self.bkmodeEnabled = bkmodeEnabled
|
|
76
80
|
self.showControls = showControls
|
|
77
81
|
self.chromecast = chromecast
|
|
78
82
|
self.chromecastUrl = chromecastUrl
|
|
@@ -780,45 +784,36 @@ class FullscreenVideoPlayer: NSObject {
|
|
|
780
784
|
}
|
|
781
785
|
|
|
782
786
|
private func configureAudioSession() {
|
|
783
|
-
guard let audioCategory else { return }
|
|
784
|
-
|
|
785
|
-
let session = AVAudioSession.sharedInstance()
|
|
786
787
|
do {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
case "moviePlayback":
|
|
793
|
-
if #available(iOS 13.0, *) {
|
|
794
|
-
try session.setCategory(
|
|
795
|
-
.playback,
|
|
796
|
-
mode: .moviePlayback,
|
|
797
|
-
policy: .longFormVideo,
|
|
798
|
-
options: [.mixWithOthers]
|
|
799
|
-
)
|
|
800
|
-
} else {
|
|
801
|
-
try session.setCategory(.playback, mode: .moviePlayback, options: [.mixWithOthers])
|
|
802
|
-
}
|
|
803
|
-
default:
|
|
804
|
-
return
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
try session.setActive(true)
|
|
808
|
-
didActivateAudioSession = true
|
|
788
|
+
try VideoPlayerAudioSessionCoordinator.acquire(
|
|
789
|
+
registrationId: audioSessionRegistrationId,
|
|
790
|
+
needs: currentAudioSessionNeeds()
|
|
791
|
+
)
|
|
792
|
+
holdsSharedAudioSession = true
|
|
809
793
|
} catch {
|
|
794
|
+
holdsSharedAudioSession = false
|
|
810
795
|
print("Error configuring AVAudioSession: \(error)")
|
|
811
796
|
}
|
|
812
797
|
}
|
|
813
798
|
|
|
814
|
-
private func
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
|
|
799
|
+
private func currentAudioSessionNeeds() -> VideoPlayerAudioSessionNeeds {
|
|
800
|
+
let explicitCategory: String?
|
|
801
|
+
if let audioCategory {
|
|
802
|
+
let trimmed = audioCategory.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
803
|
+
explicitCategory = trimmed.isEmpty ? nil : trimmed
|
|
804
|
+
} else {
|
|
805
|
+
explicitCategory = nil
|
|
821
806
|
}
|
|
807
|
+
return VideoPlayerAudioSessionNeeds(
|
|
808
|
+
explicitCategory: explicitCategory,
|
|
809
|
+
needsBackgroundOrPipPlayback: bkmodeEnabled || pipEnabled
|
|
810
|
+
)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private func deactivateAudioSessionIfNeeded() {
|
|
814
|
+
guard holdsSharedAudioSession else { return }
|
|
815
|
+
VideoPlayerAudioSessionCoordinator.release(registrationId: audioSessionRegistrationId)
|
|
816
|
+
holdsSharedAudioSession = false
|
|
822
817
|
}
|
|
823
818
|
|
|
824
819
|
private func configurePresentationDelegate(for playerVC: UIViewController) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
struct VideoPlayerAudioSessionNeeds: Equatable {
|
|
5
|
+
let explicitCategory: String?
|
|
6
|
+
let needsBackgroundOrPipPlayback: Bool
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
enum VideoPlayerAudioSessionCategoryPolicy {
|
|
10
|
+
static func category(for needs: VideoPlayerAudioSessionNeeds) -> String {
|
|
11
|
+
if let explicitCategory = needs.explicitCategory {
|
|
12
|
+
let trimmed = explicitCategory.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
13
|
+
if !trimmed.isEmpty {
|
|
14
|
+
return trimmed
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if needs.needsBackgroundOrPipPlayback {
|
|
18
|
+
return "playback"
|
|
19
|
+
}
|
|
20
|
+
return "ambient"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static func unionCategory(for players: [VideoPlayerAudioSessionNeeds]) -> String {
|
|
24
|
+
guard !players.isEmpty else {
|
|
25
|
+
return "ambient"
|
|
26
|
+
}
|
|
27
|
+
let categories = players.map(category(for:))
|
|
28
|
+
if categories.contains("moviePlayback") {
|
|
29
|
+
return "moviePlayback"
|
|
30
|
+
}
|
|
31
|
+
if categories.contains("playback") {
|
|
32
|
+
return "playback"
|
|
33
|
+
}
|
|
34
|
+
return "ambient"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
enum VideoPlayerAudioSessionCoordinator {
|
|
39
|
+
private static let lock = NSLock()
|
|
40
|
+
private static var activeRegistrations: [UUID: VideoPlayerAudioSessionNeeds] = [:]
|
|
41
|
+
|
|
42
|
+
static func acquire(registrationId: UUID, needs: VideoPlayerAudioSessionNeeds) throws {
|
|
43
|
+
lock.lock()
|
|
44
|
+
defer { lock.unlock() }
|
|
45
|
+
|
|
46
|
+
let wasEmpty = activeRegistrations.isEmpty
|
|
47
|
+
var stagedRegistrations = activeRegistrations
|
|
48
|
+
stagedRegistrations[registrationId] = needs
|
|
49
|
+
|
|
50
|
+
try applyCategory(for: stagedRegistrations.values)
|
|
51
|
+
if wasEmpty {
|
|
52
|
+
try AVAudioSession.sharedInstance().setActive(true)
|
|
53
|
+
}
|
|
54
|
+
activeRegistrations = stagedRegistrations
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static func release(registrationId: UUID) {
|
|
58
|
+
lock.lock()
|
|
59
|
+
defer { lock.unlock() }
|
|
60
|
+
|
|
61
|
+
activeRegistrations.removeValue(forKey: registrationId)
|
|
62
|
+
if activeRegistrations.isEmpty {
|
|
63
|
+
do {
|
|
64
|
+
try AVAudioSession.sharedInstance().setActive(false, options: [.notifyOthersOnDeactivation])
|
|
65
|
+
} catch {
|
|
66
|
+
print("Error deactivating AVAudioSession: \(error)")
|
|
67
|
+
}
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
do {
|
|
72
|
+
try applyCategory(for: activeRegistrations.values)
|
|
73
|
+
} catch {
|
|
74
|
+
print("Error reconfiguring AVAudioSession after player exit: \(error)")
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private static func applyCategory(for players: some Collection<VideoPlayerAudioSessionNeeds>) throws {
|
|
79
|
+
let resolvedCategory = VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: Array(players))
|
|
80
|
+
let session = AVAudioSession.sharedInstance()
|
|
81
|
+
switch resolvedCategory {
|
|
82
|
+
case "ambient":
|
|
83
|
+
try session.setCategory(.ambient, mode: .default, options: [.mixWithOthers])
|
|
84
|
+
case "playback":
|
|
85
|
+
try session.setCategory(.playback, mode: .default, options: [.mixWithOthers])
|
|
86
|
+
case "moviePlayback":
|
|
87
|
+
if #available(iOS 13.0, *) {
|
|
88
|
+
try session.setCategory(
|
|
89
|
+
.playback,
|
|
90
|
+
mode: .moviePlayback,
|
|
91
|
+
policy: .longFormVideo,
|
|
92
|
+
options: [.mixWithOthers]
|
|
93
|
+
)
|
|
94
|
+
} else {
|
|
95
|
+
try session.setCategory(.playback, mode: .moviePlayback, options: [.mixWithOthers])
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
try session.setCategory(.ambient, mode: .default, options: [.mixWithOthers])
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -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.2.
|
|
11
|
+
private let pluginVersion: String = "8.2.15"
|
|
12
12
|
public let identifier = "VideoPlayerPlugin"
|
|
13
13
|
public let jsName = "VideoPlayer"
|
|
14
14
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -76,6 +76,7 @@ extension VideoPlayerPlugin {
|
|
|
76
76
|
let exitOnEnd = call.getBool("exitOnEnd") ?? true
|
|
77
77
|
let loopOnEnd = call.getBool("loopOnEnd") ?? false
|
|
78
78
|
let pipEnabled = call.getBool("pipEnabled") ?? true
|
|
79
|
+
let bkmodeEnabled = call.getBool("bkmodeEnabled") ?? true
|
|
79
80
|
let showControls = call.getBool("showControls") ?? true
|
|
80
81
|
let chromecast = call.getBool("chromecast") ?? true
|
|
81
82
|
let title = call.getString("title")
|
|
@@ -94,6 +95,7 @@ extension VideoPlayerPlugin {
|
|
|
94
95
|
exitOnEnd: exitOnEnd,
|
|
95
96
|
loopOnEnd: loopOnEnd,
|
|
96
97
|
pipEnabled: pipEnabled,
|
|
98
|
+
bkmodeEnabled: bkmodeEnabled,
|
|
97
99
|
showControls: showControls,
|
|
98
100
|
chromecast: chromecast,
|
|
99
101
|
chromecastUrl: chromecastUrl,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import VideoPlayerPlugin
|
|
3
|
+
|
|
4
|
+
final class VideoPlayerAudioSessionCategoryPolicyTests: XCTestCase {
|
|
5
|
+
func testPerPlayerCategoryUsesExplicitCategoryWhenSet() {
|
|
6
|
+
let needs = VideoPlayerAudioSessionNeeds(
|
|
7
|
+
explicitCategory: "moviePlayback",
|
|
8
|
+
needsBackgroundOrPipPlayback: false
|
|
9
|
+
)
|
|
10
|
+
XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.category(for: needs), "moviePlayback")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
func testPerPlayerCategoryUsesPlaybackForBackgroundOrPip() {
|
|
14
|
+
let needs = VideoPlayerAudioSessionNeeds(
|
|
15
|
+
explicitCategory: nil,
|
|
16
|
+
needsBackgroundOrPipPlayback: true
|
|
17
|
+
)
|
|
18
|
+
XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.category(for: needs), "playback")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func testUnionCategoryStaysAmbientWhenNoPlayerNeedsPlayback() {
|
|
22
|
+
let ambient = VideoPlayerAudioSessionNeeds(
|
|
23
|
+
explicitCategory: nil,
|
|
24
|
+
needsBackgroundOrPipPlayback: false
|
|
25
|
+
)
|
|
26
|
+
XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: [ambient, ambient]), "ambient")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func testUnionCategoryUpgradesToPlaybackWhenAnyPlayerNeedsBackgroundOrPip() {
|
|
30
|
+
let ambient = VideoPlayerAudioSessionNeeds(
|
|
31
|
+
explicitCategory: nil,
|
|
32
|
+
needsBackgroundOrPipPlayback: false
|
|
33
|
+
)
|
|
34
|
+
let pip = VideoPlayerAudioSessionNeeds(
|
|
35
|
+
explicitCategory: nil,
|
|
36
|
+
needsBackgroundOrPipPlayback: true
|
|
37
|
+
)
|
|
38
|
+
XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: [ambient, pip]), "playback")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func testUnionCategoryPrefersMoviePlaybackOverPlayback() {
|
|
42
|
+
let movie = VideoPlayerAudioSessionNeeds(
|
|
43
|
+
explicitCategory: "moviePlayback",
|
|
44
|
+
needsBackgroundOrPipPlayback: false
|
|
45
|
+
)
|
|
46
|
+
let pip = VideoPlayerAudioSessionNeeds(
|
|
47
|
+
explicitCategory: nil,
|
|
48
|
+
needsBackgroundOrPipPlayback: true
|
|
49
|
+
)
|
|
50
|
+
XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: [movie, pip]), "moviePlayback")
|
|
51
|
+
}
|
|
52
|
+
}
|