@pexip/media 18.5.0 → 19.1.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.
package/dist/media.js CHANGED
@@ -1,14 +1,103 @@
1
- import { subscribe, MediaEventType, MediaDeviceFailure, createStreamTrackEventSubscriptions, getDevices, isRequestedResolution, mergeConstraints, shouldRequestDevice, extractConstrainDevice, } from '@pexip/media-control';
2
- import { createAsyncQueue, isEmpty } from '@pexip/utils';
3
- import { getPermissionStatus, deriveInitialPermissionStatus, isInitialPermissions, isInitialPermissionsGranted, isVideoDeviceInUse, isDevicesInUse, isAudioDeviceInUse, } from './status';
1
+ import { MediaEventType, createIndexedDevices, createStreamTrackEventSubscriptions, getDevices, getInputDevicePermissionState, isIndexedDevices, isRequestedResolution, mergeConstraints, subscribe, } from '@pexip/media-control';
2
+ import { createAsyncQueue, isEmpty, assert } from '@pexip/utils';
3
+ import { internalSignals } from './signals';
4
4
  import { UserMediaStatus } from './types';
5
5
  import { createModuleLogger, logger } from './logger';
6
6
  import { createGetUserMediaProcess, requestUserMediaWithRetry, } from './userMedia';
7
- import { createMediaPipeline, createMediaProcess, buildMedia, getDevicesChanges, shallowCopy, wrapToJSON, hasSettingsChanged, AUDIO_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, MIXING_SETTINGS_KEYS, applyContentHint, hasPtzFeature, } from './utils';
7
+ import { AUDIO_SETTINGS_KEYS, MIXING_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, buildMedia, createMediaProcessor, diffSettings, getDevicesChanges, hasPtzFeature, mergeSettings, refineMediaConstraints, } from './utils';
8
+ import { getPermissionStatus, isInitialPermissionsGranted } from './status';
8
9
  import { isMedia } from './typeGuard';
9
10
  import { updateFeatureProps as getVideoFeatures } from './videoProcessor';
10
11
  import { updateFeatureProps as getAudioFeatures } from './audioProcessor';
11
12
  import { updateFeatureProps as getMixingFeatures } from './audioMixingProcessor';
