@pexip/media 22.2.0 → 22.3.1
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/CHANGELOG.md +41 -0
- package/api-docs/README.mdx +8 -0
- package/api-docs/functions/createMediaSession.mdx +37 -0
- package/api-docs/interfaces/MediaSessionController.mdx +50 -0
- package/api-docs/interfaces/MediaSessionEnterPictureInPictureActionDetails.mdx +17 -0
- package/api-docs/interfaces/MediaSessionInit.mdx +13 -0
- package/api-docs/interfaces/MediaSessionLike.mdx +122 -0
- package/api-docs/interfaces/MediaSessionMetadataInit.mdx +11 -0
- package/api-docs/type-aliases/ExtendedMediaSessionAction.mdx +28 -0
- package/api-docs/type-aliases/MediaSessionEnterPictureInPictureReason.mdx +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/media.js +1 -0
- package/dist/mediaSession.d.ts +221 -0
- package/dist/mediaSession.js +213 -0
- package/dist/userMedia.d.ts +1 -4
- package/dist/userMedia.js +6 -8
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @pexip/media
|
|
2
2
|
|
|
3
|
+
## 22.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 70f97b7: Fix media status and mute state hook interpretation
|
|
8
|
+
- 1841fdb: Fix constraints update
|
|
9
|
+
- Updated dependencies [8b563f5]
|
|
10
|
+
- @pexip/utils@17.5.0
|
|
11
|
+
- @pexip/media-control@22.3.1
|
|
12
|
+
- @pexip/media-processor@22.3.1
|
|
13
|
+
|
|
14
|
+
## 22.3.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 5d1bb40: Add `createMediaSession` to present an ongoing call to the platform's
|
|
19
|
+
media controls via the Media Session API, and to keep audio capture alive
|
|
20
|
+
while the page is in the background on mobile. The supported actions are
|
|
21
|
+
extended with the conferencing specific `hangup`, `togglecamera`,
|
|
22
|
+
`togglemicrophone`, `togglescreenshare` and `voiceactivity`, along with
|
|
23
|
+
`enterpictureinpicture`.
|
|
24
|
+
|
|
25
|
+
The controller also exposes `setPositionState` and `setScreenshareActive`, and
|
|
26
|
+
`destroy` is now terminal, so a slower owner cannot resurrect the platform
|
|
27
|
+
controls after the call has ended.
|
|
28
|
+
|
|
29
|
+
The capture state is only reported to platforms that treat it as such. WebKit
|
|
30
|
+
implements `setMicrophoneActive`, `setCameraActive` and `setScreenshareActive`
|
|
31
|
+
as commands mutating the real capture state, and rejects activating one
|
|
32
|
+
outside a user gesture, which would otherwise leave the microphone muted with
|
|
33
|
+
no way back. Pass `applyImperativeCaptureState` to opt in when every call is
|
|
34
|
+
made from a user gesture handler.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [b666166]
|
|
39
|
+
- Updated dependencies [53af4ef]
|
|
40
|
+
- Updated dependencies [2f3c6b7]
|
|
41
|
+
- @pexip/media-processor@22.3.0
|
|
42
|
+
- @pexip/media-control@22.3.0
|
|
43
|
+
|
|
3
44
|
## 22.2.0
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
package/api-docs/README.mdx
CHANGED
|
@@ -282,6 +282,11 @@ await mediaController.media.applyConstraints({
|
|
|
282
282
|
|
|
283
283
|
| Interface | Description |
|
|
284
284
|
| ------ | ------ |
|
|
285
|
+
| [MediaSessionEnterPictureInPictureActionDetails](interfaces/MediaSessionEnterPictureInPictureActionDetails.mdx) | Details passed to the `enterpictureinpicture` action handler, which are not part of the `MediaSessionActionDetails` type shipped with TypeScript's DOM library. |
|
|
286
|
+
| [MediaSessionLike](interfaces/MediaSessionLike.mdx) | The subset of the `navigator.mediaSession` API used by [createMediaSession](functions/createMediaSession.mdx), so that an equivalent implementation can be injected, e.g. in tests. |
|
|
287
|
+
| [MediaSessionMetadataInit](interfaces/MediaSessionMetadataInit.mdx) | Descriptive information presented by the platform's media controls, e.g. the lock screen or the notification shade. |
|
|
288
|
+
| [MediaSessionInit](interfaces/MediaSessionInit.mdx) | Options to create a media session. |
|
|
289
|
+
| [MediaSessionController](interfaces/MediaSessionController.mdx) | Controls an active media session. |
|
|
285
290
|
| [PreviewEventHandler](interfaces/PreviewEventHandler.mdx) | - |
|
|
286
291
|
| [PreviewStreamParams](interfaces/PreviewStreamParams.mdx) | - |
|
|
287
292
|
| [PreviewControllerProps](interfaces/PreviewControllerProps.mdx) | - |
|
|
@@ -307,6 +312,8 @@ await mediaController.media.applyConstraints({
|
|
|
307
312
|
| Type Alias | Description |
|
|
308
313
|
| ------ | ------ |
|
|
309
314
|
| [GetDisplayMedia](type-aliases/GetDisplayMedia.mdx) | - |
|
|
315
|
+
| [ExtendedMediaSessionAction](type-aliases/ExtendedMediaSessionAction.mdx) | Media session actions, including the conferencing specific ones which are not part of the `MediaSessionAction` type shipped with TypeScript's DOM library. |
|
|
316
|
+
| [MediaSessionEnterPictureInPictureReason](type-aliases/MediaSessionEnterPictureInPictureReason.mdx) | Why the user agent asked the page to enter picture in picture. |
|
|
310
317
|
| [PreviewInput](type-aliases/PreviewInput.mdx) | - |
|
|
311
318
|
| [CreatePreviewStreamController](type-aliases/CreatePreviewStreamController.mdx) | - |
|
|
312
319
|
| [GeneratedMediaSignals](type-aliases/GeneratedMediaSignals.mdx) | - |
|
|
@@ -373,6 +380,7 @@ await mediaController.media.applyConstraints({
|
|
|
373
380
|
| [createGetDisplayMedia](functions/createGetDisplayMedia.mdx) | - |
|
|
374
381
|
| [setLogger](functions/setLogger.mdx) | - |
|
|
375
382
|
| [createMedia](functions/createMedia.mdx) | Create an object to interact with the media scream, which is usually used for our main stream. |
|
|
383
|
+
| [createMediaSession](functions/createMediaSession.mdx) | Create a media session, presenting the ongoing call to the platform's media controls, e.g. the lock screen, the notification shade or headset buttons. |
|
|
376
384
|
| [createPreviewStreamController](functions/createPreviewStreamController.mdx) | - |
|
|
377
385
|
| [createMediaSignal](functions/createMediaSignal.mdx) | Create a general signal with consistent scoped name |
|
|
378
386
|
| [createMediaSignals](functions/createMediaSignals.mdx) | Create and return all required and optional (if specified with `more`), signals for media to work |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
```ts
|
|
2
|
+
function createMediaSession(__namedParameters): MediaSessionController;
|
|
3
|
+
```
|
|
4
|
+
|
|
5
|
+
Create a media session, presenting the ongoing call to the platform's media
|
|
6
|
+
controls, e.g. the lock screen, the notification shade or headset buttons.
|
|
7
|
+
|
|
8
|
+
Declaring a media session also hints the browser that the page is playing
|
|
9
|
+
media, which mobile browsers use to relax the throttling applied to
|
|
10
|
+
backgrounded pages and to keep the audio capture alive.
|
|
11
|
+
|
|
12
|
+
Every method is a no-op when the Media Session API is unavailable, so the
|
|
13
|
+
caller does not need to feature detect it.
|
|
14
|
+
|
|
15
|
+
## Parameters
|
|
16
|
+
|
|
17
|
+
| Parameter | Type |
|
|
18
|
+
| ------ | ------ |
|
|
19
|
+
| `__namedParameters` | [`MediaSessionInit`](../interfaces/MediaSessionInit.mdx) |
|
|
20
|
+
|
|
21
|
+
## Returns
|
|
22
|
+
|
|
23
|
+
[`MediaSessionController`](../interfaces/MediaSessionController.mdx)
|
|
24
|
+
|
|
25
|
+
## Example
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const session = createMediaSession({
|
|
29
|
+
metadata: {title: 'Weekly sync', artist: 'Pexip'},
|
|
30
|
+
handlers: {
|
|
31
|
+
hangup: () => leaveMeeting(),
|
|
32
|
+
togglemicrophone: () => toggleAudioMuted(),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
await session.setMicrophoneActive(true);
|
|
36
|
+
session.destroy();
|
|
37
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Controls an active media session.
|
|
2
|
+
|
|
3
|
+
## Properties
|
|
4
|
+
|
|
5
|
+
| Property | Modifier | Type | Description |
|
|
6
|
+
| ------ | ------ | ------ | ------ |
|
|
7
|
+
| <a id="issupported"></a> `isSupported` | `readonly` | `boolean` | Whether the media session API is available in the current environment. Every other method is a no-op when it is `false`. |
|
|
8
|
+
| <a id="setmetadata"></a> `setMetadata` | `public` | `(metadata?) => void` | Update, or clear when omitted, the presented metadata. |
|
|
9
|
+
| <a id="setplaybackstate"></a> `setPlaybackState` | `public` | `(playbackState) => void` | Update the presented playback state. |
|
|
10
|
+
| <a id="setmicrophoneactive"></a> `setMicrophoneActive` | `public` | `(active) => Promise<void>` | Update whether the platform should present the microphone as active. A no-op on platforms that implement this as a command mutating the real tracks, unless `applyImperativeCaptureState` was set. |
|
|
11
|
+
| <a id="setcameraactive"></a> `setCameraActive` | `public` | `(active) => Promise<void>` | Update whether the platform should present the camera as active, with the same caveat as [MediaSessionController.setMicrophoneActive](#setmicrophoneactive). |
|
|
12
|
+
| <a id="setscreenshareactive"></a> `setScreenshareActive` | `public` | `(active) => Promise<void>` | Update whether the platform should present the screen as shared, with the same caveat as [MediaSessionController.setMicrophoneActive](#setmicrophoneactive). Only implemented by WebKit so far. |
|
|
13
|
+
| <a id="setpositionstate"></a> `setPositionState` | `public` | `(state?) => void` | Update, or reset when omitted, the presented playback position. Pass `duration: Infinity` for a live stream such as a call, which makes the platform present an elapsed clock rather than a seekable scrub bar. States the user agent rejects are logged and otherwise ignored. |
|
|
14
|
+
| <a id="destroy"></a> `destroy` | `public` | `() => void` | Unregister every handler registered by this session and reset the metadata, the playback state and the position state. Terminal and idempotent: every other method becomes a no-op afterwards, so a slower owner cannot resurrect the platform's media controls. |
|
|
15
|
+
|
|
16
|
+
## Methods
|
|
17
|
+
|
|
18
|
+
### registerActions()
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
registerActions(handlers): void;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
#### Parameters
|
|
25
|
+
|
|
26
|
+
| Parameter | Type |
|
|
27
|
+
| ------ | ------ |
|
|
28
|
+
| `handlers` | `Partial`\<`Record`\<[`ExtendedMediaSessionAction`](../type-aliases/ExtendedMediaSessionAction.mdx), `MediaSessionActionHandler`\>\> |
|
|
29
|
+
|
|
30
|
+
#### Returns
|
|
31
|
+
|
|
32
|
+
`void`
|
|
33
|
+
|
|
34
|
+
***
|
|
35
|
+
|
|
36
|
+
### deregisterActions()
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
deregisterActions(actions): void;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### Parameters
|
|
43
|
+
|
|
44
|
+
| Parameter | Type |
|
|
45
|
+
| ------ | ------ |
|
|
46
|
+
| `actions` | [`ExtendedMediaSessionAction`](../type-aliases/ExtendedMediaSessionAction.mdx)[] |
|
|
47
|
+
|
|
48
|
+
#### Returns
|
|
49
|
+
|
|
50
|
+
`void`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Details passed to the `enterpictureinpicture` action handler, which are not
|
|
2
|
+
part of the `MediaSessionActionDetails` type shipped with TypeScript's DOM
|
|
3
|
+
library.
|
|
4
|
+
|
|
5
|
+
## Extends
|
|
6
|
+
|
|
7
|
+
- `MediaSessionActionDetails`
|
|
8
|
+
|
|
9
|
+
## Properties
|
|
10
|
+
|
|
11
|
+
| Property | Type | Inherited from |
|
|
12
|
+
| ------ | ------ | ------ |
|
|
13
|
+
| <a id="enterpictureinpicturereason"></a> `enterPictureInPictureReason?` | [`MediaSessionEnterPictureInPictureReason`](../type-aliases/MediaSessionEnterPictureInPictureReason.mdx) | - |
|
|
14
|
+
| <a id="action"></a> `action` | `MediaSessionAction` | `MediaSessionActionDetails.action` |
|
|
15
|
+
| <a id="fastseek"></a> `fastSeek?` | `boolean` | `MediaSessionActionDetails.fastSeek` |
|
|
16
|
+
| <a id="seekoffset"></a> `seekOffset?` | `number` | `MediaSessionActionDetails.seekOffset` |
|
|
17
|
+
| <a id="seektime"></a> `seekTime?` | `number` | `MediaSessionActionDetails.seekTime` |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Options to create a media session.
|
|
2
|
+
|
|
3
|
+
## Properties
|
|
4
|
+
|
|
5
|
+
| Property | Type | Description |
|
|
6
|
+
| ------ | ------ | ------ |
|
|
7
|
+
| <a id="handlers"></a> `handlers?` | `Partial`\<`Record`\<[`ExtendedMediaSessionAction`](../type-aliases/ExtendedMediaSessionAction.mdx), `MediaSessionActionHandler`\>\> | Handlers to register, keyed by the action they handle. Actions without a handler are left untouched, allowing the platform to fall back to its default behavior. |
|
|
8
|
+
| <a id="metadata"></a> `metadata?` | [`MediaSessionMetadataInit`](MediaSessionMetadataInit.mdx) | Initial metadata to present. |
|
|
9
|
+
| <a id="playbackstate"></a> `playbackState?` | `MediaSessionPlaybackState` | Initial playback state, defaults to `playing`. |
|
|
10
|
+
| <a id="positionstate"></a> `positionState?` | `MediaPositionState` | Initial playback position to present. |
|
|
11
|
+
| <a id="applyimperativecapturestate"></a> `applyImperativeCaptureState?` | `boolean` | Apply the capture state even when the platform implements it as a command that mutes the real tracks, defaults to `false`. Only enable this when every call is made from a user gesture handler, otherwise unmuting is rejected and the capture stays muted. |
|
|
12
|
+
| <a id="mediasession"></a> `mediaSession?` | [`MediaSessionLike`](MediaSessionLike.mdx) | The media session implementation to use, defaults to `navigator.mediaSession` when available. |
|
|
13
|
+
| <a id="createmetadata"></a> `createMetadata?` | `(init) => MediaMetadata` | Factory building the metadata object, defaults to the `MediaMetadata` constructor when available. |
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
The subset of the `navigator.mediaSession` API used by
|
|
2
|
+
[createMediaSession](../functions/createMediaSession.mdx), so that an equivalent implementation can be
|
|
3
|
+
injected, e.g. in tests.
|
|
4
|
+
|
|
5
|
+
## Properties
|
|
6
|
+
|
|
7
|
+
| Property | Type | Description |
|
|
8
|
+
| ------ | ------ | ------ |
|
|
9
|
+
| <a id="metadata"></a> `metadata` | `MediaMetadata \| null` | Descriptive information about the currently played media. |
|
|
10
|
+
| <a id="playbackstate"></a> `playbackState` | `MediaSessionPlaybackState` | Whether the media session is currently playing or paused. |
|
|
11
|
+
|
|
12
|
+
## Methods
|
|
13
|
+
|
|
14
|
+
### setActionHandler()
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
setActionHandler(action, handler): void;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Register, or unregister when the handler is `null`, a handler for the
|
|
21
|
+
given media session action.
|
|
22
|
+
|
|
23
|
+
#### Parameters
|
|
24
|
+
|
|
25
|
+
| Parameter | Type |
|
|
26
|
+
| ------ | ------ |
|
|
27
|
+
| `action` | [`ExtendedMediaSessionAction`](../type-aliases/ExtendedMediaSessionAction.mdx) |
|
|
28
|
+
| `handler` | `MediaSessionActionHandler \| null` |
|
|
29
|
+
|
|
30
|
+
#### Returns
|
|
31
|
+
|
|
32
|
+
`void`
|
|
33
|
+
|
|
34
|
+
***
|
|
35
|
+
|
|
36
|
+
### setCameraActive()?
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
optional setCameraActive(active): Promise<void>;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Tell the user agent whether the camera is considered to be active.
|
|
43
|
+
|
|
44
|
+
P.S. Safari's impl. is an imperative action to change the track's
|
|
45
|
+
muted stated whereas Chromium-based browsers indicate to the user
|
|
46
|
+
agent the microphone capture state desired by the page
|
|
47
|
+
|
|
48
|
+
#### Parameters
|
|
49
|
+
|
|
50
|
+
| Parameter | Type |
|
|
51
|
+
| ------ | ------ |
|
|
52
|
+
| `active` | `boolean` |
|
|
53
|
+
|
|
54
|
+
#### Returns
|
|
55
|
+
|
|
56
|
+
`Promise<void>`
|
|
57
|
+
|
|
58
|
+
***
|
|
59
|
+
|
|
60
|
+
### setMicrophoneActive()?
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
optional setMicrophoneActive(active): Promise<void>;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Tell the user agent whether the microphone is considered to be active.
|
|
67
|
+
|
|
68
|
+
P.S. Safari's impl. is an imperative action to change the track's
|
|
69
|
+
muted stated whereas Chromium-based browsers indicate to the user
|
|
70
|
+
agent the microphone capture state desired by the page
|
|
71
|
+
|
|
72
|
+
#### Parameters
|
|
73
|
+
|
|
74
|
+
| Parameter | Type |
|
|
75
|
+
| ------ | ------ |
|
|
76
|
+
| `active` | `boolean` |
|
|
77
|
+
|
|
78
|
+
#### Returns
|
|
79
|
+
|
|
80
|
+
`Promise<void>`
|
|
81
|
+
|
|
82
|
+
***
|
|
83
|
+
|
|
84
|
+
### setScreenshareActive()?
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
optional setScreenshareActive(active): Promise<void>;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Tell the user agent whether the screen is considered to be shared. Only
|
|
91
|
+
specified so far, no browser implements it yet.
|
|
92
|
+
|
|
93
|
+
#### Parameters
|
|
94
|
+
|
|
95
|
+
| Parameter | Type |
|
|
96
|
+
| ------ | ------ |
|
|
97
|
+
| `active` | `boolean` |
|
|
98
|
+
|
|
99
|
+
#### Returns
|
|
100
|
+
|
|
101
|
+
`Promise<void>`
|
|
102
|
+
|
|
103
|
+
***
|
|
104
|
+
|
|
105
|
+
### setPositionState()?
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
optional setPositionState(state?): void;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Present the playback position, or reset it when called without a state.
|
|
112
|
+
Throws a `TypeError` for a state the user agent considers invalid.
|
|
113
|
+
|
|
114
|
+
#### Parameters
|
|
115
|
+
|
|
116
|
+
| Parameter | Type |
|
|
117
|
+
| ------ | ------ |
|
|
118
|
+
| `state?` | `MediaPositionState` |
|
|
119
|
+
|
|
120
|
+
#### Returns
|
|
121
|
+
|
|
122
|
+
`void`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Descriptive information presented by the platform's media controls, e.g. the
|
|
2
|
+
lock screen or the notification shade.
|
|
3
|
+
|
|
4
|
+
## Properties
|
|
5
|
+
|
|
6
|
+
| Property | Type | Description |
|
|
7
|
+
| ------ | ------ | ------ |
|
|
8
|
+
| <a id="title"></a> `title` | `string` | Main label, e.g. the name of the meeting. |
|
|
9
|
+
| <a id="artist"></a> `artist?` | `string` | Secondary label, e.g. the name of the brand. |
|
|
10
|
+
| <a id="album"></a> `album?` | `string` | Tertiary label. |
|
|
11
|
+
| <a id="artwork"></a> `artwork?` | `MediaImage[]` | Images associated with the session, e.g. the brand logo. |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
```ts
|
|
2
|
+
type ExtendedMediaSessionAction =
|
|
3
|
+
| MediaSessionAction
|
|
4
|
+
| "enterpictureinpicture"
|
|
5
|
+
| "hangup"
|
|
6
|
+
| "togglecamera"
|
|
7
|
+
| "togglemicrophone"
|
|
8
|
+
| "togglescreenshare"
|
|
9
|
+
| "voiceactivity";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Media session actions, including the conferencing specific ones which are
|
|
13
|
+
not part of the `MediaSessionAction` type shipped with TypeScript's DOM
|
|
14
|
+
library.
|
|
15
|
+
|
|
16
|
+
`hangup`, `togglecamera`, `togglemicrophone` and `enterpictureinpicture` are
|
|
17
|
+
implemented by Chromium based browsers. `togglescreenshare` and
|
|
18
|
+
`voiceactivity` are only specified so far, and are registered as a no-op by
|
|
19
|
+
every current browser, which [createMediaSession](../functions/createMediaSession.mdx) handles gracefully.
|
|
20
|
+
|
|
21
|
+
Note that `voiceactivity` runs the other way around to the rest: the user
|
|
22
|
+
agent invokes the handler when it detects voice on a live microphone track,
|
|
23
|
+
e.g. to let a page nudge a muted user who is talking.
|
|
24
|
+
|
|
25
|
+
## See
|
|
26
|
+
|
|
27
|
+
- [https://w3c.github.io/mediasession/#enumdef-mediasessionaction](https://w3c.github.io/mediasession/#enumdef-mediasessionaction)
|
|
28
|
+
- [https://developer.mozilla.org/en-US/docs/Web/API/MediaSession/setActionHandler](https://developer.mozilla.org/en-US/docs/Web/API/MediaSession/setActionHandler)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
```ts
|
|
2
|
+
type MediaSessionEnterPictureInPictureReason = "other" | "useraction" | "contentoccluded";
|
|
3
|
+
```
|
|
4
|
+
|
|
5
|
+
Why the user agent asked the page to enter picture in picture.
|
|
6
|
+
|
|
7
|
+
## See
|
|
8
|
+
|
|
9
|
+
[https://developer.mozilla.org/en-US/docs/Web/API/MediaSession/setActionHandler](https://developer.mozilla.org/en-US/docs/Web/API/MediaSession/setActionHandler)
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/media.js
CHANGED
|
@@ -101,6 +101,7 @@ export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDisc
|
|
|
101
101
|
]);
|
|
102
102
|
}
|
|
103
103
|
else if (currentMedia?.status) {
|
|
104
|
+
currentMedia.setOriginalConstraints(constraints);
|
|
104
105
|
// No media request, update status only
|
|
105
106
|
const anyAudioTrack = currentMedia
|
|
106
107
|
.getAudioTracks()
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media session actions, including the conferencing specific ones which are
|
|
3
|
+
* not part of the `MediaSessionAction` type shipped with TypeScript's DOM
|
|
4
|
+
* library.
|
|
5
|
+
*
|
|
6
|
+
* `hangup`, `togglecamera`, `togglemicrophone` and `enterpictureinpicture` are
|
|
7
|
+
* implemented by Chromium based browsers. `togglescreenshare` and
|
|
8
|
+
* `voiceactivity` are only specified so far, and are registered as a no-op by
|
|
9
|
+
* every current browser, which {@link createMediaSession} handles gracefully.
|
|
10
|
+
*
|
|
11
|
+
* Note that `voiceactivity` runs the other way around to the rest: the user
|
|
12
|
+
* agent invokes the handler when it detects voice on a live microphone track,
|
|
13
|
+
* e.g. to let a page nudge a muted user who is talking.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link https://w3c.github.io/mediasession/#enumdef-mediasessionaction}
|
|
16
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaSession/setActionHandler}
|
|
17
|
+
*/
|
|
18
|
+
export type ExtendedMediaSessionAction = MediaSessionAction | 'enterpictureinpicture' | 'hangup' | 'togglecamera' | 'togglemicrophone' | 'togglescreenshare' | 'voiceactivity';
|
|
19
|
+
/**
|
|
20
|
+
* Why the user agent asked the page to enter picture in picture.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaSession/setActionHandler}
|
|
23
|
+
*/
|
|
24
|
+
export type MediaSessionEnterPictureInPictureReason = 'other' | 'useraction' | 'contentoccluded';
|
|
25
|
+
/**
|
|
26
|
+
* Details passed to the `enterpictureinpicture` action handler, which are not
|
|
27
|
+
* part of the `MediaSessionActionDetails` type shipped with TypeScript's DOM
|
|
28
|
+
* library.
|
|
29
|
+
*/
|
|
30
|
+
export interface MediaSessionEnterPictureInPictureActionDetails extends MediaSessionActionDetails {
|
|
31
|
+
enterPictureInPictureReason?: MediaSessionEnterPictureInPictureReason;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The subset of the `navigator.mediaSession` API used by
|
|
35
|
+
* {@link createMediaSession}, so that an equivalent implementation can be
|
|
36
|
+
* injected, e.g. in tests.
|
|
37
|
+
*/
|
|
38
|
+
export interface MediaSessionLike {
|
|
39
|
+
/**
|
|
40
|
+
* Descriptive information about the currently played media.
|
|
41
|
+
*/
|
|
42
|
+
metadata: MediaMetadata | null;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the media session is currently playing or paused.
|
|
45
|
+
*/
|
|
46
|
+
playbackState: MediaSessionPlaybackState;
|
|
47
|
+
/**
|
|
48
|
+
* Register, or unregister when the handler is `null`, a handler for the
|
|
49
|
+
* given media session action.
|
|
50
|
+
*/
|
|
51
|
+
setActionHandler(action: ExtendedMediaSessionAction, handler: MediaSessionActionHandler | null): void;
|
|
52
|
+
/**
|
|
53
|
+
* Tell the user agent whether the camera is considered to be active.
|
|
54
|
+
*
|
|
55
|
+
* P.S. Safari's impl. is an imperative action to change the track's
|
|
56
|
+
* muted stated whereas Chromium-based browsers indicate to the user
|
|
57
|
+
* agent the microphone capture state desired by the page
|
|
58
|
+
*/
|
|
59
|
+
setCameraActive?(active: boolean): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Tell the user agent whether the microphone is considered to be active.
|
|
62
|
+
*
|
|
63
|
+
* P.S. Safari's impl. is an imperative action to change the track's
|
|
64
|
+
* muted stated whereas Chromium-based browsers indicate to the user
|
|
65
|
+
* agent the microphone capture state desired by the page
|
|
66
|
+
*/
|
|
67
|
+
setMicrophoneActive?(active: boolean): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Tell the user agent whether the screen is considered to be shared. Only
|
|
70
|
+
* specified so far, no browser implements it yet.
|
|
71
|
+
*/
|
|
72
|
+
setScreenshareActive?(active: boolean): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Present the playback position, or reset it when called without a state.
|
|
75
|
+
* Throws a `TypeError` for a state the user agent considers invalid.
|
|
76
|
+
*/
|
|
77
|
+
setPositionState?(state?: MediaPositionState): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Descriptive information presented by the platform's media controls, e.g. the
|
|
81
|
+
* lock screen or the notification shade.
|
|
82
|
+
*/
|
|
83
|
+
export interface MediaSessionMetadataInit {
|
|
84
|
+
/**
|
|
85
|
+
* Main label, e.g. the name of the meeting.
|
|
86
|
+
*/
|
|
87
|
+
title: string;
|
|
88
|
+
/**
|
|
89
|
+
* Secondary label, e.g. the name of the brand.
|
|
90
|
+
*/
|
|
91
|
+
artist?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Tertiary label.
|
|
94
|
+
*/
|
|
95
|
+
album?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Images associated with the session, e.g. the brand logo.
|
|
98
|
+
*/
|
|
99
|
+
artwork?: MediaImage[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Options to create a media session.
|
|
103
|
+
*/
|
|
104
|
+
export interface MediaSessionInit {
|
|
105
|
+
/**
|
|
106
|
+
* Handlers to register, keyed by the action they handle. Actions without a
|
|
107
|
+
* handler are left untouched, allowing the platform to fall back to its
|
|
108
|
+
* default behavior.
|
|
109
|
+
*/
|
|
110
|
+
handlers?: Partial<Record<ExtendedMediaSessionAction, MediaSessionActionHandler>>;
|
|
111
|
+
/**
|
|
112
|
+
* Initial metadata to present.
|
|
113
|
+
*/
|
|
114
|
+
metadata?: MediaSessionMetadataInit;
|
|
115
|
+
/**
|
|
116
|
+
* Initial playback state, defaults to `playing`.
|
|
117
|
+
*/
|
|
118
|
+
playbackState?: MediaSessionPlaybackState;
|
|
119
|
+
/**
|
|
120
|
+
* Initial playback position to present.
|
|
121
|
+
*/
|
|
122
|
+
positionState?: MediaPositionState;
|
|
123
|
+
/**
|
|
124
|
+
* Apply the capture state even when the platform implements it as a
|
|
125
|
+
* command that mutes the real tracks, defaults to `false`.
|
|
126
|
+
*
|
|
127
|
+
* Only enable this when every call is made from a user gesture handler,
|
|
128
|
+
* otherwise unmuting is rejected and the capture stays muted.
|
|
129
|
+
*/
|
|
130
|
+
applyImperativeCaptureState?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* The media session implementation to use, defaults to
|
|
133
|
+
* `navigator.mediaSession` when available.
|
|
134
|
+
*/
|
|
135
|
+
mediaSession?: MediaSessionLike;
|
|
136
|
+
/**
|
|
137
|
+
* Factory building the metadata object, defaults to the `MediaMetadata`
|
|
138
|
+
* constructor when available.
|
|
139
|
+
*/
|
|
140
|
+
createMetadata?: (init: MediaSessionMetadataInit) => MediaMetadata;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Controls an active media session.
|
|
144
|
+
*/
|
|
145
|
+
export interface MediaSessionController {
|
|
146
|
+
/**
|
|
147
|
+
* Whether the media session API is available in the current environment.
|
|
148
|
+
* Every other method is a no-op when it is `false`.
|
|
149
|
+
*/
|
|
150
|
+
readonly isSupported: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Update, or clear when omitted, the presented metadata.
|
|
153
|
+
*/
|
|
154
|
+
setMetadata: (metadata?: MediaSessionMetadataInit) => void;
|
|
155
|
+
/**
|
|
156
|
+
* Update the presented playback state.
|
|
157
|
+
*/
|
|
158
|
+
setPlaybackState: (playbackState: MediaSessionPlaybackState) => void;
|
|
159
|
+
/**
|
|
160
|
+
* Update whether the platform should present the microphone as active.
|
|
161
|
+
*
|
|
162
|
+
* A no-op on platforms that implement this as a command mutating the real
|
|
163
|
+
* tracks, unless `applyImperativeCaptureState` was set.
|
|
164
|
+
*/
|
|
165
|
+
setMicrophoneActive: (active: boolean) => Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Update whether the platform should present the camera as active, with
|
|
168
|
+
* the same caveat as {@link MediaSessionController.setMicrophoneActive}.
|
|
169
|
+
*/
|
|
170
|
+
setCameraActive: (active: boolean) => Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Update whether the platform should present the screen as shared, with
|
|
173
|
+
* the same caveat as {@link MediaSessionController.setMicrophoneActive}.
|
|
174
|
+
*
|
|
175
|
+
* Only implemented by WebKit so far.
|
|
176
|
+
*/
|
|
177
|
+
setScreenshareActive: (active: boolean) => Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Update, or reset when omitted, the presented playback position.
|
|
180
|
+
*
|
|
181
|
+
* Pass `duration: Infinity` for a live stream such as a call, which makes
|
|
182
|
+
* the platform present an elapsed clock rather than a seekable scrub bar.
|
|
183
|
+
* States the user agent rejects are logged and otherwise ignored.
|
|
184
|
+
*/
|
|
185
|
+
setPositionState: (state?: MediaPositionState) => void;
|
|
186
|
+
registerActions(handlers: Partial<Record<ExtendedMediaSessionAction, MediaSessionActionHandler>>): void;
|
|
187
|
+
deregisterActions(actions: ExtendedMediaSessionAction[]): void;
|
|
188
|
+
/**
|
|
189
|
+
* Unregister every handler registered by this session and reset the
|
|
190
|
+
* metadata, the playback state and the position state.
|
|
191
|
+
*
|
|
192
|
+
* Terminal and idempotent: every other method becomes a no-op afterwards,
|
|
193
|
+
* so a slower owner cannot resurrect the platform's media controls.
|
|
194
|
+
*/
|
|
195
|
+
destroy: () => void;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create a media session, presenting the ongoing call to the platform's media
|
|
199
|
+
* controls, e.g. the lock screen, the notification shade or headset buttons.
|
|
200
|
+
*
|
|
201
|
+
* Declaring a media session also hints the browser that the page is playing
|
|
202
|
+
* media, which mobile browsers use to relax the throttling applied to
|
|
203
|
+
* backgrounded pages and to keep the audio capture alive.
|
|
204
|
+
*
|
|
205
|
+
* Every method is a no-op when the Media Session API is unavailable, so the
|
|
206
|
+
* caller does not need to feature detect it.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const session = createMediaSession({
|
|
211
|
+
* metadata: {title: 'Weekly sync', artist: 'Pexip'},
|
|
212
|
+
* handlers: {
|
|
213
|
+
* hangup: () => leaveMeeting(),
|
|
214
|
+
* togglemicrophone: () => toggleAudioMuted(),
|
|
215
|
+
* },
|
|
216
|
+
* });
|
|
217
|
+
* await session.setMicrophoneActive(true);
|
|
218
|
+
* session.destroy();
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export declare const createMediaSession: ({ handlers, metadata, playbackState, positionState, applyImperativeCaptureState, mediaSession, createMetadata, }?: MediaSessionInit) => MediaSessionController;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { logger } from './logger';
|
|
2
|
+
const getDefaultMediaSession = () => typeof navigator !== 'undefined' && 'mediaSession' in navigator
|
|
3
|
+
? navigator.mediaSession
|
|
4
|
+
: undefined;
|
|
5
|
+
const getDefaultCreateMetadata = () => typeof MediaMetadata === 'function'
|
|
6
|
+
? init => new MediaMetadata(init)
|
|
7
|
+
: undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Whether the platform treats the capture state setters as a command rather
|
|
10
|
+
* than as a report.
|
|
11
|
+
*
|
|
12
|
+
* Chromium's `setMicrophoneActive`/`setCameraActive` are purely declarative:
|
|
13
|
+
* they only choose which glyph the media control draws. WebKit's are
|
|
14
|
+
* imperative, and route through `UserMediaController::updateCaptureState` to
|
|
15
|
+
* the page level capture muted state, which mutes the real tracks. Activating
|
|
16
|
+
* capture there additionally requires a visible document and a user gesture,
|
|
17
|
+
* and can raise a permission prompt.
|
|
18
|
+
*
|
|
19
|
+
* That asymmetry is a trap for a state driven caller: muting always succeeds,
|
|
20
|
+
* while the matching unmute is rejected outside a gesture, leaving the
|
|
21
|
+
* microphone muted at the page level with no way back from a signal handler.
|
|
22
|
+
*
|
|
23
|
+
* `setScreenshareActive` distinguishes the two, as only WebKit implements it.
|
|
24
|
+
* WebKit exposes all three under a single `MediaSessionCaptureToggleAPIEnabled`
|
|
25
|
+
* setting, so they appear and disappear together, which makes the probe
|
|
26
|
+
* reliable: when the setting is off none of them exist and there is nothing to
|
|
27
|
+
* call, and Chromium only ever exposes the microphone and the camera ones.
|
|
28
|
+
*/
|
|
29
|
+
const hasImperativeCaptureState = (mediaSession) => typeof mediaSession.setScreenshareActive === 'function';
|
|
30
|
+
const createUnsupportedMediaSession = () => ({
|
|
31
|
+
isSupported: false,
|
|
32
|
+
setMetadata: () => undefined,
|
|
33
|
+
setPlaybackState: () => undefined,
|
|
34
|
+
setMicrophoneActive: () => Promise.resolve(),
|
|
35
|
+
setCameraActive: () => Promise.resolve(),
|
|
36
|
+
setScreenshareActive: () => Promise.resolve(),
|
|
37
|
+
setPositionState: () => undefined,
|
|
38
|
+
registerActions: () => undefined,
|
|
39
|
+
deregisterActions: () => undefined,
|
|
40
|
+
destroy: () => undefined,
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* Create a media session, presenting the ongoing call to the platform's media
|
|
44
|
+
* controls, e.g. the lock screen, the notification shade or headset buttons.
|
|
45
|
+
*
|
|
46
|
+
* Declaring a media session also hints the browser that the page is playing
|
|
47
|
+
* media, which mobile browsers use to relax the throttling applied to
|
|
48
|
+
* backgrounded pages and to keep the audio capture alive.
|
|
49
|
+
*
|
|
50
|
+
* Every method is a no-op when the Media Session API is unavailable, so the
|
|
51
|
+
* caller does not need to feature detect it.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const session = createMediaSession({
|
|
56
|
+
* metadata: {title: 'Weekly sync', artist: 'Pexip'},
|
|
57
|
+
* handlers: {
|
|
58
|
+
* hangup: () => leaveMeeting(),
|
|
59
|
+
* togglemicrophone: () => toggleAudioMuted(),
|
|
60
|
+
* },
|
|
61
|
+
* });
|
|
62
|
+
* await session.setMicrophoneActive(true);
|
|
63
|
+
* session.destroy();
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export const createMediaSession = ({ handlers = {}, metadata, playbackState = 'playing', positionState, applyImperativeCaptureState = false, mediaSession = getDefaultMediaSession(), createMetadata = getDefaultCreateMetadata(), } = {}) => {
|
|
67
|
+
if (!mediaSession) {
|
|
68
|
+
logger.debug({ context: 'MediaSession' }, 'Media Session API is not supported');
|
|
69
|
+
return createUnsupportedMediaSession();
|
|
70
|
+
}
|
|
71
|
+
const registeredActions = new Set();
|
|
72
|
+
let destroyed = false;
|
|
73
|
+
const skipCaptureState = !applyImperativeCaptureState && hasImperativeCaptureState(mediaSession);
|
|
74
|
+
if (skipCaptureState) {
|
|
75
|
+
logger.debug({ context: 'MediaSession' }, 'Skipping the media session capture state, the platform applies it to the real tracks');
|
|
76
|
+
}
|
|
77
|
+
const setActionHandler = (action, handler) => {
|
|
78
|
+
try {
|
|
79
|
+
mediaSession.setActionHandler(action, handler);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logger.debug({ context: 'MediaSession', action, error }, 'Media session action is not supported');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const setMetadata = (updatedMetadata) => {
|
|
88
|
+
if (destroyed) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!updatedMetadata || !createMetadata) {
|
|
92
|
+
mediaSession.metadata = null;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
mediaSession.metadata = createMetadata(updatedMetadata);
|
|
96
|
+
};
|
|
97
|
+
const setPlaybackState = (updatedPlaybackState) => {
|
|
98
|
+
if (destroyed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
mediaSession.playbackState = updatedPlaybackState;
|
|
102
|
+
};
|
|
103
|
+
const setMicrophoneActive = async (active) => {
|
|
104
|
+
if (destroyed ||
|
|
105
|
+
skipCaptureState ||
|
|
106
|
+
!mediaSession.setMicrophoneActive) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await mediaSession.setMicrophoneActive(active);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logger.debug({ context: 'MediaSession', active, error }, 'Failed to update the media session microphone state');
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const setCameraActive = async (active) => {
|
|
117
|
+
if (destroyed || skipCaptureState || !mediaSession.setCameraActive) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await mediaSession.setCameraActive(active);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
logger.debug({ context: 'MediaSession', active, error }, 'Failed to update the media session camera state');
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const setScreenshareActive = async (active) => {
|
|
128
|
+
if (destroyed ||
|
|
129
|
+
skipCaptureState ||
|
|
130
|
+
!mediaSession.setScreenshareActive) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await mediaSession.setScreenshareActive(active);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
logger.debug({ context: 'MediaSession', active, error }, 'Failed to update the media session screenshare state');
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const setPositionState = (state) => {
|
|
141
|
+
if (destroyed || !mediaSession.setPositionState) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
// Blink rejects a missing, `NaN` or negative duration, a negative
|
|
146
|
+
// position, a position past the duration and a zero playback rate.
|
|
147
|
+
mediaSession.setPositionState(state);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
logger.debug({ context: 'MediaSession', state, error }, 'Failed to update the media session position state');
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const destroy = () => {
|
|
154
|
+
if (destroyed) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const action of registeredActions) {
|
|
158
|
+
setActionHandler(action, null);
|
|
159
|
+
}
|
|
160
|
+
registeredActions.clear();
|
|
161
|
+
setMetadata();
|
|
162
|
+
setPlaybackState('none');
|
|
163
|
+
setPositionState();
|
|
164
|
+
// The session is a view onto the global `navigator.mediaSession`, which
|
|
165
|
+
// outlives it, so every mutator has to stop writing to it once the
|
|
166
|
+
// session is gone. Owners tear down on different clocks, e.g. the view
|
|
167
|
+
// layer only unbinds its media element listeners on the next React
|
|
168
|
+
// passive cleanup, well after the service has ended the call.
|
|
169
|
+
destroyed = true;
|
|
170
|
+
};
|
|
171
|
+
const registerActions = handlers => {
|
|
172
|
+
if (destroyed) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
for (const [action, handler] of Object.entries(handlers)) {
|
|
176
|
+
if (!handler) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (setActionHandler(action, handler)) {
|
|
180
|
+
registeredActions.add(action);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const deregisterActions = actions => {
|
|
185
|
+
if (destroyed) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (const action of actions) {
|
|
189
|
+
if (registeredActions.delete(action)) {
|
|
190
|
+
setActionHandler(action, null);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
// Register initial actions
|
|
195
|
+
registerActions(handlers);
|
|
196
|
+
setMetadata(metadata);
|
|
197
|
+
mediaSession.playbackState = playbackState;
|
|
198
|
+
if (positionState) {
|
|
199
|
+
setPositionState(positionState);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
isSupported: true,
|
|
203
|
+
setMetadata,
|
|
204
|
+
setPlaybackState,
|
|
205
|
+
setMicrophoneActive,
|
|
206
|
+
setCameraActive,
|
|
207
|
+
setScreenshareActive,
|
|
208
|
+
setPositionState,
|
|
209
|
+
registerActions,
|
|
210
|
+
deregisterActions,
|
|
211
|
+
destroy,
|
|
212
|
+
};
|
|
213
|
+
};
|
package/dist/userMedia.d.ts
CHANGED
|
@@ -11,10 +11,7 @@ export declare const toSameDeviceStatus: ({ audio, video, }: {
|
|
|
11
11
|
audio: boolean;
|
|
12
12
|
video: boolean;
|
|
13
13
|
}) => UserMediaStatus.PermissionsGranted | UserMediaStatus.PermissionsGrantedFallback | UserMediaStatus.PermissionsGrantedFallbackAudioinput | UserMediaStatus.PermissionsGrantedFallbackVideoinput;
|
|
14
|
-
export declare const toOnlyDeviceStatus: (kind: "audioinput" | "videoinput",
|
|
15
|
-
audio: boolean;
|
|
16
|
-
video: boolean;
|
|
17
|
-
}, devices: IndexedDevices) => UserMediaStatus.PermissionsRejectedAudioInput | UserMediaStatus.PermissionsRejectedVideoInput | UserMediaStatus.PermissionsOnlyAudioinput | UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices | UserMediaStatus.PermissionsOnlyAudioinputFallback | UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices | UserMediaStatus.PermissionsOnlyVideoinput | UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices | UserMediaStatus.PermissionsOnlyVideoinputFallback | UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
|
|
14
|
+
export declare const toOnlyDeviceStatus: (kind: "audioinput" | "videoinput", matched: boolean, devices: IndexedDevices) => UserMediaStatus.PermissionsRejectedAudioInput | UserMediaStatus.PermissionsRejectedVideoInput | UserMediaStatus.PermissionsOnlyAudioinput | UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices | UserMediaStatus.PermissionsOnlyAudioinputFallback | UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices | UserMediaStatus.PermissionsOnlyVideoinput | UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices | UserMediaStatus.PermissionsOnlyVideoinputFallback | UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
|
|
18
15
|
/**
|
|
19
16
|
* Check if there is any track match with the `kind`
|
|
20
17
|
*/
|
package/dist/userMedia.js
CHANGED
|
@@ -71,16 +71,13 @@ export const toSameDeviceStatus = ({ audio, video, }) => {
|
|
|
71
71
|
}
|
|
72
72
|
return UserMediaStatus.PermissionsGrantedFallback;
|
|
73
73
|
};
|
|
74
|
-
export const toOnlyDeviceStatus = (kind,
|
|
75
|
-
const [self, other] = kind === 'audioinput' ? ['audio', 'video'] : ['video', 'audio'];
|
|
74
|
+
export const toOnlyDeviceStatus = (kind, matched, devices) => {
|
|
76
75
|
const otherKind = kind === 'audioinput' ? 'videoinput' : 'audioinput';
|
|
77
|
-
const matched = streamedRequestedDevices[self];
|
|
78
|
-
const anotherDeviceRequested = streamedRequestedDevices[other];
|
|
79
76
|
const hasAnotherTypeOfAuthorizedDevice = devices.anyAuthorizedDevice(otherKind);
|
|
80
77
|
const hasAnotherTypeOfDevice = devices.size(otherKind) > 0;
|
|
81
78
|
if (matched) {
|
|
82
79
|
if (hasAnotherTypeOfDevice) {
|
|
83
|
-
if (hasAnotherTypeOfAuthorizedDevice
|
|
80
|
+
if (hasAnotherTypeOfAuthorizedDevice) {
|
|
84
81
|
return kind === 'audioinput'
|
|
85
82
|
? UserMediaStatus.PermissionsOnlyAudioinput
|
|
86
83
|
: UserMediaStatus.PermissionsOnlyVideoinput;
|
|
@@ -94,7 +91,7 @@ export const toOnlyDeviceStatus = (kind, streamedRequestedDevices, devices) => {
|
|
|
94
91
|
: UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices;
|
|
95
92
|
}
|
|
96
93
|
if (hasAnotherTypeOfDevice) {
|
|
97
|
-
if (hasAnotherTypeOfAuthorizedDevice
|
|
94
|
+
if (hasAnotherTypeOfAuthorizedDevice) {
|
|
98
95
|
return kind === 'audioinput'
|
|
99
96
|
? UserMediaStatus.PermissionsOnlyAudioinputFallback
|
|
100
97
|
: UserMediaStatus.PermissionsOnlyVideoinputFallback;
|
|
@@ -259,6 +256,7 @@ export const mergeStatus = (prevStatus, nextStatus, prevTracks = [], nextTracks
|
|
|
259
256
|
return nextStatus;
|
|
260
257
|
}
|
|
261
258
|
}
|
|
259
|
+
return nextStatus;
|
|
262
260
|
}
|
|
263
261
|
// Failed to get a new track
|
|
264
262
|
if (prevValidAudio) {
|
|
@@ -426,8 +424,8 @@ export const requestUserMedia = (getCurrentDevices, getMedia = getUserMedia) =>
|
|
|
426
424
|
: false,
|
|
427
425
|
});
|
|
428
426
|
const streamedRequestedDevices = isStreamingRequestedDevices(constraints, stream, devices);
|
|
429
|
-
const onlyAudioStatus = toOnlyDeviceStatus('audioinput', streamedRequestedDevices, devices);
|
|
430
|
-
const onlyVideoStatus = toOnlyDeviceStatus('videoinput', streamedRequestedDevices, devices);
|
|
427
|
+
const onlyAudioStatus = toOnlyDeviceStatus('audioinput', streamedRequestedDevices.audio, devices);
|
|
428
|
+
const onlyVideoStatus = toOnlyDeviceStatus('videoinput', streamedRequestedDevices.video, devices);
|
|
431
429
|
const status = deriveDeviceStatus(onlyAudioStatus, onlyVideoStatus, toSameDeviceStatus(streamedRequestedDevices));
|
|
432
430
|
return [stream, status];
|
|
433
431
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pexip/media",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.3.1",
|
|
4
4
|
"description": "Home for media related stuff",
|
|
5
5
|
"homepage": "https://developer.pexip.com",
|
|
6
6
|
"bugs": "https://gitlab.com/pexip/zoo/issues",
|
|
@@ -47,15 +47,15 @@
|
|
|
47
47
|
"docs": "typedoc"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@pexip/media-control": "22.
|
|
51
|
-
"@pexip/media-processor": "22.
|
|
50
|
+
"@pexip/media-control": "22.3.1",
|
|
51
|
+
"@pexip/media-processor": "22.3.1",
|
|
52
52
|
"@pexip/signal": "16.9.6",
|
|
53
|
-
"@pexip/utils": "17.
|
|
53
|
+
"@pexip/utils": "17.5.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@pexip/bundler": "18.3.0",
|
|
57
57
|
"prettier": "^3.2.5",
|
|
58
|
-
"typedoc": "^0.28.
|
|
58
|
+
"typedoc": "^0.28.20",
|
|
59
59
|
"typedoc-plugin-markdown": "^4.11.0",
|
|
60
60
|
"typescript": "~6.0.2"
|
|
61
61
|
},
|