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