13
+ import { GET_USER_MEDIA_TIMEOUT_MS } from './constants';
14
+ export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDiscardMedia: shouldDisgardMedia, onMediaTracksChanged, getInputDevicePermission = getInputDevicePermissionState, signals, }) => {
15
+ return async (constraints, currentMedia) => {
16
+ const currentDevices = await getCurrentDevices();
17
+ const permission = await getInputDevicePermission();
18
+ const requestNothing = !constraints.audio && !constraints.video;
19
+ const permissionRejected = permission.audio === 'denied' && permission.video === 'denied';
20
+ if (requestNothing || permissionRejected) {
21
+ for (const track of currentMedia?.getTracks() ?? []) {
22
+ await track.release();
23
+ currentMedia?.removeTrack(track);
24
+ }
25
+ currentMedia?.setOriginalConstraints(constraints);
26
+ const media = currentMedia ??
27
+ buildMedia({
28
+ constraints,
29
+ permission,
30
+ devices: currentDevices,
31
+ status: permissionRejected
32
+ ? UserMediaStatus.PermissionsRejected
33
+ : UserMediaStatus.PermissionsGranted,
34
+ stream: undefined,
35
+ signals,
36
+ tracks: [],
37
+ });
38
+ return onMediaTracksChanged(media, []);
39
+ }
40
+ const { audio: prevAudioSettings, video: prevVideoSettings } = currentMedia?.getSettings() ?? {};
41
+ const videoFeatures = getVideoFeatures(constraints.video, {});
42
+ const audioFeatures = getAudioFeatures(constraints.audio, {});
43
+ const mixingFeatures = getMixingFeatures(constraints.audio, {});
44
+ const hasRequestedResolution = ['blur', 'overlay'].includes(videoFeatures.videoSegmentation ?? '') || // Skip when there is a render effect to modify the video
45
+ currentDevices.size('videoinput') === 0 || // Skip when no authorized video found
46
+ isRequestedResolution(constraints.video, currentMedia?.getVideoTracks().at(0)?.getSettings());
47
+ const audioFeaturesChanged = diffSettings(AUDIO_SETTINGS_KEYS)(prevAudioSettings, audioFeatures);
48
+ const videoFeaturesChanged = diffSettings(VIDEO_SETTINGS_KEYS)(prevVideoSettings, videoFeatures);
49
+ const mixingFeaturesChanged = diffSettings(MIXING_SETTINGS_KEYS)(prevAudioSettings, mixingFeatures);
50
+ const ptzFeaturesChanges = hasPtzFeature() &&
51
+ diffSettings(['pan', 'tilt', 'zoom'])(prevVideoSettings, videoFeatures);
52
+ const audioRequest = refineMediaConstraints({
53
+ kind: 'audioinput',
54
+ request: constraints.audio,
55
+ currentDevices,
56
+ permission: permission.audio,
57
+ currentMediaTracks: currentMedia?.getAudioTracks() ?? [],
58
+ });
59
+ const videoRequest = refineMediaConstraints({
60
+ kind: 'videoinput',
61
+ request: constraints.video,
62
+ currentDevices,
63
+ permission: permission.video,
64
+ currentMediaTracks: currentMedia?.getVideoTracks() ?? [],
65
+ force: !hasRequestedResolution || Boolean(ptzFeaturesChanges),
66
+ });
67
+ if (audioRequest !== undefined || videoRequest !== undefined) {
68
+ const media = await getUserMedia({
69
+ constraints: { audio: audioRequest, video: videoRequest },
70
+ originalConstraints: constraints,
71
+ permission,
72
+ currentMedia,
73
+ });
74
+ if (shouldDisgardMedia()) {
75
+ logger.debug('Discard media');
76
+ return await media.release();
77
+ }
78
+ onMediaTracksChanged(media, [
79
+ ...(audioRequest ? media.getAudioTracks() : []),
80
+ ...(videoRequest ? media.getVideoTracks() : []),
81
+ ]);
82
+ }
83
+ const audioDiff = constraints.audio &&
84
+ audioRequest === undefined &&
85
+ currentMedia?.getAudioTracks().at(0)
86
+ ? mergeSettings(audioFeaturesChanged, mixingFeaturesChanged)
87
+ : undefined;
88
+ const videoDiff = constraints.video &&
89
+ videoRequest === undefined &&
90
+ currentMedia?.getVideoTracks().at(0)
91
+ ? videoFeaturesChanged
92
+ : undefined;
93
+ if (audioDiff || videoDiff) {
94
+ await currentMedia?.applyConstraints({
95
+ audio: audioDiff,
96
+ video: videoDiff,
97
+ });
98
+ }
99
+ };
100
+ };
12
101
  /**
13
102
  * Proxy handler for Media Props
14
103
  */
