@pexip/media 18.4.0 → 19.0.0

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.
@@ -1,15 +1,14 @@
1
- import { getUserMedia, hasChangedInput, isMediaDeviceInfo, } from '@pexip/media-control';
2
- import { createAsyncQueue } from '@pexip/utils';
3
- import { UserMediaStatus } from './types';
4
- import { buildMedia, cloneMedia, createMediaPipeline, hasSettingsChanged, AUDIO_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, } from './utils';
1
+ import { hasChangedInput, isMediaDeviceInfo, reverseDeviceId, } from '@pexip/media-control';
2
+ import { createAsyncQueue, assert } from '@pexip/utils';
3
+ import { AUDIO_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, createMediaProcessor, hasSettingsChanged, } from './utils';
5
4
  import { isMedia } from './typeGuard';
6
5
  import { createModuleLogger } from './logger';
7
- import { isInitial } from './status';
8
- import { createGetUserMediaProcess } from './userMedia';
9
- const DEFAULT_QUEUE_SIZE = 1;
10
- const DEFAULT_QUEUE_THROTTLE_MS = 500;
6
+ import { createGetUserMediaProcess, requestUserMediaWithRetry, } from './userMedia';
7
+ import { createMediaUpdater } from './media';
11
8
  const DEFAULT_QUEUE_DELAY_MS = 100;
12
9
  const DEFAULT_QUEUE_DROP_LAST = false;
10
+ const DEFAULT_QUEUE_SIZE = 1;
11
+ const DEFAULT_QUEUE_THROTTLE_MS = 500;
13
12
  const isPreviewInput = (value) => {
14
13
  if (isMediaDeviceInfo(value) || value === undefined) {
15
14
  return true;
@@ -33,18 +32,14 @@ const createEventHandler = (eventHandlers) => (key) => (callback) => {
33
32
  eventHandlers[key] = undefined;
34
33
  };
35
34
  };
36
- const getUM = async (constraints) => [
37
- await getUserMedia(constraints),
38
- UserMediaStatus.PermissionsGranted,
39
- ];
40
35
  const createAudioVideoProcessingSettingsChangeDetector = (getCurrentMedia, getPreviewMedia) => {
41
36
  const hasAudioSettingsChanged = hasSettingsChanged(AUDIO_SETTINGS_KEYS);
42
37
  const hasVideoSettingsChanged = hasSettingsChanged(VIDEO_SETTINGS_KEYS);
43
38
  return () => {
44
39
  const currentMedia = getCurrentMedia?.();
45
40
  const previewMedia = getPreviewMedia();
46
- const { audio: [mainAudio], video: [mainVideo], } = currentMedia?.getSettings() ?? { audio: [], video: [] };
47
- const { audio: [previewAudio], video: [previewVideo], } = previewMedia.getSettings();
41
+ const { audio: mainAudio, video: mainVideo } = currentMedia?.getSettings() ?? { audio: {}, video: {} };
42
+ const { audio: previewAudio, video: previewVideo } = previewMedia?.getSettings() ?? { audio: {}, video: {} };
48
43
  const changed = hasAudioSettingsChanged(mainAudio, previewAudio) ||
49
44
  hasVideoSettingsChanged(mainVideo, previewVideo);
50
45
  return changed;
@@ -55,18 +50,11 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
55
50
  throttleInMS: DEFAULT_QUEUE_THROTTLE_MS,
56
51
  delayInMS: DEFAULT_QUEUE_DELAY_MS,
57
52
  dropLast: DEFAULT_QUEUE_DROP_LAST,
58
- }, processors, }) => {
53
+ }, audioProcessors, videoProcessors, }) => {
59
54
  const queue = createAsyncQueue(queueOptions);
60
55
  const eventHandlers = {};
61
- const release = () => {
62
- if (isInitial(props.media.status)) {
63
- props.discardMedia = true;
64
- }
65
- props.media = buildMedia(() => ({ release }));
66
- return Promise.resolve();
67
- };
68
56
  const internalProps = {
69
- media: buildMedia(() => ({ release })),
57
+ media: undefined,
70
58
  updatingPreview: false,
71
59
  updatingMain: false,
72
60
  discardMedia: false,
@@ -127,30 +115,68 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
127
115
  }
128
116
  },