@@ -24,8 +113,11 @@ const createMediaPropsHandler = (signals) => ({
24
113
  return true;
25
114
  }
26
115
  if (p === 'devices') {
116
+ if (!isIndexedDevices(value)) {
117
+ return false;
118
+ }
27
119
  const nextDevices = value;
28
- const changes = getDevicesChanges(target[p].flatMap(device => (device.label ? [device] : [])), nextDevices);
120
+ const changes = getDevicesChanges(target[p].get(), nextDevices.get());
29
121
  if (isEmpty(changes.found) && isEmpty(changes.lost)) {
30
122
  return true;
31
123
  }
@@ -40,20 +132,19 @@ const createMediaPropsHandler = (signals) => ({
40
132
  }, `Update Props[${p}]`);
41
133
  switch (p) {
42
134
  case 'devices': {
43
- if (!Array.isArray(value)) {
135
+ if (!isIndexedDevices(value)) {
44
136
  return false;
45
137
  }
46
- const devices = value;
47
- target[p] = devices;
48
- signals?.onDevicesChanged?.emit(devices);
49
- target.media.devices = devices;
138
+ target[p] = value;
139
+ signals?.onDevicesChanged?.emit(target[p]);
140
+ internalSignals.onDevicesChanged.emit(target[p]);
50
141
  return true;
51
142
  }
52
143
  case 'media': {
53
144
  if (!isMedia(value)) {
54
145
  return false;
55
146
  }
56
- const currentStatus = target[p].status;
147
+ const currentStatus = target[p]?.status;
57
148
  target[p] = value;
58
149
  signals?.onMediaChanged?.emit(value);
59
150
  if (currentStatus !== value.status) {
@@ -61,9 +152,13 @@ const createMediaPropsHandler = (signals) => ({
61
152
  }
62
153
  return true;
63
154
  }
155
+ case 'updatingMedia': {
156
+ const result = Reflect.set(target, p, value);
157
+ signals?.onUpdatingMedia?.emit(value);
158
+ return result;
159
+ }
64
160
  default: {
65
- Reflect.set(target, p, value);
66
- return true;
161
+ return Reflect.set(target, p, value);
67
162
  }
68
163
  }
69
164
  },
@@ -74,93 +169,137 @@ const createMediaPropsHandler = (signals) => ({
74
169
  *
75
170
  * @param options - @see MediaOptions
76
171
  */
77
- export const createMedia = ({ getMuteState, signals, mediaProcessors, getDefaultConstraints = () => ({}), }) => {
78
- const initMedia = (status = UserMediaStatus.Initial, devices = [], constraints) => buildMedia(() => ({ status, devices, constraints }), signals.onStatusChanged?.emit);
172
+ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProcessors, getDefaultConstraints = () => ({}), }) => {
79
173
  const _props = {
80
- devices: [],
81
- media: initMedia(),
174
+ devices: createIndexedDevices([]),
82
175
  discardMedia: false,
176
+ updatingMedia: false,
83
177
  };
84
178
  const props = new Proxy(_props, createMediaPropsHandler(signals));
85
- const queue = createAsyncQueue();
179
+ const queue = createAsyncQueue({
180
+ timeoutInMS: GET_USER_MEDIA_TIMEOUT_MS,
181
+ handleTimeout: async () => {
182
+ const status = UserMediaStatus.NoDevicesFound;
183
+ if (props.media) {
184
+ props.media.status = status;
185
+ }
186
+ else {
187
+ const permission = await getInputDevicePermissionState();
188
+ const devices = await getCurrentDevices();
189
+ props.media = buildMedia({
190
+ constraints: getDefaultConstraints(),
191
+ permission,
192
+ devices,
193
+ status: UserMediaStatus.NoDevicesFound,
194
+ stream: undefined,
195
+ signals,
196
+ tracks: [],
197
+ });
198
+ }
199
+ },
200
+ });
86
201
  const logger = createModuleLogger({
87
202
  module: 'Media',
88
203
  props: _props,
89
204
  get mediaTracks() {
90
205
  return _props.media?.stream?.getTracks();
91
206
  },
92
- get rawMediaTracks() {
93
- return _props.media?.rawStream?.getTracks();
207
+ });
208
+ const getCurrentDevices = async () => {
209
+ if (props.devices.size()) {
210
+ return props.devices;
211
+ }
212
+ props.devices = createIndexedDevices(await getDevices());
213
+ return props.devices;
214
+ };
215
+ const syncMuteState = (tracks) => {
216
+ const inputMuted = getMuteState();
217
+ for (const track of tracks) {
218
+ const prevMuted = track.muted;
219
+ switch (track.kind) {
220
+ case 'audioinput': {
221
+ track.mute(inputMuted.audio);
222
+ if (prevMuted !== track.muted) {
223
+ signals.onAudioMuteStateChanged?.emit(track.muted);
224
+ }
225
+ break;
226
+ }
227
+ case 'videoinput':
228
+ track.mute(inputMuted.video);
229
+ if (prevMuted !== track.muted) {
230
+ signals.onVideoMuteStateChanged?.emit(track.muted);
231
+ }
232
+ break;
233
+ default:
234
+ break;
235
+ }
236
+ }
237
+ };
238
+ const processMedia = createMediaProcessor({
239
+ audioProcessors,
240
+ videoProcessors,
241
+ onProcessingError(error, track) {
242
+ logger.error({ error, track }, 'Failed to process media track');
243
+ assert(props.media);
244
+ switch (track.kind) {
245
+ case 'audioinput':
246
+ // FIXME: It should not be device-not-found error, will be fixed with https://gitlab.com/pexip/zoo/-/issues/3793
247
+ props.media.status = UserMediaStatus.AudioDeviceNotFound;
248
+ break;
249
+ case 'videoinput':
250
+ // FIXME: It should not be device-not-found error, will be fixed with https://gitlab.com/pexip/zoo/-/issues/3793
251
+ props.media.status = UserMediaStatus.VideoDeviceNotFound;
252
+ break;
253
+ }
94
254
  },
95
255
  });
96
- const cleanup = async () => {
97
- if (isInitialPermissions(props.media.status)) {
98
- props.discardMedia = true;
256
+ const getUserMediaProcess = createGetUserMediaProcess({
257
+ getUserMedia: requestUserMediaWithRetry(getCurrentDevices),
258
+ getCurrentDevices,
259
+ signals,
260
+ });
261
+ const processAndUpdateMedia = async (media, tracks) => {
262
+ const processedTracks = await processMedia(tracks);
263
+ // Sync mute state to processed tracks
264
+ syncMuteState(processedTracks);
265
+ for (const [idx, track] of processedTracks.entries()) {
266
+ const originTrack = tracks.at(idx);
267
+ assert(originTrack, 'Processed track should always has the original track in the same order');
268
+ // Only replace track when they are not the same
269
+ if (originTrack.id !== track.id) {
270
+ media.removeTrack(originTrack);
271
+ media.addTrack(track);
272
+ }
99
273
  }
100
- // carry the most recent status over for the next time
101
- const status = await deriveInitialPermissionStatus(props.media.status);
102
- props.media = initMedia(status, props.devices, props.media.constraints);
103
274
  };
104
- // Media Pipeline
105
- const mediaPipeline = createMediaPipeline([
106
- createGetUserMediaProcess(requestUserMediaWithRetry(), () => props.devices, { initialMedia: props.media }),
107
- ...mediaProcessors,
108
- createMediaProcess(media => {
109
- // Subscribe the track event from raw stream
110
- const trackSubscriptions = media.rawStream?.getTracks().map(track => createStreamTrackEventSubscriptions(track, {
111
- ended: signals.onStreamTrackEnded?.emit,
112
- mute: signals.onStreamTrackMuted?.emit,
113
- unmute: signals.onStreamTrackUnmuted?.emit,
114
- }));
115
- const muteTrack = (tracks) => (muted) => {
116
- const [track] = tracks;
117
- const kind = track?.kind;
118
- if (track && (kind === 'audio' || kind === 'video')) {
119
- media[kind === 'audio' ? 'muteAudio' : 'muteVideo'](muted);
120
- return tracks.forEach(track => {
121
- logger.debug({ trackInResult: track, intendToMute: muted }, `mute ${track.kind}`);
122
- signals.onStreamTrackEnabled?.emit(track);
275
+ const updateMediaProcess = createMediaUpdater({
276
+ signals,
277
+ getUserMedia: getUserMediaProcess,
278
+ getCurrentDevices,
279
+ shouldDiscardMedia: () => props.discardMedia,
280
+ onMediaTracksChanged: (media, tracks) => {
281
+ // Sync mute state to input tracks
282
+ syncMuteState(tracks);
283
+ tracks.forEach(track => {
284
+ if (track.track) {
285
+ const unsubscribe = createStreamTrackEventSubscriptions(track.track, {
286
+ ended: track => {
287
+ signals.onStreamTrackEnded?.emit(track);
288
+ unsubscribe();
289
+ },
290
+ mute: signals.onStreamTrackMuted?.emit,
291
+ unmute: signals.onStreamTrackUnmuted?.emit,
123
292
  });
124
293
  }
125
- logger.warn({ tracks, kind }, 'trying to mute but no track');
126
- };
127
- const muteAudio = muteTrack(media.stream?.getAudioTracks() ?? []);
128
- const muteVideo = muteTrack(media.stream?.getVideoTracks() ?? []);
129
- // Sync mute
130
- const { audio, video } = getMuteState();
131
- media.audioMuted !== undefined && muteAudio(audio);
132
- media.videoMuted !== undefined && muteVideo(video);
133
- const release = async () => {
134
- trackSubscriptions?.forEach(unsubscribe => unsubscribe());
135
- await media.release();
136
- await cleanup();
137
- };
138
- const applyConstraints = async (constraints) => {
139
- await media.applyConstraints(constraints);
140
- const videoFeatures = getVideoFeatures(constraints.video, {});
141
- const audioFeatures = getAudioFeatures(constraints.audio, {});
142
- media.stream
143
- ?.getAudioTracks()
144
- .forEach(applyContentHint(audioFeatures.contentHint));
145
- media.stream
146
- ?.getVideoTracks()
147
- .forEach(applyContentHint(videoFeatures.contentHint));
148
- };
149
- logger.debug({ finalAudioMute: audio, finalVideoMute: video }, 'End of media pipeline');
150
- return wrapToJSON(shallowCopy(media, {
151
- release,
152
- muteAudio: mute => {
153
- muteAudio(mute);
154
- signals.onAudioMuteStateChanged?.emit(mute);
155
- },
156
- muteVideo: mute => {
157
- muteVideo(mute);
158
- signals.onVideoMuteStateChanged?.emit(mute);
159
- },
160
- applyConstraints,
161
- }));
162
- }),
163
- ]);
294
+ });
295
+ props.media = media;
296
+ processAndUpdateMedia(media, tracks).catch((error) => {
297
+ if (error instanceof Error) {
298
+ logger.error(error, 'Failed to process media');
299
+ }
300
+ });
301
+ },
302
+ });
164
303
  /**
165
304
  * A function to update the media only when necessary, aka the current setup
166
305
  * for the stream is different from the new constraints
@@ -168,81 +307,12 @@ export const createMedia = ({ getMuteState, signals, mediaProcessors, getDefault
168
307
  * @param constraints - @see MediaDeviceRequest
169
308
  */
170
309
  const updateMedia = async (constraints) => {
171
- if (!constraints.audio && !constraints.video) {
172
- throw new Error(MediaDeviceFailure.MissingConstraintsError);
173
- }
174
- // Check if the audio device was requested before and is currently in use by another application
175
- const isTryingToRequestSameUsedAudioDevice = (isAudioDeviceInUse(props.media.status) ||
176
- isDevicesInUse(props.media.status)) &&
177
- props.media.expectedAudioInput?.deviceId &&
178
- props.media.expectedAudioInput?.deviceId ===
179
- extractConstrainDevice(constraints.audio)?.[0]?.[0]?.deviceId;
180
- // Check if the video device was requested before and is currently in use by another application
181
- const isTryingToRequestSameUsedVideoDevice = (isVideoDeviceInUse(props.media.status) ||
182
- isDevicesInUse(props.media.status)) &&
183
- props.media.expectedVideoInput?.deviceId &&
184
- props.media.expectedVideoInput?.deviceId ===
185
- extractConstrainDevice(constraints?.video)?.[0]?.[0]?.deviceId;
186
- const shouldRequestAudio = !isTryingToRequestSameUsedAudioDevice &&
187
- shouldRequestDevice(constraints.audio, props.media?.rawStream?.getAudioTracks() ?? [], props.devices.filter(device => device.kind === 'audioinput'));
188
- const shouldRequestVideo = !isTryingToRequestSameUsedVideoDevice &&
189
- shouldRequestDevice(constraints.video, props.media?.rawStream?.getVideoTracks() ?? [], props.devices.filter(device => device.kind === 'videoinput'));
190
- const { audio: [prevAudioSettings], video: [prevVideoSettings], } = props.media.getSettings();
191
- const videoFeatures = getVideoFeatures(constraints.video, {});
192
- const audioFeatures = getAudioFeatures(constraints.audio, {});
193
- const mixingFeatures = getMixingFeatures(constraints.audio, {});
194
- const hasRequestedResolution = ['blur', 'overlay'].includes(videoFeatures.videoSegmentation ?? '') || // Skip when there is a render effect to modify the video
195
- !props.devices.some(device => device.kind === 'videoinput' && device.label) || // Skip when no authorized video found
196
- isTryingToRequestSameUsedVideoDevice ||
197
- isRequestedResolution(constraints.video, props.media?.videoInput);
198
- const audioFeaturesChanged = hasSettingsChanged(AUDIO_SETTINGS_KEYS)(prevAudioSettings, audioFeatures);
199
- const videoFeaturesChanged = hasSettingsChanged(VIDEO_SETTINGS_KEYS)(prevVideoSettings, videoFeatures);
200
- const mixingFeaturesChanged = hasSettingsChanged(MIXING_SETTINGS_KEYS)(prevAudioSettings, mixingFeatures);
201
- const ptzFeaturesChanges = hasPtzFeature() &&
202
- hasSettingsChanged(['pan', 'tilt', 'zoom'])(prevVideoSettings, videoFeatures);
203
- logger.debug({
204
- constraints,
205
- stream: props.media.stream,
206
- currentDevices: props.devices,
207
- shouldRequestAudio,
208
- shouldRequestVideo,
209
- sameResolution: hasRequestedResolution,
210
- audioFeaturesChanged,
211
- videoFeaturesChanged,
212
- mixingFeaturesChanged,
213
- }, 'Is currently streaming requested stream');
214
- if (shouldRequestAudio ||
215
- shouldRequestVideo ||
216
- ptzFeaturesChanges ||
217
- !hasRequestedResolution ||
218
- !props.media.stream) {
219
- if (props.media) {
220
- await props.media.release();
221
- }
222
- if (props.discardMedia) {
223
- props.discardMedia = false;
224
- }
225
- const media = await mediaPipeline.execute(constraints);
226
- media.stream
227
- ?.getAudioTracks()
228
- .forEach(applyContentHint(audioFeatures.contentHint));
229
- media.stream
230
- ?.getVideoTracks()
231
- .forEach(applyContentHint(videoFeatures.contentHint));
232
- if (props.discardMedia) {
233
- logger.debug('Discard media');
234
- return await media.release();
235
- }
236
- props.media = media;
310
+ try {
311
+ props.updatingMedia = true;
312
+ await updateMediaProcess(constraints, props.media);
237
313
  }
238
- else if (audioFeaturesChanged ||
239
- videoFeaturesChanged ||
240
- mixingFeaturesChanged) {
241
- props.media.constraints = constraints;
242
- await props.media.applyConstraints({
243
- audio: audioFeatures,
244
- video: videoFeatures,
245
- });
314
+ finally {
315
+ props.updatingMedia = false;
246
316
  }
247
317
  };
248
318
  const mergeMediaConstraints = (constraints) => {
@@ -265,25 +335,33 @@ export const createMedia = ({ getMuteState, signals, mediaProcessors, getDefault
265
335
  };
266
336
  const tryAndGetUserMedia = constraints => {
267
337
  queue.enqueue(async () => {
268
- props.media.status = await getPermissionStatus();
269
- logger.debug({ status: props.media.status }, 'Current Permission State');
270
- const mergedConstraints = mergeMediaConstraints(constraints);
271
- props.media.constraints = mergedConstraints;
272
- props.devices = await getDevices();
273
- if (!mergedConstraints.audio && !mergedConstraints.video) {
274
- return;
338
+ const permission = await getInputDevicePermissionState();
339
+ const status = getPermissionStatus(permission);
340
+ if (isInitialPermissionsGranted(status)) {
341
+ await updateMedia(mergeMediaConstraints(constraints));
275
342
  }
276
- if (isInitialPermissionsGranted(props.media.status)) {
277
- return await updateMedia(mergedConstraints);
343
+ else {
344
+ const currentDevices = await getCurrentDevices();
345
+ props.media = buildMedia({
346
+ constraints,
347
+ permission,
348
+ devices: currentDevices,
349
+ status,
350
+ stream: undefined,
351
+ signals,
352
+ tracks: [],
353
+ });
278
354
  }
279
355
  });
280
356
  };
281
357
  // Media Control Event handlers
282
358
  const handleNoInputs = (_availableDevices) => {
283
- props.media.status = UserMediaStatus.NoDevicesFound;
359
+ if (props.media) {
360
+ props.media.status = UserMediaStatus.NoDevicesFound;
361
+ }
284
362
  };
285
363
  const handleDeviceChange = (devices) => {
286
- props.devices = devices;
364
+ props.devices = createIndexedDevices(devices);
287
365
  };
288
366
  // Subscribe Media Events for devices
289
367
  subscribe(event => {
@@ -305,6 +383,9 @@ export const createMedia = ({ getMuteState, signals, mediaProcessors, getDefault
305
383
  }
306
384
  });
307
385
  return {
386
+ get updatingMedia() {
387
+ return props.updatingMedia;
388
+ },
308
389
  get media() {
309
390
  return props.media;
310
391
  },
@@ -1,6 +1,6 @@
1
- import type { MediaDeviceRequest, MediaDeviceInfoLike } from '@pexip/media-control';
1
+ import type { IndexedDevices, MediaDeviceInfoLike, MediaDeviceRequest } from '@pexip/media-control';
2
2
  import type { AsyncQueueOptions } from '@pexip/utils';
3
- import type { Media, Unsubscribe, MediaSignals, MediaProcessor } from './types';
3
+ import type { Media, MediaSignals, TrackProcessor, Unsubscribe } from './types';
4
4
  type EventCallback<T> = (event: T) => void;
5
5
  type EventErrorCallback = (error: Error) => void;
6
6
  export type PreviewInput = MediaDeviceInfoLike | undefined;
@@ -17,17 +17,18 @@ export interface PreviewEventHandler {
17
17
  unsubscribeMain?: Unsubscribe;
18
18
  }
19
19
  export interface PreviewStreamParams {
20
- getCurrentDevices: () => MediaDeviceInfoLike[];
20
+ getCurrentDevices: () => IndexedDevices;
21
21
  getCurrentMedia: () => Media | undefined;
22
22
  updateMainStream: (request: MediaDeviceRequest) => Promise<void>;
23
23
  mediaSignal: MediaSignals['onMediaChanged'];
24
24
  onEnded?: () => void;
25
25
  fftSize?: number;
26
26
  queueOptions?: Partial<AsyncQueueOptions>;
27
- processors: MediaProcessor[];
27
+ audioProcessors: TrackProcessor[];
28
+ videoProcessors: TrackProcessor[];
28
29
  }
29
30
  export interface PreviewControllerProps {
30
- media: Media;
31
+ media: Media | undefined;
31
32
  audioInput?: MediaDeviceInfoLike;
32
33
  videoInput?: MediaDeviceInfoLike;
33
34
  updatingPreview: boolean;
@@ -37,7 +38,7 @@ export interface PreviewControllerProps {
37
38
  initialized: boolean;
38
39
  }
39
40
  export interface PreviewStreamController {
40
- media: Media;
41
+ media: Media | undefined;
41
42
  audioInputChanged: boolean;
42
43
  videoInputChanged: boolean;
43
44
  inputChanged: boolean;
@@ -45,8 +46,8 @@ export interface PreviewStreamController {
45
46
  videoInput: PreviewInput;
46
47
  updatingPreview: boolean;
47
48
  updatingMain: boolean;
48
- updateAudioInput(input: PreviewInput): void;
49
- updateVideoInput(input: PreviewInput): void;
49
+ updateAudioInput(id: string): void;
50
+ updateVideoInput(id: string): void;
50
51
  applyChanges(force?: boolean): Promise<void>;
51
52
  revertChanges(): Promise<void>;
52
53
  cleanup: () => Promise<void>;
@@ -60,6 +61,6 @@ export interface PreviewStreamController {
60
61
  onApplyChangesError(callback: EventErrorCallback): Unsubscribe;
61
62
  onRevertChangesError(callback: EventErrorCallback): Unsubscribe;
62
63
  }
63
- export declare const createPreviewStreamController: ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded, mediaSignal, queueOptions, processors, }: PreviewStreamParams) => PreviewStreamController;
64
+ export declare const createPreviewStreamController: ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded, mediaSignal, queueOptions, audioProcessors, videoProcessors, }: PreviewStreamParams) => PreviewStreamController;
64
65
  export type CreatePreviewStreamController = typeof createPreviewStreamController;
65
66
  export {};