129
117
  });
130
- const postMediaPipeline = createMediaPipeline(
131
- // Ignore the type conversion between tuple and array
132
- processors);
118
+ const getUserMedia = createGetUserMediaProcess({
119
+ getUserMedia: requestUserMediaWithRetry(() => Promise.resolve(getCurrentDevices())),
120
+ getCurrentDevices: () => Promise.resolve(getCurrentDevices()),
121
+ });
122
+ const processMedia = createMediaProcessor({
123
+ audioProcessors,
124
+ videoProcessors,
125
+ onProcessingError(error, track) {
126
+ logger.error({ error, track }, 'Failed to process media track');
127
+ },
128
+ });
129
+ const processAndUpdateMedia = async (media, tracks) => {
130
+ try {
131
+ const processedTracks = await processMedia(tracks);
132
+ for (const [idx, track] of processedTracks.entries()) {
133
+ const originTrack = tracks.at(idx);
134
+ assert(originTrack, 'Processed track should always has the original track in the same order');
135
+ // Only replace track when they are not the same
136
+ if (originTrack.id !== track.id) {
137
+ media.removeTrack(originTrack);
138
+ media.addTrack(track);
139
+ }
140
+ }
141
+ }
142
+ catch (error) {
143
+ logger.error({ error, tracks }, 'Failed to process media tracks');
144
+ }
145
+ };
146
+ const updateMediaProcess = createMediaUpdater({
147
+ getUserMedia,
148
+ getCurrentDevices: () => Promise.resolve(getCurrentDevices()),
149
+ shouldDiscardMedia: () => props.discardMedia,
150
+ onMediaTracksChanged: (media, tracks) => {
151
+ props.media = media;
152
+ queue.enqueue(async () => {
153
+ await processAndUpdateMedia(media, tracks);
154
+ });
155
+ },
156
+ });
133
157
  const initFromMain = (mainMedia) => {
134
- if (!mainMedia.stream) {
158
+ if (!mainMedia?.stream) {
135
159
  return;
136
160
  }
137
- postMediaPipeline
138
- .execute(cloneMedia(mainMedia))
139
- .then(media => {
140
- props.media = media;
141
- })
142
- .catch((error) => {
161
+ try {
162
+ const clonedMedia = mainMedia.clone();
163
+ props.media = clonedMedia;
164
+ queue.enqueue(async () => {
165
+ await processAndUpdateMedia(clonedMedia, clonedMedia.getTracks());
166
+ });
167
+ props.audioInput = mainMedia.audioInput;
168
+ props.videoInput = mainMedia.videoInput;
169
+ props.originalMainAudioInput = mainMedia.audioInput;
170
+ }
171
+ catch (error) {
143
172
  logger.error({ error }, 'Failed to post process media');
144
- })
145
- .finally(() => {
173
+ }
174
+ finally {
146
175
  props.initialized = true;
147
176
  if (props.discardMedia) {
148
- void props.media.release();
177
+ void props.media?.release();
149
178
  }
150
- });
151
- props.audioInput = mainMedia.audioInput;
152
- props.videoInput = mainMedia.videoInput;
153
- props.originalMainAudioInput = mainMedia.audioInput;
179
+ }
154
180
  };
155
181
  const mainMedia = getCurrentMedia();
156
182
  if (mainMedia?.stream) {
@@ -165,31 +191,29 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
165
191
  await updateMainStream(constraints);
166
192
  props.updatingMain = false;
167
193
  };
168
- const mediaPipeline = createMediaPipeline(() => [
169
- createGetUserMediaProcess(getUM, getCurrentDevices, {
170
- initialMedia: props.media,
171
- scope: 'PreviewStreamController',
172
- }),
173
- ...processors,
174
- ]);
175
194
  const updatePreviewMedia = async (constraints) => {
176
195
  logger.debug({ constraints }, 'Requesting a new preview stream');
177
- await props.media?.release();
178
- const media = await mediaPipeline.execute(constraints);
179
- if (props.discardMedia) {
180
- logger.debug('Discard media');
181
- return await media.release();
196
+ await updateMediaProcess(constraints, props.media);
197
+ logger.debug({ media: props.media }, 'Preview media updated');
198
+ };
199
+ const releaseAudio = async () => {
200
+ for (const track of props.media?.getAudioTracks() ?? []) {
201
+ await track.release();
202
+ }
203
+ };
204
+ const releaseVideo = async () => {
205
+ for (const track of props.media?.getVideoTracks() ?? []) {
206
+ await track.release();
182
207
  }
183
- props.media = media;
184
208
  };
185
209
  const updateAudioInput = async (input) => {
186
- props.audioInput = input;
187
- const request = {
188
- audio: { device: { exact: input } },
189
- video: props.videoInput,
190
- };
210
+ const request = { audio: { device: { exact: input } } };
191
211
  try {
212
+ props.audioInput = input;
192
213
  props.updatingPreview = true;
214
+ if (props.audioInput === undefined) {
215
+ return await releaseAudio();
216
+ }
193
217
  await updatePreviewMedia(request);
194
218
  }
195
219
  catch (error) {
@@ -227,13 +251,13 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
227
251
  }
228
252
  };
229
253
  const updateVideoInput = async (input) => {
230
- props.videoInput = input;
231
254
  try {
255
+ props.videoInput = input;
232
256
  props.updatingPreview = true;
233
- await updatePreviewMedia({
234
- audio: props.audioInput,
235
- video: { device: { exact: input } },
236
- });
257
+ if (input === undefined) {
258
+ return await releaseVideo();
259
+ }
260
+ return await updatePreviewMedia({ video: { device: { exact: input } } });
237
261
  }
238
262
  catch (error) {
239
263
  if (error instanceof Error) {
@@ -248,7 +272,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
248
272
  };
249
273
  const cleanup = async () => {
250
274
  logger.debug('Cleanup preview controller');
251
- await props.media.release();
275
+ await props.media?.release();
252
276
  cleanUpMainSubscription();
253
277
  props.audioInput = undefined;
254
278
  props.videoInput = undefined;
@@ -262,7 +286,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
262
286
  hasAudioVideoProcessingSettingsChanged();
263
287
  const applyChanges = async (force = false) => {
264
288
  const { audioInput, videoInput } = props;
265
- const { audio: [previewAudio], video: [previewVideo], } = props.media.getSettings();
289
+ const { audio: previewAudio, video: previewVideo } = props.media?.getSettings() ?? { audio: {}, video: {} };
266
290
  if (force || hasChanges()) {
267
291
  const audioSettings = extractFeaturesToConstraints(AUDIO_SETTINGS_KEYS, previewAudio);
268
292
  const videoSettings = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
@@ -273,8 +297,12 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
273
297
  logger.info({ audioInput, videoInput, audioSettings, videoSettings }, 'Apply changes to main');
274
298
  try {
275
299
  await replaceMainStream({
276
- audio: { device: audioInput, ...audioSettings },
277
- video: { device: videoInput, ...videoSettings },
300
+ audio: audioInput
301
+ ? { device: audioInput, ...audioSettings }
302
+ : false,
303
+ video: videoInput
304
+ ? { device: videoInput, ...videoSettings }
305
+ : false,
278
306
  });
279
307
  props.originalMainAudioInput = props.audioInput;
280
308
  }
@@ -321,7 +349,10 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
321
349
  }
322
350
  }
323
351
  };
324
- const updateInput = (hasChanged, updater) => (input) => {
352
+ const updateInput = (hasChanged, updater) => (id) => {
353
+ const [input] = id
354
+ ? getCurrentDevices().get(reverseDeviceId(id))
355
+ : [];
325
356
  if (hasChanged(input)) {
326
357
  queue.enqueue(async () => {
327
358
  await updater(input);
package/dist/signals.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Signal, SignalVariant } from '@pexip/signal';
2
+ import type { IndexedDevices } from '@pexip/media-control';
2
3
  import type { MediaSignals } from './types';
3
4
  /**
4
5
  * Create a general signal with consistent scoped name
@@ -22,4 +23,12 @@ export declare const REQUIRED_SIGNAL_KEYS: readonly ["onMediaChanged", "onVAD",
22
23
  *
23
24
  * @see REQUIRED_SIGNAL_KEYS
24
25
  */
25
- export declare const createMediaSignals: <K extends "onDevicesChanged" | "onStatusChanged" | "onAudioMuteStateChanged" | "onVideoMuteStateChanged" | keyof import("./types").StreamTrackSignals>(more: K[], scope?: string) => Pick<Required<MediaSignals>, "onMediaChanged" | "onVAD" | "onSilentDetected" | K>;
26
+ export declare const createMediaSignals: <K extends "onAddTrack" | "onAudioMuteStateChanged" | "onTrackReleased" | "onDevicesChanged" | "onRemoveTrack" | "onStatusChanged" | "onVideoMuteStateChanged" | "onUpdatingMedia" | keyof import("./types").StreamTrackSignals | "onStreamTrackMutedFinal" | "onStreamTrackUnmutedFinal" | "onStreamTrackEndedFinal" | "onStreamTrackEnabledFinal">(more: K[], scope?: string) => Pick<Required<MediaSignals>, "onMediaChanged" | "onVAD" | "onSilentDetected" | K>;
27
+ export type GeneratedMediaSignals<K extends keyof MediaSignals> = Pick<Required<MediaSignals>, K>;
28
+ /**
29
+ * Internal signals
30
+ * @internal
31
+ */
32
+ export declare const internalSignals: {
33
+ onDevicesChanged: Signal<IndexedDevices>;
34
+ };
package/dist/signals.js CHANGED
@@ -36,3 +36,12 @@ export const createMediaSignals = (more, scope = '') => {
36
36
  [key]: createMediaSignal(`${signalScope}${key}`),
37
37
  }), {});
38
38
  };
39
+ /**
40
+ * Internal signals
41
+ * @internal
42
+ */
43
+ export const internalSignals = {
44
+ onDevicesChanged: createSignal({
45
+ name: 'media/devicesChanged',
46
+ }),
47
+ };
package/dist/status.d.ts CHANGED
@@ -32,5 +32,8 @@ export declare const isDeviceInUse: UserMediaValidator;
32
32
  export declare const isDevicesInUse: UserMediaValidator;
33
33
  export declare const isVideoDeviceInUse: UserMediaValidator;
34
34
  export declare const toDeniedDevices: (status?: UserMediaStatus) => DeniedDevices | undefined;
35
- export declare const getPermissionStatus: (getPermissionState?: (anyActiveStream?: boolean | undefined) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus.Initial | UserMediaStatus.InitialPermissionsGranted | UserMediaStatus.InitialPermissionsNotGranted | UserMediaStatus.InitialPermissionsVideoInputDenied | UserMediaStatus.InitialPermissionsAudioInputDenied | UserMediaStatus.InitialPermissionsVideoInputGranted | UserMediaStatus.InitialPermissionsAudioInputGranted | UserMediaStatus.InitialPermissionsGrantedVideoInputDenied | UserMediaStatus.InitialPermissionsGrantedAudioInputDenied | UserMediaStatus.PermissionsRejected>;
36
- export declare const deriveInitialPermissionStatus: (prevStatus: UserMediaStatus, getPermissionState?: (anyActiveStream?: boolean | undefined) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus>;
35
+ export declare const getPermissionStatus: ({ audio, video, }: {
36
+ audio: PermissionState;
37
+ video: PermissionState;
38
+ }) => UserMediaStatus.Initial | UserMediaStatus.InitialPermissionsGranted | UserMediaStatus.InitialPermissionsNotGranted | UserMediaStatus.InitialPermissionsVideoInputDenied | UserMediaStatus.InitialPermissionsAudioInputDenied | UserMediaStatus.InitialPermissionsVideoInputGranted | UserMediaStatus.InitialPermissionsAudioInputGranted | UserMediaStatus.InitialPermissionsGrantedVideoInputDenied | UserMediaStatus.InitialPermissionsGrantedAudioInputDenied | UserMediaStatus.PermissionsRejected;
39
+ export declare const deriveInitialPermissionStatus: (prevStatus: UserMediaStatus, getPermissionState?: (anyActiveAudioTrack?: boolean | undefined, anyActiveVideoTrack?: boolean | undefined, getDevices?: (() => Promise<MediaDeviceInfo[]>) | undefined) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus>;
package/dist/status.js CHANGED
@@ -137,8 +137,7 @@ export const toDeniedDevices = (status) => {
137
137
  return DeniedDevices.Both;
138
138
  }
139
139
  };
140
- export const getPermissionStatus = async (getPermissionState = getInputDevicePermissionState) => {
141
- const { audio, video } = await getPermissionState();
140
+ export const getPermissionStatus = ({ audio, video, }) => {
142
141
  if (audio === 'denied') {
143
142
  if (video === 'denied') {
144
143
  return UserMediaStatus.PermissionsRejected;
@@ -196,5 +195,6 @@ export const deriveInitialPermissionStatus = async (prevStatus, getPermissionSta
196
195
  if (prevStatus === UserMediaStatus.DevicesInUse) {
197
196
  return UserMediaStatus.InitialPermissionsGranted;
198
197
  }
199
- return await getPermissionStatus(getPermissionState);
198
+ const permission = await getPermissionState();
199
+ return getPermissionStatus(permission);
200
200
  };
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { InputConstraintSet, MediaDeviceInfoLike, MediaDeviceRequest } from '@pexip/media-control';
1
+ import type { IndexedDevices, InputConstraintSet, InputDeviceConstraint, MediaDeviceInfoLike, MediaDeviceRequest } from '@pexip/media-control';
2
2
  import type { RendererOptions, RenderEffects, Segmenter, SegmentationModel } from '@pexip/media-processor';
3
3
  import type { Signal } from '@pexip/signal';
4
4
  export type Unsubscribe = () => void;
@@ -22,8 +22,8 @@ export interface ExtendedMediaTrackSettings extends MediaTrackSettings {
22
22
  }
23
23
  export type ExtendedMediaTrackSettingsKey = keyof ExtendedMediaTrackSettings;
24
24
  export interface MediaSettings {
25
- audio: ExtendedMediaTrackSettings[];
26
- video: ExtendedMediaTrackSettings[];
25
+ audio?: ExtendedMediaTrackSettings;
26
+ video?: ExtendedMediaTrackSettings;
27
27
  }
28
28
  /**
29
29
  * URL links needed for creating a denoise node for audio processing
@@ -44,29 +44,26 @@ export interface DenoiseParams {
44
44
  }
45
45
  export interface MediaAttributes {
46
46
  /**
47
- * The constraints used to request the media
48
- */
49
- constraints?: MediaDeviceRequest;
50
- /**
51
- * The devices used for the media
47
+ * Indicating whether the media is active
48
+ *
49
+ * A stream is considered active if at least one of its MediaStreamTracks
50
+ * does not have its property MediaStreamTrack.readyState set to ended.
51
+ * Once every track has ended, the stream's active property becomes false.
52
52
  */
53
- devices: MediaDeviceInfoLike[];
53
+ readonly active: boolean;
54
54
  /**
55
- * Media stream for the media
55
+ * A string containing a 36-character universally unique identifier (UUID)
56
+ * for the object.
56
57
  */
57
- stream?: MediaStream;
58
+ readonly id: string;
58
59
  /**
59
- * The raw stream obtained from the `getUserMedia` API
60
+ * The devices used for the media
60
61
  */
61
- rawStream?: MediaStream;
62
+ devices: IndexedDevices;
62
63
  /**
63
- * Audio input device is used for the audio track from the current stream
64
+ * Media stream for the media
64
65
  */
65
- audioInput?: MediaDeviceInfoLike;
66
- /**
67
- * Video input device is used for the video track from the current stream
68
- */
69
- videoInput?: MediaDeviceInfoLike;
66
+ stream: MediaStream;
70
67
  /**
71
68
  * The audio input device which is expected to be used for the current
72
69
  * stream based on the provided constraints
@@ -91,6 +88,14 @@ export interface MediaAttributes {
91
88
  * `undefined` means there is no such track from the stream
92
89
  */
93
90
  videoMuted: boolean | undefined;
91
+ /**
92
+ * Audio input device is used for the audio track from the current stream
93
+ */
94
+ audioInput?: MediaDeviceInfoLike;
95
+ /**
96
+ * Video input device is used for the video track from the current stream
97
+ */
98
+ videoInput?: MediaDeviceInfoLike;
94
99
  }
95
100
  export interface Media extends MediaAttributes {
96
101
  /**
@@ -109,19 +114,95 @@ export interface Media extends MediaAttributes {
109
114
  * Release the media resources, e.g. camera/microphone
110
115
  */
111
116
  release(): Promise<void>;
117
+ getConstraints(): MediaDeviceRequest;
118
+ setOriginalConstraints(constraints: MediaDeviceRequest): void;
119
+ getOriginalConstraints(): MediaDeviceRequest;
120
+ isAudioInputUnavailable(): boolean;
121
+ isVideoInputUnavailable(): boolean;
122
+ requestedAudio(): boolean;
123
+ requestedVideo(): boolean;
112
124
  getSettings(): MediaSettings;
113
- toJSON?: () => unknown;
125
+ toJSON(): unknown;
126
+ getTracks(): MediaTrack[];
127
+ getAudioTracks(): MediaTrack[];
128
+ getVideoTracks(): MediaTrack[];
129
+ addTrack(track: MediaTrack): void;
130
+ removeTrack(track: MediaTrack): void;
131
+ clone(): Media;
114
132
  }
115
- export type Process<T> = (a: T) => Promise<Media>;
116
- export type MediaProcessor = Process<Promise<Media>>;
117
- /**
118
- * A media pipeline to get and process media
119
- */
120
- export type Pipeline<T = MediaDeviceRequest> = [
121
- Process<T>,
122
- ...MediaProcessor[]
123
- ];
124
- export type ProcessMedia = (media: Media) => undefined | Media;
133
+ export interface MediaInit {
134
+ constraints: MediaDeviceRequest;
135
+ permission: {
136
+ audio: PermissionState;
137
+ video: PermissionState;
138
+ };
139
+ originalConstraints?: MediaDeviceRequest;
140
+ tracks: MediaTrack[];
141
+ devices: IndexedDevices;
142
+ status: UserMediaStatus;
143
+ stream: MediaStream | undefined;
144
+ signals?: MediaSignals;
145
+ }
146
+ export interface MediaTrack {
147
+ /**
148
+ * A unique identifier for the track same as `MediaStreamTrack.id` if
149
+ * `track` is available otherwise it uses the `id` from the construction or the
150
+ * `kind`. @see MediaTrackInit
151
+ */
152
+ id: string;
153
+ kind: 'audioinput' | 'videoinput';
154
+ label?: string;
155
+ previousMediaTrack?: MediaTrack;
156
+ /**
157
+ * The MediaStreamTrack object representing the track
158
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack
159
+ */
160
+ track?: MediaStreamTrack;
161
+ /**
162
+ * The `MediaDeviceInfo` object representing the device that the track is
163
+ * connected to. @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo
164
+ */
165
+ input?: MediaDeviceInfoLike;
166
+ /**
167
+ * The `MediaDeviceInfo` object representing the device that the track is
168
+ * expected to be connected to. @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo
169
+ */
170
+ expectedInput?: MediaDeviceInfoLike;
171
+ /**
172
+ * The current mute state of the track. `undefined` means the track is not available
173
+ */
174
+ muted?: boolean;
175
+ /**
176
+ * The source track that it is originated from
177
+ */
178
+ source: MediaTrack;
179
+ mute(toMute: boolean): void;
180
+ getSettings(): ExtendedMediaTrackSettings;
181
+ getConstraints(): InputDeviceConstraint;
182
+ applyConstraints(constraints: InputDeviceConstraint): Promise<void>;
183
+ release(): Promise<void>;
184
+ clone(): MediaTrack;
185
+ toJSON(): unknown;
186
+ }
187
+ export interface MediaTrackInit extends Partial<MediaTrack> {
188
+ kind: 'audioinput' | 'videoinput';
189
+ input: MediaDeviceInfoLike | undefined;
190
+ expectedInput?: MediaDeviceInfoLike | undefined;
191
+ constraints: InputDeviceConstraint;
192
+ overrideMute?: boolean;
193
+ signals?: MediaSignals;
194
+ }
195
+ export type Process<T> = (a: T) => Promise<MediaTrack>;
196
+ export type TrackProcessor = Process<MediaTrack>;
197
+ export type GetUserMediaProcess = (request: {
198
+ constraints: MediaDeviceRequest;
199
+ originalConstraints: MediaDeviceRequest;
200
+ permission: {
201
+ audio: PermissionState;
202
+ video: PermissionState;
203
+ };
204
+ currentMedia: Media | undefined;
205
+ }) => Promise<Media>;
125
206
  /**
126
207
  * Use which processor API to process the stream track
127
208
  * `stream` - Use MediaStreamTrackProcessor, when available
@@ -361,7 +442,8 @@ export interface MediaOptions {
361
442
  /**
362
443
  * Media Processors
363
444
  */
364
- mediaProcessors: MediaProcessor[];
445
+ audioProcessors: TrackProcessor[];
446
+ videoProcessors: TrackProcessor[];
365
447
  /**
366
448
  * A function to get the devices' mute state
367
449
  */
@@ -378,25 +460,27 @@ export interface MediaProps {
378
460
  /**
379
461
  * Current media
380
462
  */
381
- media: Media;
463
+ media?: Media;
382
464
  /**
383
465
  * Current device
384
466
  */
385
- devices: MediaDeviceInfoLike[];
467
+ devices: IndexedDevices;
386
468
  /**
387
469
  * When should we discard the requested MediaStream
388
470
  */
389
471
  discardMedia: boolean;
472
+ updatingMedia: boolean;
390
473
  }
391
474
  export interface MediaController {
475
+ readonly updatingMedia: boolean;
392
476
  /**
393
477
  * Current Media
394
478
  */
395
- media: Media;
479
+ readonly media?: Media;
396
480
  /**
397
481
  * Current device list
398
482
  */
399
- devices: MediaDeviceInfoLike[];
483
+ readonly devices: IndexedDevices;
400
484
  /**
401
485
  * Execute the media pipeline immediately with provided constraints
402
486
  *
@@ -454,18 +538,29 @@ export interface StreamTrackSignals {
454
538
  */
455
539
  onStreamTrackEnabled: Signal<MediaStreamTrack>;
456
540
  }
541
+ /**
542
+ * Track state signals that used for the final track that the media pipeline
543
+ * produced
544
+ */
545
+ export type StreamTrackSignalFinals = {
546
+ [key in keyof StreamTrackSignals as `${key}Final`]: Signal<MediaStreamTrack>;
547
+ };
457
548
  export interface MediaChangesSignals {
458
- onDevicesChanged: Signal<MediaDeviceInfoLike[]>;
549
+ onAddTrack: Signal<MediaStreamTrack>;
550
+ onAudioMuteStateChanged: Signal<boolean | undefined>;
551
+ onTrackReleased: Signal<MediaStreamTrack>;
552
+ onDevicesChanged: Signal<IndexedDevices>;
553
+ onMediaChanged: Signal<Media | undefined>;
554
+ onRemoveTrack: Signal<MediaStreamTrack>;
459
555
  onStatusChanged: Signal<UserMediaStatus>;
460
- onMediaChanged: Signal<Media>;
461
- onAudioMuteStateChanged: Signal<boolean>;
462
- onVideoMuteStateChanged: Signal<boolean>;
556
+ onVideoMuteStateChanged: Signal<boolean | undefined>;
557
+ onUpdatingMedia: Signal<boolean>;
463
558
  }
464
559
  export interface AudioDetectionSignals {
465
560
  onVAD: Signal<undefined>;
466
561
  onSilentDetected: Signal<boolean>;
467
562
  }
468
- export type MediaSignalsOptional = Pick<Partial<MediaChangesSignals>, 'onDevicesChanged' | 'onStatusChanged' | 'onAudioMuteStateChanged' | 'onVideoMuteStateChanged'> & Partial<StreamTrackSignals>;
563
+ export type MediaSignalsOptional = Omit<Partial<MediaChangesSignals>, 'onMediaChanged'> & Partial<StreamTrackSignals> & Partial<StreamTrackSignalFinals>;
469
564
  export type MediaSignalsRequired = Pick<MediaChangesSignals, 'onMediaChanged'> & AudioDetectionSignals;
470
565
  export type MediaSignals = MediaSignalsRequired & MediaSignalsOptional;
471
566
  export declare enum DeniedDevices {
@@ -1,16 +1,17 @@
1
- import type { MediaDeviceRequest, MediaDeviceInfoLike } from '@pexip/media-control';
2
- import type { Process, Media } from './types';
1
+ import type { MediaDeviceRequest, IndexedDevices } from '@pexip/media-control';
2
+ import type { GetUserMediaProcess, MediaSignals } from './types';
3
3
  import { UserMediaStatus } from './types';
4
4
  import { makeDeriveDeviceStatus } from './utils';
5
5
  export type UserMedia = [MediaStream | undefined, UserMediaStatus];
6
6
  export type GetUserMedia<T extends Promise<Iterable<unknown>> = Promise<UserMedia>> = (constraints: MediaDeviceRequest) => T;
7
+ export type GetCurrentDevices = () => Promise<IndexedDevices>;
7
8
  export declare const toErrorDeviceStatus: (error: Error, deriveDeviceStatus: ReturnType<typeof makeDeriveDeviceStatus>) => UserMediaStatus;
8
9
  export declare const mapErrorStatus: (status: UserMediaStatus) => (constraints: MediaDeviceRequest) => UserMediaStatus;
9
10
  export declare const toSameDeviceStatus: ({ audio, video, }: {
10
11
  audio: boolean;
11
12
  video: boolean;
12
13
  }) => UserMediaStatus.PermissionsGranted | UserMediaStatus.PermissionsGrantedFallback | UserMediaStatus.PermissionsGrantedFallbackAudioinput | UserMediaStatus.PermissionsGrantedFallbackVideoinput;
13
- export declare const toOnlyDeviceStatus: (kind: Extract<MediaDeviceKind, 'audioinput' | 'videoinput'>, matched: boolean, devices: MediaDeviceInfoLike[]) => 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.PermissionsOnlyAudioinput | UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices | UserMediaStatus.PermissionsOnlyAudioinputFallback | UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices | UserMediaStatus.PermissionsOnlyVideoinput | UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices | UserMediaStatus.PermissionsOnlyVideoinputFallback | UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
14
15
  /**
15
16
  * Try to come up with an error level according to provided UserMediaStatus and
16
17
  * MediaDeviceRequest
@@ -20,19 +21,20 @@ export declare const toOnlyDeviceStatus: (kind: Extract<MediaDeviceKind, 'audioi
20
21
  * @param constraints - The constraints used for the request
21
22
  */
22
23
  export declare const deriveErrorLevel: (error: Error, status: UserMediaStatus, constraints: MediaDeviceRequest) => "error" | "warn";
23
- export declare const requestUserMedia: (currentDevices?: MediaDeviceInfoLike[], getMedia?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia;
24
+ export declare const requestUserMedia: (getCurrentDevices: GetCurrentDevices, getMedia?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia;
24
25
  export declare const mergeNoDeviceStatus: (constraints: MediaDeviceRequest, anyDevices: {
25
26
  audio: boolean;
26
27
  video: boolean;
27
28
  }, status: UserMediaStatus) => UserMediaStatus;
28
- export declare const requestUserMediaWithRetry: (currentDevices?: MediaDeviceInfoLike[], createRequestUserMedia?: (currentDevices?: MediaDeviceInfoLike[], getMedia?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia) => GetUserMedia;
29
+ export declare const requestUserMediaWithRetry: (getCurrentDevices: GetCurrentDevices, createRequestUserMedia?: (getCurrentDevices: GetCurrentDevices, getMedia?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia, gUM?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia;
29
30
  interface Options {
30
- initialMedia: Media;
31
+ getUserMedia: GetUserMedia;
32
+ getCurrentDevices: GetCurrentDevices;
33
+ signals?: MediaSignals;
31
34
  scope?: string;
32
35
  }
33
- type CreateGetUserMediaProcess = (getUserMedia: GetUserMedia, getCurrentDevices: () => MediaDeviceInfoLike[], options: Options) => Process<MediaDeviceRequest>;
34
36
  /**
35
37
  * A process to get user media
36
38
  */
37
- export declare const createGetUserMediaProcess: CreateGetUserMediaProcess;
39
+ export declare const createGetUserMediaProcess: ({ getUserMedia, getCurrentDevices, signals, scope, }: Options) => GetUserMediaProcess;
38
40
  export {};