@pexip/media 16.7.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/dist/index.mjs ADDED
@@ -0,0 +1,2698 @@
1
+ // src/media.ts
2
+ import {
3
+ subscribe,
4
+ MediaEventType,
5
+ MediaDeviceFailure as MediaDeviceFailure2,
6
+ createStreamTrackEventSubscriptions,
7
+ getDevices as getDevices2,
8
+ isRequestedResolution as isRequestedResolution2,
9
+ mergeConstraints,
10
+ shouldRequestDevice
11
+ } from "@pexip/media-control";
12
+ import { createAsyncQueue, isEmpty as isEmpty5 } from "@pexip/utils";
13
+
14
+ // src/status.ts
15
+ import { getInputDevicePermissionState } from "@pexip/media-control";
16
+
17
+ // src/types.ts
18
+ var UserMediaStatus = /* @__PURE__ */ ((UserMediaStatus2) => {
19
+ UserMediaStatus2["Initial"] = "initial";
20
+ UserMediaStatus2["InitialPermissionsGranted"] = "initial-permissions-granted";
21
+ UserMediaStatus2["InitialPermissionsNotGranted"] = "initial-permissions-not-granted";
22
+ UserMediaStatus2["InitialPermissionsVideoInputDenied"] = "initial-permissions-videoinput-denied";
23
+ UserMediaStatus2["InitialPermissionsAudioInputDenied"] = "initial-permissions-audioinput-denied";
24
+ UserMediaStatus2["InitialPermissionsVideoInputGranted"] = "initial-permissions-videoinput-granted";
25
+ UserMediaStatus2["InitialPermissionsAudioInputGranted"] = "initial-permissions-audioinput-granted";
26
+ UserMediaStatus2["InitialPermissionsGrantedVideoInputDenied"] = "initial-permissions-audioinput-granted-videoinput-denied";
27
+ UserMediaStatus2["InitialPermissionsGrantedAudioInputDenied"] = "initial-permissions-videoinput-granted-audioinput-denied";
28
+ UserMediaStatus2["NoDevicesFound"] = "no-devices-found";
29
+ UserMediaStatus2["NoVideoDevicesFound"] = "no-video-devices-found";
30
+ UserMediaStatus2["NoAudioDevicesFound"] = "no-audio-devices-found";
31
+ UserMediaStatus2["AudioDeviceNotFound"] = "audio-device-not-found";
32
+ UserMediaStatus2["VideoDeviceNotFound"] = "video-device-not-found";
33
+ UserMediaStatus2["AudioVideoDevicesNotFound"] = "audio-video-devices-not-found";
34
+ UserMediaStatus2["PermissionsGranted"] = "permissions-granted";
35
+ UserMediaStatus2["PermissionsGrantedFallback"] = "permissions-granted-fallback-devices";
36
+ UserMediaStatus2["PermissionsGrantedFallbackAudioinput"] = "permissions-granted-fallback-audioinput";
37
+ UserMediaStatus2["PermissionsGrantedFallbackVideoinput"] = "permissions-granted-fallback-videoinput";
38
+ UserMediaStatus2["PermissionsRejected"] = "permissions-rejected";
39
+ UserMediaStatus2["PermissionsRejectedAudioInput"] = "permissions-rejected-audioinput";
40
+ UserMediaStatus2["PermissionsRejectedVideoInput"] = "permissions-rejected-videoinput";
41
+ UserMediaStatus2["PermissionsOnlyAudioinput"] = "permissions-only-audioinput";
42
+ UserMediaStatus2["PermissionsOnlyAudioinputNoVideoDevices"] = "permissions-only-audioinput-no-video-devices";
43
+ UserMediaStatus2["PermissionsOnlyAudioinputFallback"] = "permissions-only-fallback-audioinput";
44
+ UserMediaStatus2["PermissionsOnlyAudioinputFallbackNoVideoDevices"] = "permissions-only-fallback-audioinput-no-video-devices";
45
+ UserMediaStatus2["PermissionsOnlyVideoinput"] = "permissions-only-videoinput";
46
+ UserMediaStatus2["PermissionsOnlyVideoinputNoAudioDevices"] = "permissions-only-videoinput-no-audio-devices";
47
+ UserMediaStatus2["PermissionsOnlyVideoinputFallback"] = "permissions-only-fallback-videoinput";
48
+ UserMediaStatus2["PermissionsOnlyVideoinputFallbackNoAudioDevices"] = "permissions-only-fallback-videoinput-no-audio-devices";
49
+ UserMediaStatus2["AudioDeviceInUse"] = "audio-device-in-use";
50
+ UserMediaStatus2["VideoDeviceInUse"] = "video-device-in-use";
51
+ UserMediaStatus2["DevicesInUse"] = "devices-in-use";
52
+ UserMediaStatus2["Overconstrained"] = "overconstrained";
53
+ UserMediaStatus2["VideoOverconstrained"] = "video-overconstrained";
54
+ UserMediaStatus2["AudioOverconstrained"] = "audio-overconstrained";
55
+ UserMediaStatus2["InvalidConstraints"] = "invalid-constraints";
56
+ UserMediaStatus2["InvalidVideoConstraints"] = "invalid-video-constraints";
57
+ UserMediaStatus2["InvalidAudioConstraints"] = "invalid-audio-constraints";
58
+ UserMediaStatus2["NotSupportedError"] = "not-supported-error";
59
+ UserMediaStatus2["NotSupportedErrorOnlyVideoInput"] = "not-supported-error-only-video";
60
+ UserMediaStatus2["NotSupportedErrorOnlyAudioInput"] = "not-supported-error-only-audio";
61
+ UserMediaStatus2["UnknownError"] = "unknown-error";
62
+ UserMediaStatus2["UnknownErrorOnlyAudioinput"] = "unknown-error-only-audioinput";
63
+ UserMediaStatus2["UnknownErrorOnlyVideoinput"] = "unknown-error-only-videoinput";
64
+ return UserMediaStatus2;
65
+ })(UserMediaStatus || {});
66
+ var DeniedDevices = /* @__PURE__ */ ((DeniedDevices2) => {
67
+ DeniedDevices2["Microphone"] = "microphone";
68
+ DeniedDevices2["Camera"] = "camera";
69
+ DeniedDevices2["Both"] = "microphone-and-camera";
70
+ return DeniedDevices2;
71
+ })(DeniedDevices || {});
72
+
73
+ // src/status.ts
74
+ var isFallbackVideo = (status) => [
75
+ "permissions-only-fallback-videoinput" /* PermissionsOnlyVideoinputFallback */,
76
+ "permissions-only-fallback-videoinput-no-audio-devices" /* PermissionsOnlyVideoinputFallbackNoAudioDevices */,
77
+ "permissions-granted-fallback-devices" /* PermissionsGrantedFallback */,
78
+ "permissions-granted-fallback-videoinput" /* PermissionsGrantedFallbackVideoinput */
79
+ ].includes(status);
80
+ var isFallbackAudio = (status) => [
81
+ "permissions-only-fallback-audioinput" /* PermissionsOnlyAudioinputFallback */,
82
+ "permissions-only-fallback-audioinput-no-video-devices" /* PermissionsOnlyAudioinputFallbackNoVideoDevices */,
83
+ "permissions-granted-fallback-devices" /* PermissionsGrantedFallback */,
84
+ "permissions-granted-fallback-audioinput" /* PermissionsGrantedFallbackAudioinput */
85
+ ].includes(status);
86
+ var isFallback = (status) => status === "permissions-granted-fallback-devices" /* PermissionsGrantedFallback */ || isFallbackAudio(status) || isFallbackVideo(status);
87
+ var hasNoDevice = (status) => "no-devices-found" /* NoDevicesFound */ === status;
88
+ var hasNoAudioDevices = (status) => hasNoDevice(status) || status === "no-audio-devices-found" /* NoAudioDevicesFound */ || isGrantedOnlyVideoNoAudioDevices(status);
89
+ var hasNoVideoDevices = (status) => hasNoDevice(status) || status === "no-video-devices-found" /* NoVideoDevicesFound */ || isGrantedOnlyAudioNoVideoDevices(status);
90
+ var isGrantedOnlyVideoNoAudioDevices = (status) => [
91
+ "permissions-only-videoinput-no-audio-devices" /* PermissionsOnlyVideoinputNoAudioDevices */,
92
+ "permissions-only-fallback-videoinput-no-audio-devices" /* PermissionsOnlyVideoinputFallbackNoAudioDevices */
93
+ ].includes(status);
94
+ var isPromptAudio = (status) => [
95
+ "permissions-only-videoinput" /* PermissionsOnlyVideoinput */,
96
+ "permissions-only-fallback-videoinput" /* PermissionsOnlyVideoinputFallback */
97
+ ].includes(status);
98
+ var isGrantedOnlyVideo = (status) => isGrantedOnlyVideoNoAudioDevices(status) || isPromptAudio(status) || isRejectedOnlyAudio(status) || isOnlyAudioError(status);
99
+ var isGrantedOnlyAudioNoVideoDevices = (status) => [
100
+ "permissions-only-audioinput-no-video-devices" /* PermissionsOnlyAudioinputNoVideoDevices */,
101
+ "permissions-only-fallback-audioinput-no-video-devices" /* PermissionsOnlyAudioinputFallbackNoVideoDevices */
102
+ ].includes(status);
103
+ var isPromptVideo = (status) => [
104
+ "permissions-only-audioinput" /* PermissionsOnlyAudioinput */,
105
+ "permissions-only-fallback-audioinput" /* PermissionsOnlyAudioinputFallback */
106
+ ].includes(status);
107
+ var isGrantedOnlyAudio = (status) => isGrantedOnlyAudioNoVideoDevices(status) || isPromptVideo(status) || isRejectedOnlyVideo(status) || isOnlyVideoError(status);
108
+ var areBothGranted = (status) => [
109
+ "permissions-granted" /* PermissionsGranted */,
110
+ "permissions-granted-fallback-devices" /* PermissionsGrantedFallback */,
111
+ "permissions-granted-fallback-audioinput" /* PermissionsGrantedFallbackAudioinput */,
112
+ "permissions-granted-fallback-videoinput" /* PermissionsGrantedFallbackVideoinput */
113
+ ].includes(status);
114
+ var isGrantedVideo = (status) => areBothGranted(status) || isGrantedOnlyVideoNoAudioDevices(status) || isGrantedOnlyVideo(status);
115
+ var isGrantedAudio = (status) => areBothGranted(status) || isGrantedOnlyAudio(status) || isGrantedOnlyAudioNoVideoDevices(status);
116
+ var isGranted = (status) => isGrantedVideo(status) || isGrantedAudio(status);
117
+ var isOnlyAudioError = (status) => [
118
+ "permissions-rejected-audioinput" /* PermissionsRejectedAudioInput */,
119
+ "audio-device-in-use" /* AudioDeviceInUse */,
120
+ "audio-overconstrained" /* AudioOverconstrained */,
121
+ "invalid-audio-constraints" /* InvalidAudioConstraints */,
122
+ "not-supported-error-only-audio" /* NotSupportedErrorOnlyAudioInput */
123
+ ].includes(status);
124
+ var isOnlyVideoError = (status) => [
125
+ "permissions-rejected-videoinput" /* PermissionsRejectedVideoInput */,
126
+ "video-device-in-use" /* VideoDeviceInUse */,
127
+ "video-overconstrained" /* VideoOverconstrained */,
128
+ "invalid-video-constraints" /* InvalidVideoConstraints */,
129
+ "not-supported-error-only-video" /* NotSupportedErrorOnlyVideoInput */
130
+ ].includes(status);
131
+ var isRejected = (status) => "permissions-rejected" /* PermissionsRejected */ === status;
132
+ var isRejectedOnlyAudio = (status) => [
133
+ "permissions-rejected-audioinput" /* PermissionsRejectedAudioInput */,
134
+ "initial-permissions-videoinput-granted-audioinput-denied" /* InitialPermissionsGrantedAudioInputDenied */,
135
+ "initial-permissions-audioinput-denied" /* InitialPermissionsAudioInputDenied */
136
+ ].includes(status);
137
+ var isRejectedOnlyVideo = (status) => [
138
+ "permissions-rejected-videoinput" /* PermissionsRejectedVideoInput */,
139
+ "initial-permissions-audioinput-granted-videoinput-denied" /* InitialPermissionsGrantedVideoInputDenied */,
140
+ "initial-permissions-videoinput-denied" /* InitialPermissionsVideoInputDenied */
141
+ ].includes(status);
142
+ var isOverConstrained = (status) => [
143
+ "overconstrained" /* Overconstrained */,
144
+ "video-overconstrained" /* VideoOverconstrained */,
145
+ "audio-overconstrained" /* AudioOverconstrained */
146
+ ].includes(status);
147
+ var isUnknownError = (status) => [
148
+ "unknown-error" /* UnknownError */,
149
+ "unknown-error-only-videoinput" /* UnknownErrorOnlyVideoinput */,
150
+ "unknown-error-only-audioinput" /* UnknownErrorOnlyAudioinput */
151
+ ].includes(status);
152
+ var isInitial = (status) => status === "initial" /* Initial */;
153
+ var isInitialPermissions = (status) => [
154
+ "initial-permissions-audioinput-denied" /* InitialPermissionsAudioInputDenied */,
155
+ "initial-permissions-audioinput-granted" /* InitialPermissionsAudioInputGranted */,
156
+ "initial-permissions-granted" /* InitialPermissionsGranted */,
157
+ "initial-permissions-videoinput-granted-audioinput-denied" /* InitialPermissionsGrantedAudioInputDenied */,
158
+ "initial-permissions-audioinput-granted-videoinput-denied" /* InitialPermissionsGrantedVideoInputDenied */,
159
+ "initial-permissions-not-granted" /* InitialPermissionsNotGranted */,
160
+ "initial-permissions-videoinput-denied" /* InitialPermissionsVideoInputDenied */,
161
+ "initial-permissions-videoinput-granted" /* InitialPermissionsVideoInputGranted */
162
+ ].includes(status);
163
+ var isInitialPermissionsNotGranted = (status) => [
164
+ "initial-permissions-audioinput-denied" /* InitialPermissionsAudioInputDenied */,
165
+ "initial-permissions-audioinput-granted" /* InitialPermissionsAudioInputGranted */,
166
+ "initial-permissions-not-granted" /* InitialPermissionsNotGranted */,
167
+ "initial-permissions-videoinput-denied" /* InitialPermissionsVideoInputDenied */,
168
+ "initial-permissions-videoinput-granted" /* InitialPermissionsVideoInputGranted */
169
+ ].includes(status);
170
+ var isInitialPermissionsGranted = (status) => [
171
+ "initial-permissions-granted" /* InitialPermissionsGranted */,
172
+ "initial-permissions-videoinput-granted-audioinput-denied" /* InitialPermissionsGrantedAudioInputDenied */,
173
+ "initial-permissions-audioinput-granted-videoinput-denied" /* InitialPermissionsGrantedVideoInputDenied */
174
+ ].includes(status);
175
+ var isAudioDeviceInUse = (status) => status === "audio-device-in-use" /* AudioDeviceInUse */;
176
+ var isDeviceInUse = (status) => [
177
+ "devices-in-use" /* DevicesInUse */,
178
+ "audio-device-in-use" /* AudioDeviceInUse */,
179
+ "video-device-in-use" /* VideoDeviceInUse */
180
+ ].includes(status);
181
+ var isVideoDeviceInUse = (status) => status === "video-device-in-use" /* VideoDeviceInUse */;
182
+ var toDeniedDevices = (status) => {
183
+ if (!status) {
184
+ return void 0;
185
+ }
186
+ if (isRejectedOnlyVideo(status)) {
187
+ return "camera" /* Camera */;
188
+ }
189
+ if (isRejectedOnlyAudio(status)) {
190
+ return "microphone" /* Microphone */;
191
+ }
192
+ if (isRejected(status)) {
193
+ return "microphone-and-camera" /* Both */;
194
+ }
195
+ };
196
+ var getPermissionStatus = async (getPermissionState = getInputDevicePermissionState) => {
197
+ const { audio, video } = await getPermissionState();
198
+ if (audio === "denied") {
199
+ if (video === "denied") {
200
+ return "permissions-rejected" /* PermissionsRejected */;
201
+ }
202
+ if (video === "granted") {
203
+ return "initial-permissions-videoinput-granted-audioinput-denied" /* InitialPermissionsGrantedAudioInputDenied */;
204
+ }
205
+ if (video === "prompt") {
206
+ return "initial-permissions-audioinput-denied" /* InitialPermissionsAudioInputDenied */;
207
+ }
208
+ }
209
+ if (audio === "granted") {
210
+ if (video === "denied") {
211
+ return "initial-permissions-audioinput-granted-videoinput-denied" /* InitialPermissionsGrantedVideoInputDenied */;
212
+ }
213
+ if (video === "granted") {
214
+ return "initial-permissions-granted" /* InitialPermissionsGranted */;
215
+ }
216
+ if (video === "prompt") {
217
+ return "initial-permissions-audioinput-granted" /* InitialPermissionsAudioInputGranted */;
218
+ }
219
+ }
220
+ if (audio === "prompt") {
221
+ if (video === "denied") {
222
+ return "initial-permissions-videoinput-denied" /* InitialPermissionsVideoInputDenied */;
223
+ }
224
+ if (video === "granted") {
225
+ return "initial-permissions-videoinput-granted" /* InitialPermissionsVideoInputGranted */;
226
+ }
227
+ if (video === "prompt") {
228
+ return "initial-permissions-not-granted" /* InitialPermissionsNotGranted */;
229
+ }
230
+ }
231
+ return "initial" /* Initial */;
232
+ };
233
+ var deriveInitialPermissionStatus = async (prevStatus, getPermissionState = getInputDevicePermissionState) => {
234
+ if (isInitialPermissions(prevStatus) || isRejected(prevStatus)) {
235
+ return prevStatus;
236
+ }
237
+ if (areBothGranted(prevStatus)) {
238
+ return "initial-permissions-granted" /* InitialPermissionsGranted */;
239
+ }
240
+ if (isGrantedAudio(prevStatus)) {
241
+ return isRejectedOnlyVideo(prevStatus) ? "initial-permissions-audioinput-granted-videoinput-denied" /* InitialPermissionsGrantedVideoInputDenied */ : "initial-permissions-audioinput-granted" /* InitialPermissionsAudioInputGranted */;
242
+ }
243
+ if (isGrantedVideo(prevStatus)) {
244
+ return isRejectedOnlyAudio(prevStatus) ? "initial-permissions-videoinput-granted-audioinput-denied" /* InitialPermissionsGrantedAudioInputDenied */ : "initial-permissions-videoinput-granted" /* InitialPermissionsVideoInputGranted */;
245
+ }
246
+ if (prevStatus === "devices-in-use" /* DevicesInUse */) {
247
+ return "initial-permissions-granted" /* InitialPermissionsGranted */;
248
+ }
249
+ return await getPermissionStatus(getPermissionState);
250
+ };
251
+
252
+ // ../../shared/baseLogger.ts
253
+ var LogLevels = ((LogLevels2) => {
254
+ LogLevels2[LogLevels2["trace"] = 10] = "trace";
255
+ LogLevels2[LogLevels2["debug"] = 20] = "debug";
256
+ LogLevels2[LogLevels2["info"] = 30] = "info";
257
+ LogLevels2[LogLevels2["warn"] = 40] = "warn";
258
+ LogLevels2[LogLevels2["error"] = 50] = "error";
259
+ LogLevels2[LogLevels2["fatal"] = 60] = "fatal";
260
+ LogLevels2[LogLevels2["silent"] = Number.MAX_SAFE_INTEGER] = "silent";
261
+ return LogLevels2;
262
+ })(LogLevels || {});
263
+ function createConsoleLogger() {
264
+ return Object.freeze({
265
+ /* eslint-disable no-console -- set logger to console */
266
+ fatal: (meta, message) => console.error(message, meta),
267
+ error: (meta, message) => console.error(message, meta),
268
+ warn: (meta, message) => console.warn(message, meta),
269
+ info: (meta, message) => console.info(message, meta),
270
+ debug: (meta, message) => console.debug(message, meta),
271
+ trace() {
272
+ },
273
+ // Noop
274
+ silent() {
275
+ },
276
+ // Noop
277
+ redact() {
278
+ }
279
+ // Noop
280
+ /* eslint-enable no-console -- set logger to console */
281
+ });
282
+ }
283
+
284
+ // src/logger.ts
285
+ var logger = createConsoleLogger();
286
+ function setLogger(newLogger) {
287
+ logger = newLogger;
288
+ }
289
+ var createModuleLogger = (metaBase) => {
290
+ return Object.keys(logger).reduce((log, key) => {
291
+ const k = key;
292
+ log[k] = (meta, msg) => {
293
+ if (typeof meta === "string") {
294
+ return logger[k](metaBase, meta);
295
+ }
296
+ if (typeof meta === "object") {
297
+ return logger[k]({ ...meta, meta: metaBase }, msg);
298
+ }
299
+ return logger[k]({ meta: metaBase, context: meta }, msg);
300
+ };
301
+ return log;
302
+ }, {});
303
+ };
304
+ var createLogProxyHandler = (logger2, name, scope) => {
305
+ return {
306
+ get: (target, p, receiver) => {
307
+ const value = Reflect.get(target, p, receiver);
308
+ logger2.debug(
309
+ { scope, name, prop: p, value },
310
+ `called get ${name}[${String(p)}]`
311
+ );
312
+ return value;
313
+ },
314
+ // eslint-disable-next-line max-params --- from lib.es2015 and log
315
+ set: (target, p, value, receiver) => {
316
+ logger2.debug(
317
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- it's a log call...
318
+ { scope, name, prop: p, value },
319
+ `called set ${name}[${String(p)}]`
320
+ );
321
+ return Reflect.set(target, p, value, receiver);
322
+ }
323
+ };
324
+ };
325
+ var proxyWithLog = (logger2, scope) => (obj, name) => {
326
+ return new Proxy(obj, createLogProxyHandler(logger2, name, scope));
327
+ };
328
+
329
+ // src/userMedia.ts
330
+ import {
331
+ MediaDeviceFailure,
332
+ applyConstraints as applyConstraints2,
333
+ findMediaInputFromMediaStreamTrack,
334
+ getDevices,
335
+ getUserMedia,
336
+ isExactDeviceConstraint,
337
+ isRequestedResolution,
338
+ isStreamingRequestedDevices,
339
+ muteStreamTrack as muteStreamTrack2,
340
+ stopMediaStream as stopMediaStream2,
341
+ isAudioInput as isAudioInput2,
342
+ isVideoInput as isVideoInput2,
343
+ hasAnyInputs,
344
+ hasAnyGrantedInput
345
+ } from "@pexip/media-control";
346
+ import { isEmpty } from "@pexip/utils";
347
+
348
+ // src/utils.ts
349
+ import {
350
+ applyConstraints,
351
+ createTrackDevicesChanges,
352
+ extractConstraintsWithKeys,
353
+ findDeviceFromConstraints,
354
+ muteStreamTrack,
355
+ relaxInputConstraint,
356
+ stopMediaStream,
357
+ isAudioInput,
358
+ isVideoInput
359
+ } from "@pexip/media-control";
360
+
361
+ // src/typeGuard.ts
362
+ import { isAudioNodeInit } from "@pexip/media-processor";
363
+ var isNonNullObject = (value) => {
364
+ if (typeof value === "object" && value !== null) {
365
+ return true;
366
+ }
367
+ return false;
368
+ };
369
+ var isMedia = (value) => {
370
+ if (isNonNullObject(value) && "release" in value) {
371
+ return true;
372
+ }
373
+ return false;
374
+ };
375
+
376
+ // src/utils.ts
377
+ var makeDeriveDeviceStatus = (constraints) => (audio, video, both) => {
378
+ if (!constraints.audio && constraints.video) {
379
+ return video;
380
+ }
381
+ if (!constraints.video && constraints.audio) {
382
+ return audio;
383
+ }
384
+ return both;
385
+ };
386
+ var createMediaProcess = (process) => async (mediaP) => {
387
+ const media = await mediaP;
388
+ return process(media) ?? media;
389
+ };
390
+ var createMediaPipeline = (init) => {
391
+ const getPipeline = () => typeof init === "function" ? init() : init;
392
+ return {
393
+ pipe: (process) => {
394
+ getPipeline().push(process);
395
+ },
396
+ execute: async (m) => {
397
+ const [first, ...processes] = getPipeline();
398
+ if (first) {
399
+ const piped = processes.reduce(
400
+ (prev, next) => next(prev),
401
+ first(m)
402
+ );
403
+ return piped;
404
+ }
405
+ const media = m instanceof Promise ? await m : m;
406
+ if (isMedia(media)) {
407
+ return Promise.resolve(media);
408
+ }
409
+ throw new Error("Expect a media input or a processor");
410
+ }
411
+ };
412
+ };
413
+ var interpretInput = (input, getCurrentInput) => {
414
+ if (input === true || input === void 0) {
415
+ return getCurrentInput();
416
+ }
417
+ if (input === false) {
418
+ return void 0;
419
+ }
420
+ return input;
421
+ };
422
+ var createMemorizedGetExpectedInput = () => {
423
+ const props = {
424
+ cachedExpectedInputs: /* @__PURE__ */ new Map()
425
+ };
426
+ return (constraints, getInfo) => {
427
+ if (props.cachedExpectedInputs.has(constraints)) {
428
+ return props.cachedExpectedInputs.get(constraints);
429
+ }
430
+ const { devices, input } = getInfo();
431
+ const relaxedConstraints = relaxInputConstraint(constraints, devices);
432
+ const {
433
+ device: [[device] = []]
434
+ } = extractConstraintsWithKeys(["device"])(relaxedConstraints);
435
+ const found = device ?? findDeviceFromConstraints(constraints, devices);
436
+ const expectedInput = interpretInput(found, () => input);
437
+ props.cachedExpectedInputs.clear();
438
+ props.cachedExpectedInputs.set(constraints, expectedInput);
439
+ return expectedInput;
440
+ };
441
+ };
442
+ var isMuted = (tracks) => {
443
+ if (!tracks?.length) {
444
+ return void 0;
445
+ }
446
+ return !tracks.some((track) => !track.muted && track.enabled);
447
+ };
448
+ var buildMedia = (getMedia, onSetStatus) => {
449
+ const props = {
450
+ status: getMedia().status ?? "initial" /* Initial */,
451
+ devices: getMedia().devices ?? [],
452
+ constraints: getMedia().constraints
453
+ };
454
+ const getExpectedAudioInput = createMemorizedGetExpectedInput();
455
+ const getExpectedVideoInput = createMemorizedGetExpectedInput();
456
+ const muteTrack = (kind) => (muted) => {
457
+ const { muteAudio, muteVideo, stream } = getMedia();
458
+ const mute = kind === "audio" ? muteAudio : muteVideo;
459
+ if (mute) {
460
+ return mute(muted);
461
+ }
462
+ return muteStreamTrack(stream)(muted, kind);
463
+ };
464
+ const release = () => {
465
+ const { release: release2, stream } = getMedia();
466
+ if (release2) {
467
+ return release2();
468
+ }
469
+ return new Promise((resolve) => {
470
+ stopMediaStream(stream);
471
+ resolve();
472
+ });
473
+ };
474
+ return {
475
+ get constraints() {
476
+ return props.constraints;
477
+ },
478
+ get devices() {
479
+ return props.devices;
480
+ },
481
+ set devices(newDevices) {
482
+ props.devices = newDevices;
483
+ },
484
+ get stream() {
485
+ return getMedia().stream;
486
+ },
487
+ get expectedAudioInput() {
488
+ const media = getMedia();
489
+ return getExpectedAudioInput(props.constraints?.audio, () => ({
490
+ devices: media.devices?.filter(isAudioInput) ?? [],
491
+ input: media.audioInput
492
+ }));
493
+ },
494
+ get expectedVideoInput() {
495
+ const media = getMedia();
496
+ return getExpectedVideoInput(props.constraints?.video, () => ({
497
+ devices: media.devices?.filter(isVideoInput) ?? [],
498
+ input: media.videoInput
499
+ }));
500
+ },
501
+ get rawStream() {
502
+ const { rawStream, stream } = getMedia();
503
+ return rawStream ?? stream;
504
+ },
505
+ get audioInput() {
506
+ return getMedia().audioInput;
507
+ },
508
+ get videoInput() {
509
+ return getMedia().videoInput;
510
+ },
511
+ get status() {
512
+ return props.status;
513
+ },
514
+ set status(status) {
515
+ props.status = status;
516
+ onSetStatus?.(status);
517
+ },
518
+ set constraints(value) {
519
+ props.constraints = value;
520
+ },
521
+ get audioMuted() {
522
+ return isMuted(getMedia().stream?.getAudioTracks());
523
+ },
524
+ get videoMuted() {
525
+ return isMuted(getMedia().stream?.getVideoTracks());
526
+ },
527
+ muteAudio: muteTrack("audio"),
528
+ muteVideo: muteTrack("video"),
529
+ applyConstraints: async (constraints) => {
530
+ const { stream, applyConstraints: prevApplyConstraints } = getMedia();
531
+ if (prevApplyConstraints) {
532
+ return await prevApplyConstraints(constraints);
533
+ }
534
+ return await applyConstraints(stream?.getTracks(), constraints);
535
+ },
536
+ release,
537
+ getSettings: () => {
538
+ const { getSettings, stream } = getMedia();
539
+ if (!stream) {
540
+ return {
541
+ audio: [],
542
+ video: []
543
+ };
544
+ }
545
+ if (getSettings) {
546
+ return getSettings();
547
+ }
548
+ return {
549
+ audio: stream.getAudioTracks().map((track) => track.getSettings()),
550
+ video: stream.getVideoTracks().map((track) => track.getSettings())
551
+ };
552
+ },
553
+ toJSON: () => toJSON(getMedia())
554
+ };
555
+ };
556
+ var cloneMedia = async (media) => {
557
+ const stream = (media.rawStream ?? media.stream)?.clone();
558
+ stream?.getTracks().forEach((track) => track.enabled = true);
559
+ const { audio, video } = media.getSettings();
560
+ const clonedMedia = buildMedia(() => ({
561
+ stream,
562
+ constraints: media?.constraints,
563
+ devices: media?.devices,
564
+ status: media?.status,
565
+ rawStream: stream,
566
+ audioInput: media?.audioInput,
567
+ videoInput: media?.videoInput,
568
+ getSettings: () => ({ audio, video })
569
+ }));
570
+ return Promise.resolve(clonedMedia);
571
+ };
572
+ var shallowCopy = (original, overriding) => {
573
+ const copy = Object.create(
574
+ Object.getPrototypeOf(original),
575
+ Object.getOwnPropertyDescriptors(original)
576
+ );
577
+ return Object.defineProperties(
578
+ copy,
579
+ Object.getOwnPropertyDescriptors(overriding)
580
+ );
581
+ };
582
+ var getDevicesChanges = (prev, next) => {
583
+ const trackChanges = createTrackDevicesChanges(prev);
584
+ return trackChanges(next);
585
+ };
586
+ var applyExtendedConstraints = (media, applyExtended) => (
587
+ /**
588
+ * Apply constraints
589
+ * @param constraints - The constraints to be applied to the media
590
+ */
591
+ async (constraints) => {
592
+ await media.applyConstraints(constraints);
593
+ if (!isOverConstrained(media.status)) {
594
+ await applyExtended(constraints);
595
+ }
596
+ }
597
+ );
598
+ var AUDIO_SETTINGS_KEYS = [
599
+ "denoise",
600
+ "vad",
601
+ "asd"
602
+ ];
603
+ var VIDEO_SETTINGS_KEYS = [
604
+ "frameRate",
605
+ "videoSegmentation",
606
+ "videoSegmentationModel",
607
+ "foregroundThreshold",
608
+ "backgroundBlurAmount",
609
+ "edgeBlurAmount",
610
+ "flipHorizontal",
611
+ "bgImageUrl",
612
+ "width",
613
+ "height"
614
+ ];
615
+ var MIXING_SETTINGS_KEYS = [
616
+ "mixWithAdditionalMedia"
617
+ ];
618
+ var hasSettingsChanged = (keysToLookFor) => {
619
+ const cache = {};
620
+ return (settingsA, settingsB) => {
621
+ if (cache.result !== void 0 && cache.settingsA === settingsA && cache.settingsB === settingsB) {
622
+ return cache.result;
623
+ }
624
+ cache.settingsA = settingsA;
625
+ cache.settingsB = settingsB;
626
+ for (const key of keysToLookFor) {
627
+ if (settingsA === settingsB) {
628
+ cache.result = false;
629
+ return cache.result;
630
+ }
631
+ if (settingsA === void 0 || settingsB === void 0) {
632
+ cache.result = true;
633
+ return cache.result;
634
+ }
635
+ if (settingsA[key] !== settingsB[key]) {
636
+ cache.result = true;
637
+ return cache.result;
638
+ }
639
+ }
640
+ cache.result = false;
641
+ return cache.result;
642
+ };
643
+ };
644
+ var toJSON = (media) => {
645
+ return {
646
+ constraints: media.constraints,
647
+ devices: media.devices,
648
+ stream: media.stream,
649
+ rawStream: media.rawStream,
650
+ audioInput: media.audioInput,
651
+ videoInput: media.videoInput,
652
+ expectedAudioInput: media.expectedAudioInput,
653
+ expectedVideoInput: media.expectedVideoInput,
654
+ status: media.status,
655
+ audioMuted: media.audioMuted,
656
+ videoMuted: media.videoMuted
657
+ };
658
+ };
659
+ var wrapToJSON = (media) => {
660
+ media.toJSON = () => toJSON(media);
661
+ return media;
662
+ };
663
+ var getBlurKernelSize = (percentage, height, max = 20) => {
664
+ if (height <= 0 || percentage <= 0 || max <= 0) {
665
+ return 0;
666
+ }
667
+ return Math.min(Math.ceil(percentage * 1e-3 * height), max);
668
+ };
669
+
670
+ // src/userMedia.ts
671
+ var toErrorDeviceStatus = (error, deriveDeviceStatus) => {
672
+ switch (error.message) {
673
+ case MediaDeviceFailure.AudioInputDeviceNotFoundError:
674
+ return "audio-device-not-found" /* AudioDeviceNotFound */;
675
+ case MediaDeviceFailure.VideoInputDeviceNotFoundError:
676
+ return "video-device-not-found" /* VideoDeviceNotFound */;
677
+ case MediaDeviceFailure.AudioAndVideoDeviceNotFoundError:
678
+ return "audio-video-devices-not-found" /* AudioVideoDevicesNotFound */;
679
+ case MediaDeviceFailure.NotAllowedError:
680
+ case MediaDeviceFailure.NotFoundError:
681
+ case MediaDeviceFailure.SecurityError:
682
+ case MediaDeviceFailure.PermissionDeniedError:
683
+ return deriveDeviceStatus(
684
+ "permissions-rejected-audioinput" /* PermissionsRejectedAudioInput */,
685
+ "permissions-rejected-videoinput" /* PermissionsRejectedVideoInput */,
686
+ "permissions-rejected" /* PermissionsRejected */
687
+ );
688
+ case MediaDeviceFailure.NotReadableError:
689
+ case MediaDeviceFailure.TrackStartError:
690
+ case MediaDeviceFailure.AbortError:
691
+ return deriveDeviceStatus(
692
+ "audio-device-in-use" /* AudioDeviceInUse */,
693
+ "video-device-in-use" /* VideoDeviceInUse */,
694
+ "devices-in-use" /* DevicesInUse */
695
+ );
696
+ case MediaDeviceFailure.MissingConstraintsError:
697
+ return deriveDeviceStatus(
698
+ "no-audio-devices-found" /* NoAudioDevicesFound */,
699
+ "no-video-devices-found" /* NoVideoDevicesFound */,
700
+ "no-devices-found" /* NoDevicesFound */
701
+ );
702
+ case MediaDeviceFailure.OverconstrainedError:
703
+ return deriveDeviceStatus(
704
+ "audio-overconstrained" /* AudioOverconstrained */,
705
+ "video-overconstrained" /* VideoOverconstrained */,
706
+ "overconstrained" /* Overconstrained */
707
+ );
708
+ case MediaDeviceFailure.TypeError:
709
+ return deriveDeviceStatus(
710
+ "invalid-audio-constraints" /* InvalidAudioConstraints */,
711
+ "invalid-video-constraints" /* InvalidVideoConstraints */,
712
+ "invalid-constraints" /* InvalidConstraints */
713
+ );
714
+ case MediaDeviceFailure.NotSupportedError:
715
+ return deriveDeviceStatus(
716
+ "not-supported-error-only-audio" /* NotSupportedErrorOnlyAudioInput */,
717
+ "not-supported-error-only-video" /* NotSupportedErrorOnlyVideoInput */,
718
+ "not-supported-error" /* NotSupportedError */
719
+ );
720
+ default:
721
+ return deriveDeviceStatus(
722
+ "unknown-error-only-audioinput" /* UnknownErrorOnlyAudioinput */,
723
+ "unknown-error-only-videoinput" /* UnknownErrorOnlyVideoinput */,
724
+ "unknown-error" /* UnknownError */
725
+ );
726
+ }
727
+ };
728
+ var mapErrorStatus = (status) => (constraints) => {
729
+ const toStatus = makeDeriveDeviceStatus(constraints);
730
+ switch (status) {
731
+ case "audio-video-devices-not-found" /* AudioVideoDevicesNotFound */:
732
+ return toStatus(
733
+ "audio-device-not-found" /* AudioDeviceNotFound */,
734
+ "video-device-not-found" /* VideoDeviceNotFound */,
735
+ status
736
+ );
737
+ case "devices-in-use" /* DevicesInUse */:
738
+ return toStatus(
739
+ "audio-device-in-use" /* AudioDeviceInUse */,
740
+ "video-device-in-use" /* VideoDeviceInUse */,
741
+ status
742
+ );
743
+ case "invalid-constraints" /* InvalidConstraints */:
744
+ return toStatus(
745
+ "invalid-audio-constraints" /* InvalidAudioConstraints */,
746
+ "invalid-video-constraints" /* InvalidVideoConstraints */,
747
+ status
748
+ );
749
+ case "no-devices-found" /* NoDevicesFound */:
750
+ return toStatus(
751
+ "no-audio-devices-found" /* NoAudioDevicesFound */,
752
+ "no-video-devices-found" /* NoVideoDevicesFound */,
753
+ status
754
+ );
755
+ case "not-supported-error" /* NotSupportedError */:
756
+ return toStatus(
757
+ "not-supported-error-only-audio" /* NotSupportedErrorOnlyAudioInput */,
758
+ "not-supported-error-only-video" /* NotSupportedErrorOnlyVideoInput */,
759
+ status
760
+ );
761
+ case "overconstrained" /* Overconstrained */:
762
+ return toStatus(
763
+ "audio-overconstrained" /* AudioOverconstrained */,
764
+ "video-overconstrained" /* VideoOverconstrained */,
765
+ status
766
+ );
767
+ case "permissions-rejected" /* PermissionsRejected */:
768
+ return toStatus(
769
+ "permissions-rejected-audioinput" /* PermissionsRejectedAudioInput */,
770
+ "permissions-rejected-videoinput" /* PermissionsRejectedVideoInput */,
771
+ status
772
+ );
773
+ case "unknown-error" /* UnknownError */:
774
+ return toStatus(
775
+ "unknown-error-only-audioinput" /* UnknownErrorOnlyAudioinput */,
776
+ "unknown-error-only-videoinput" /* UnknownErrorOnlyVideoinput */,
777
+ status
778
+ );
779
+ default:
780
+ return status;
781
+ }
782
+ };
783
+ var toSameDeviceStatus = ({
784
+ audio,
785
+ video
786
+ }) => {
787
+ if (audio) {
788
+ if (video) {
789
+ return "permissions-granted" /* PermissionsGranted */;
790
+ }
791
+ return "permissions-granted-fallback-videoinput" /* PermissionsGrantedFallbackVideoinput */;
792
+ }
793
+ if (video) {
794
+ return "permissions-granted-fallback-audioinput" /* PermissionsGrantedFallbackAudioinput */;
795
+ }
796
+ return "permissions-granted-fallback-devices" /* PermissionsGrantedFallback */;
797
+ };
798
+ var toOnlyDeviceStatus = (kind, matched, devices) => {
799
+ const hasRelatedDevices = devices.some((d) => {
800
+ if (kind === "audioinput") {
801
+ return d.kind === "videoinput";
802
+ }
803
+ return d.kind === "audioinput";
804
+ });
805
+ if (matched) {
806
+ if (hasRelatedDevices) {
807
+ return kind === "audioinput" ? "permissions-only-audioinput" /* PermissionsOnlyAudioinput */ : "permissions-only-videoinput" /* PermissionsOnlyVideoinput */;
808
+ }
809
+ return kind === "audioinput" ? "permissions-only-audioinput-no-video-devices" /* PermissionsOnlyAudioinputNoVideoDevices */ : "permissions-only-videoinput-no-audio-devices" /* PermissionsOnlyVideoinputNoAudioDevices */;
810
+ }
811
+ if (hasRelatedDevices) {
812
+ return kind === "audioinput" ? "permissions-only-fallback-audioinput" /* PermissionsOnlyAudioinputFallback */ : "permissions-only-fallback-videoinput" /* PermissionsOnlyVideoinputFallback */;
813
+ }
814
+ return kind === "audioinput" ? "permissions-only-fallback-audioinput-no-video-devices" /* PermissionsOnlyAudioinputFallbackNoVideoDevices */ : "permissions-only-fallback-videoinput-no-audio-devices" /* PermissionsOnlyVideoinputFallbackNoAudioDevices */;
815
+ };
816
+ var requestUserMedia = (currentDevices, getMedia = getUserMedia) => async (constraints) => {
817
+ const deriveDeviceStatus = makeDeriveDeviceStatus(constraints);
818
+ const devices = currentDevices ?? await getDevices();
819
+ try {
820
+ const stream = await getMedia(constraints);
821
+ const grantedDevices = hasAnyGrantedInput(devices) ? devices : await getDevices();
822
+ const { audio, video } = isStreamingRequestedDevices(
823
+ constraints,
824
+ stream,
825
+ grantedDevices
826
+ );
827
+ const onlyAudioStatus = toOnlyDeviceStatus(
828
+ "audioinput",
829
+ audio,
830
+ grantedDevices
831
+ );
832
+ const onlyVideoStatus = toOnlyDeviceStatus(
833
+ "videoinput",
834
+ video,
835
+ grantedDevices
836
+ );
837
+ const status = deriveDeviceStatus(
838
+ onlyAudioStatus,
839
+ onlyVideoStatus,
840
+ toSameDeviceStatus({ audio, video })
841
+ );
842
+ return [stream, status];
843
+ } catch (error) {
844
+ if (error instanceof Error) {
845
+ const status = toErrorDeviceStatus(error, deriveDeviceStatus);
846
+ return [void 0, status];
847
+ }
848
+ throw error;
849
+ }
850
+ };
851
+ var mergeNoDeviceStatus = (constraints, anyDevices, status) => {
852
+ if (status !== "no-devices-found" /* NoDevicesFound */ || !anyDevices.audio && !anyDevices.video && constraints.audio && constraints.video) {
853
+ return status;
854
+ }
855
+ if (!anyDevices.audio && constraints.audio) {
856
+ return "no-audio-devices-found" /* NoAudioDevicesFound */;
857
+ }
858
+ if (!anyDevices.video && constraints.video) {
859
+ return "no-video-devices-found" /* NoVideoDevicesFound */;
860
+ }
861
+ return status;
862
+ };
863
+ var requestUserMediaWithRetry = (currentDevices, createRequestUserMedia = requestUserMedia) => async (constraints) => {
864
+ const devices = isEmpty(currentDevices) ? await getDevices() : currentDevices;
865
+ const [anyAudioDevices, anyVideoDevices] = hasAnyInputs(devices);
866
+ const request = (constraints2, devices2) => createRequestUserMedia(devices2)(constraints2);
867
+ if (anyAudioDevices && anyVideoDevices) {
868
+ const [stream, status] = await request(constraints, devices);
869
+ if (stream) {
870
+ return [stream, status];
871
+ }
872
+ const mapStatus = mapErrorStatus(status);
873
+ if (constraints.video) {
874
+ const [audioStream] = await request(
875
+ {
876
+ ...constraints,
877
+ video: false
878
+ },
879
+ devices
880
+ );
881
+ if (audioStream) {
882
+ const videoStatus = mapStatus({ video: true });
883
+ return [audioStream, videoStatus];
884
+ }
885
+ }
886
+ if (constraints.audio) {
887
+ const [videoStream] = await request(
888
+ {
889
+ ...constraints,
890
+ audio: false
891
+ },
892
+ devices
893
+ );
894
+ if (videoStream) {
895
+ return [videoStream, mapStatus({ audio: true })];
896
+ }
897
+ }
898
+ return [stream, status];
899
+ } else {
900
+ const [stream, status] = await request(
901
+ {
902
+ audio: anyAudioDevices && constraints.audio,
903
+ video: anyVideoDevices && constraints.video
904
+ },
905
+ devices
906
+ );
907
+ return [
908
+ stream,
909
+ mergeNoDeviceStatus(
910
+ constraints,
911
+ { audio: anyAudioDevices, video: anyVideoDevices },
912
+ status
913
+ )
914
+ ];
915
+ }
916
+ };
917
+ var createGetUserMediaProcess = (getUserMedia3, getCurrentDevices, { initialMedia, scope = "media" }) => {
918
+ const props = {
919
+ unsubscribe: void 0,
920
+ currentDevices: getCurrentDevices(),
921
+ cachedAudioTrackId: "",
922
+ cachedVideoTrackId: "",
923
+ cachedAudioInput: void 0,
924
+ cachedVideoInput: void 0
925
+ };
926
+ const getExpectedAudioInput = createMemorizedGetExpectedInput();
927
+ const getExpectedVideoInput = createMemorizedGetExpectedInput();
928
+ const getInput = ([track] = []) => {
929
+ if (!track) {
930
+ return void 0;
931
+ }
932
+ const isAudio = track.kind === "audio";
933
+ const cacheKey = isAudio ? "cachedAudioInput" : "cachedVideoInput";
934
+ const cachedTrackId = isAudio ? "cachedAudioTrackId" : "cachedVideoTrackId";
935
+ const trackId = track.label;
936
+ const { width, height } = track.getSettings();
937
+ if (trackId === props[cachedTrackId] && track.readyState === "live" && (isAudio || !isAudio && isRequestedResolution({ width, height }, props[cacheKey]))) {
938
+ return props[cacheKey];
939
+ }
940
+ const devices = getCurrentDevices();
941
+ const findTrack = findMediaInputFromMediaStreamTrack(devices);
942
+ const input = findTrack(track);
943
+ props[cacheKey] = input;
944
+ props[cachedTrackId] = trackId;
945
+ return input;
946
+ };
947
+ return async (constraints) => {
948
+ const [stream, status] = await getUserMedia3(constraints);
949
+ logger.debug({ scope, stream, status }, "getUserMedia");
950
+ props.currentDevices = await getDevices();
951
+ const deriveDeviceStatus = makeDeriveDeviceStatus(constraints);
952
+ const muteTrack = muteStreamTrack2(stream);
953
+ const muteAudio = (mute) => muteTrack(mute, "audio");
954
+ const muteVideo = (mute) => muteTrack(mute, "video");
955
+ return wrapToJSON(
956
+ shallowCopy(initialMedia ?? {}, {
957
+ constraints,
958
+ rawStream: stream,
959
+ stream,
960
+ status,
961
+ get devices() {
962
+ return props.currentDevices;
963
+ },
964
+ set devices(newDevices) {
965
+ props.currentDevices = newDevices;
966
+ },
967
+ get audioInput() {
968
+ return getInput(stream?.getAudioTracks());
969
+ },
970
+ get videoInput() {
971
+ return getInput(stream?.getVideoTracks());
972
+ },
973
+ get expectedAudioInput() {
974
+ return getExpectedAudioInput(constraints.audio, () => ({
975
+ devices: getCurrentDevices().filter(isAudioInput2),
976
+ input: getInput(stream?.getAudioTracks())
977
+ }));
978
+ },
979
+ get expectedVideoInput() {
980
+ return getExpectedVideoInput(constraints.video, () => ({
981
+ devices: getCurrentDevices().filter(isVideoInput2),
982
+ input: getInput(stream?.getVideoTracks())
983
+ }));
984
+ },
985
+ muteAudio,
986
+ muteVideo,
987
+ get audioMuted() {
988
+ return isMuted(stream?.getAudioTracks());
989
+ },
990
+ get videoMuted() {
991
+ return isMuted(stream?.getVideoTracks());
992
+ },
993
+ applyConstraints: async (constraints2) => {
994
+ try {
995
+ logger.debug(
996
+ { scope, constraints: constraints2 },
997
+ "apply native constraints"
998
+ );
999
+ return await applyConstraints2(
1000
+ stream?.getTracks(),
1001
+ constraints2
1002
+ );
1003
+ } catch (error) {
1004
+ if (error instanceof Error) {
1005
+ const status2 = toErrorDeviceStatus(
1006
+ error,
1007
+ deriveDeviceStatus
1008
+ );
1009
+ logger.error(
1010
+ {
1011
+ scope,
1012
+ status: status2,
1013
+ error,
1014
+ constraints: constraints2
1015
+ },
1016
+ "fail to apply native constraints"
1017
+ );
1018
+ initialMedia.status = status2;
1019
+ }
1020
+ throw error;
1021
+ }
1022
+ },
1023
+ getSettings: () => {
1024
+ const settings = {
1025
+ audio: stream?.getAudioTracks().map((track) => track.getSettings()) ?? [],
1026
+ video: stream?.getVideoTracks().map((track) => track.getSettings()) ?? []
1027
+ };
1028
+ logger.debug({ settings, scope }, "get native settings");
1029
+ return settings;
1030
+ },
1031
+ release: () => {
1032
+ stopMediaStream2(stream);
1033
+ if (props.unsubscribe) {
1034
+ props.unsubscribe();
1035
+ props.unsubscribe = void 0;
1036
+ }
1037
+ return Promise.resolve();
1038
+ }
1039
+ })
1040
+ );
1041
+ };
1042
+ };
1043
+
1044
+ // src/videoProcessor.ts
1045
+ import {
1046
+ createVideoProcessor,
1047
+ createCanvasTransform,
1048
+ createVideoTrackProcessor,
1049
+ createVideoTrackProcessorWithFallback,
1050
+ isRenderEffects,
1051
+ isSegmentationModel
1052
+ } from "@pexip/media-processor";
1053
+ import {
1054
+ muteStreamTrack as muteStreamTrack3,
1055
+ extractConstraintsWithKeys as extractConstraintsWithKeys2,
1056
+ getValueFromConstrainNumber
1057
+ } from "@pexip/media-control";
1058
+ import { isEmpty as isEmpty2 } from "@pexip/utils";
1059
+ var FEATURE_KEYS = [
1060
+ "backgroundBlurAmount",
1061
+ "bgImageUrl",
1062
+ "edgeBlurAmount",
1063
+ "flipHorizontal",
1064
+ "foregroundThreshold",
1065
+ "frameRate",
1066
+ "videoSegmentation",
1067
+ "videoSegmentationModel",
1068
+ "width",
1069
+ "height"
1070
+ ];
1071
+ var getVideoConstraints = extractConstraintsWithKeys2(FEATURE_KEYS);
1072
+ var updateFeatureProps = (constraints, props) => {
1073
+ const extracted = getVideoConstraints(constraints);
1074
+ return FEATURE_KEYS.reduce((accm, key) => {
1075
+ switch (key) {
1076
+ case "videoSegmentation": {
1077
+ const [[feature] = []] = extracted[key];
1078
+ if (isRenderEffects(feature) && props[key] !== feature) {
1079
+ props[key] = feature;
1080
+ return { ...accm, [key]: feature };
1081
+ }
1082
+ return accm;
1083
+ }
1084
+ case "videoSegmentationModel": {
1085
+ const [[feature] = []] = extracted[key];
1086
+ if (isSegmentationModel(feature) && props[key] !== feature) {
1087
+ props[key] = feature;
1088
+ return { ...accm, [key]: feature };
1089
+ }
1090
+ return accm;
1091
+ }
1092
+ case "bgImageUrl": {
1093
+ const [[feature] = []] = extracted[key];
1094
+ if (feature) {
1095
+ props[key] = feature;
1096
+ return { ...accm, [key]: feature };
1097
+ }
1098
+ return accm;
1099
+ }
1100
+ case "width":
1101
+ case "height":
1102
+ case "frameRate":
1103
+ case "foregroundThreshold":
1104
+ case "edgeBlurAmount":
1105
+ case "backgroundBlurAmount": {
1106
+ const [feature] = extracted[key];
1107
+ if (feature !== void 0) {
1108
+ const value = getValueFromConstrainNumber(feature);
1109
+ if (props[key] !== value) {
1110
+ props[key] = value;
1111
+ return {
1112
+ ...accm,
1113
+ [key]: value
1114
+ };
1115
+ }
1116
+ }
1117
+ return accm;
1118
+ }
1119
+ case "flipHorizontal": {
1120
+ const [feature] = extracted[key];
1121
+ if (feature !== void 0 && props[key] !== feature) {
1122
+ props[key] = feature;
1123
+ return { ...accm, [key]: feature };
1124
+ }
1125
+ return accm;
1126
+ }
1127
+ }
1128
+ }, {});
1129
+ };
1130
+ var applyFeatures = async (transformer, features) => {
1131
+ if (features.bgImageUrl) {
1132
+ await transformer.loadBackgroundImage(features.bgImageUrl);
1133
+ }
1134
+ Object.keys(features).forEach((key) => {
1135
+ const k = key;
1136
+ switch (k) {
1137
+ case "edgeBlurAmount":
1138
+ case "foregroundThreshold": {
1139
+ const value = features[k];
1140
+ if (value !== void 0) {
1141
+ transformer[k] = value;
1142
+ }
1143
+ return;
1144
+ }
1145
+ case "backgroundBlurAmount": {
1146
+ const value = features[k];
1147
+ if (value !== void 0) {
1148
+ transformer[k] = getBlurKernelSize(
1149
+ value,
1150
+ transformer.height
1151
+ );
1152
+ }
1153
+ return;
1154
+ }
1155
+ case "videoSegmentation": {
1156
+ const value = features[k];
1157
+ if (value && value !== transformer.effects) {
1158
+ transformer.effects = value;
1159
+ }
1160
+ return;
1161
+ }
1162
+ case "flipHorizontal": {
1163
+ const value = features[k];
1164
+ if (value !== void 0 && value !== transformer.flipHorizontal) {
1165
+ transformer.flipHorizontal = value;
1166
+ }
1167
+ return;
1168
+ }
1169
+ default: {
1170
+ return;
1171
+ }
1172
+ }
1173
+ });
1174
+ };
1175
+ var adjustResolution = async (media, features, processingSize) => {
1176
+ if (features.videoSegmentation) {
1177
+ const {
1178
+ video: [videoSettings]
1179
+ } = media.getSettings();
1180
+ const constraints = updateFeatureProps(media.constraints?.video, {});
1181
+ switch (features.videoSegmentation) {
1182
+ case "blur":
1183
+ case "overlay": {
1184
+ if (videoSettings?.height !== processingSize.height) {
1185
+ try {
1186
+ await media.applyConstraints({
1187
+ video: {
1188
+ width: processingSize.width,
1189
+ height: processingSize.height
1190
+ }
1191
+ });
1192
+ const {
1193
+ video: [postVideoSettings]
1194
+ } = media.getSettings();
1195
+ if (postVideoSettings?.height !== processingSize.height) {
1196
+ await media.applyConstraints({
1197
+ video: { height: 720 }
1198
+ });
1199
+ }
1200
+ } catch (error) {
1201
+ await media.applyConstraints({
1202
+ video: { height: 720 }
1203
+ });
1204
+ }
1205
+ }
1206
+ break;
1207
+ }
1208
+ case "none": {
1209
+ if (constraints.height && constraints.height !== videoSettings?.height) {
1210
+ await media.applyConstraints({
1211
+ video: {
1212
+ height: constraints.height
1213
+ }
1214
+ });
1215
+ }
1216
+ break;
1217
+ }
1218
+ }
1219
+ }
1220
+ };
1221
+ var getTrackProcessor = (shouldUseStreamTrackProcessor, ...params) => {
1222
+ if (shouldUseStreamTrackProcessor && "MediaStreamTrackProcessor" in window) {
1223
+ return createVideoTrackProcessor();
1224
+ }
1225
+ return createVideoTrackProcessorWithFallback(...params);
1226
+ };
1227
+ var createVideoStreamProcess = ({
1228
+ trackProcessorAPI = "stream",
1229
+ processingWidth,
1230
+ processingHeight,
1231
+ shouldEnable,
1232
+ frameRate,
1233
+ //backgroundBlurAmount,
1234
+ videoSegmentation,
1235
+ //edgeBlurAmount,
1236
+ foregroundThreshold,
1237
+ bgImageUrl,
1238
+ flipHorizontal,
1239
+ edgeBlurAmount,
1240
+ scope = "media",
1241
+ ...options
1242
+ }) => {
1243
+ const videoSegmentationModel = options.videoSegmentationModel ?? "mediapipeSelfie";
1244
+ const backgroundBlurAmount = options.backgroundBlurAmount && getBlurKernelSize(options.backgroundBlurAmount, processingHeight);
1245
+ const transformer = options.transformer ?? createCanvasTransform(options.segmenters[videoSegmentationModel], {
1246
+ width: processingWidth,
1247
+ height: processingHeight,
1248
+ effects: videoSegmentation,
1249
+ foregroundThreshold,
1250
+ backgroundBlurAmount,
1251
+ edgeBlurAmount,
1252
+ flipHorizontal
1253
+ });
1254
+ const proxy = proxyWithLog(logger, scope);
1255
+ const props = {
1256
+ videoSegmentationModel,
1257
+ segmenters: {
1258
+ mediapipeSelfie: proxy(
1259
+ options.segmenters.mediapipeSelfie,
1260
+ "Segmenter"
1261
+ )
1262
+ },
1263
+ transformer: proxy(transformer, "Transformer"),
1264
+ videoProcessor: proxy(
1265
+ createVideoProcessor(
1266
+ [transformer],
1267
+ getTrackProcessor(trackProcessorAPI === "stream", {
1268
+ width: processingWidth,
1269
+ height: processingHeight,
1270
+ frameRate
1271
+ })
1272
+ ),
1273
+ "VideoProcessor"
1274
+ ),
1275
+ videoSegmentation,
1276
+ backgroundBlurAmount,
1277
+ edgeBlurAmount,
1278
+ foregroundThreshold,
1279
+ frameRate,
1280
+ bgImageUrl,
1281
+ flipHorizontal,
1282
+ hasInitialized: options.hasInitializedDeps ?? false
1283
+ };
1284
+ return async (mediaP) => {
1285
+ const media = await mediaP;
1286
+ const features = updateFeatureProps(media.constraints?.video, props);
1287
+ const shouldEnabled = shouldEnable();
1288
+ if (!shouldEnabled || !media.stream?.getVideoTracks().length) {
1289
+ logger.debug(
1290
+ { scope, features, shouldEnabled },
1291
+ "Video processing is skipped"
1292
+ );
1293
+ return media;
1294
+ }
1295
+ try {
1296
+ if (!props.hasInitialized) {
1297
+ await props.videoProcessor.open();
1298
+ props.hasInitialized = true;
1299
+ }
1300
+ await applyFeatures(props.transformer, features);
1301
+ await adjustResolution(media, features, {
1302
+ width: processingWidth,
1303
+ height: processingHeight
1304
+ });
1305
+ if (features.videoSegmentationModel && features.videoSegmentationModel !== props.transformer.segmenter.model) {
1306
+ props.transformer.segmenter = props.segmenters[features.videoSegmentationModel];
1307
+ }
1308
+ const stream = await props.videoProcessor.process(media.stream);
1309
+ const release = async () => {
1310
+ props.videoProcessor.close();
1311
+ await media.release();
1312
+ props.hasInitialized = false;
1313
+ };
1314
+ const muteAudio = (mute) => {
1315
+ media.muteAudio(mute);
1316
+ muteStreamTrack3(stream)(mute, "audio");
1317
+ };
1318
+ const muteVideo = (mute) => {
1319
+ media.muteVideo(mute);
1320
+ props.transformer.effects = mute ? "none" : props.videoSegmentation ?? "none";
1321
+ muteStreamTrack3(stream)(mute, "video");
1322
+ };
1323
+ const applyConstraints3 = applyExtendedConstraints(media, async (constraints) => {
1324
+ if (isEmpty2(constraints.video)) {
1325
+ return;
1326
+ }
1327
+ const features2 = updateFeatureProps(
1328
+ constraints.video,
1329
+ props
1330
+ );
1331
+ logger.debug(
1332
+ { scope, constraints: constraints.video, features: features2 },
1333
+ "apply video constraints"
1334
+ );
1335
+ if (isEmpty2(features2)) {
1336
+ return;
1337
+ }
1338
+ try {
1339
+ await applyFeatures(props.transformer, features2);
1340
+ await adjustResolution(media, features2, {
1341
+ width: processingWidth,
1342
+ height: processingHeight
1343
+ });
1344
+ if (features2.videoSegmentationModel && features2.videoSegmentationModel !== props.transformer.segmenter.model) {
1345
+ props.transformer.segmenter = props.segmenters[features2.videoSegmentationModel];
1346
+ }
1347
+ } catch (error) {
1348
+ if (error instanceof Error) {
1349
+ logger.error(
1350
+ {
1351
+ scope,
1352
+ constraints: constraints.video,
1353
+ features: features2,
1354
+ error
1355
+ },
1356
+ "failed to apply video constraints"
1357
+ );
1358
+ options.onError?.(error);
1359
+ }
1360
+ }
1361
+ });
1362
+ const prevGetSettings = media.getSettings;
1363
+ return wrapToJSON(
1364
+ shallowCopy(media, {
1365
+ stream,
1366
+ muteAudio,
1367
+ muteVideo,
1368
+ applyConstraints: applyConstraints3,
1369
+ release,
1370
+ getSettings: () => {
1371
+ const { audio, video } = prevGetSettings();
1372
+ const videoSettings = {
1373
+ videoSegmentation: transformer.effects,
1374
+ foregroundThreshold: transformer.foregroundThreshold,
1375
+ // Background blur amount is a function of height
1376
+ backgroundBlurAmount: props.backgroundBlurAmount,
1377
+ edgeBlurAmount: transformer.edgeBlurAmount,
1378
+ flipHorizontal: transformer.flipHorizontal,
1379
+ bgImageUrl: transformer.backgroundImage && props.bgImageUrl,
1380
+ videoSegmentationModel: transformer.segmenter.model
1381
+ };
1382
+ const settings = {
1383
+ audio,
1384
+ video: video.map((settings2) => ({
1385
+ ...settings2,
1386
+ ...videoSettings
1387
+ }))
1388
+ };
1389
+ logger.debug(
1390
+ { scope, settings: videoSettings },
1391
+ "get video processor settings"
1392
+ );
1393
+ return settings;
1394
+ }
1395
+ })
1396
+ );
1397
+ } catch (e) {
1398
+ if (e instanceof Error) {
1399
+ options.onError?.(e);
1400
+ }
1401
+ return media;
1402
+ }
1403
+ };
1404
+ };
1405
+
1406
+ // src/audioProcessor.ts
1407
+ import {
1408
+ stopMediaStream as stopMediaStream3,
1409
+ extractConstraintsWithKeys as extractConstraintsWithKeys3,
1410
+ muteStreamTrack as muteStreamTrack4
1411
+ } from "@pexip/media-control";
1412
+ import { createQueue, isEmpty as isEmpty3 } from "@pexip/utils";
1413
+ import {
1414
+ createAudioGraph,
1415
+ createAudioGraphProxy,
1416
+ createStreamSourceGraphNode,
1417
+ createStreamDestinationGraphNode,
1418
+ createAnalyzerSubscribableGraphNode,
1419
+ createDenoiseWorkletGraphNode,
1420
+ createAudioSignalDetector,
1421
+ createVADetector,
1422
+ createVoiceDetectorFromTimeData,
1423
+ createVoiceDetectorFromProbability,
1424
+ avg
1425
+ } from "@pexip/media-processor";
1426
+ var fetchDenoiseWasm = async (denoiseWasm, wasmURL) => {
1427
+ if (!wasmURL || denoiseWasm) {
1428
+ return denoiseWasm;
1429
+ }
1430
+ return await (await fetch(wasmURL)).arrayBuffer();
1431
+ };
1432
+ var FEATURE_KEYS2 = ["denoise", "vad", "asd"];
1433
+ var getAudioConstraints = extractConstraintsWithKeys3(FEATURE_KEYS2);
1434
+ var updateFeatureProps2 = (constraints, props) => {
1435
+ const extracted = getAudioConstraints(constraints);
1436
+ return FEATURE_KEYS2.reduce((accm, key) => {
1437
+ const [feature] = extracted[key];
1438
+ if (feature !== void 0 && props[key] !== feature) {
1439
+ props[key] = feature;
1440
+ return { ...accm, [key]: feature };
1441
+ }
1442
+ return accm;
1443
+ }, {});
1444
+ };
1445
+ var createAudioStreamProcess = ({
1446
+ analyzerUpdateFrequency = 0.5,
1447
+ // 0.5 Hz
1448
+ audioGraphOptions,
1449
+ audioSignalDetectionDuration = 4,
1450
+ // 4 seconds
1451
+ clock,
1452
+ createNodes,
1453
+ denoiseParams,
1454
+ fftSize = 2048,
1455
+ // FFT size
1456
+ onAudioSignalDetected,
1457
+ onVoiceActivityDetected,
1458
+ shouldEnable,
1459
+ silentThreshold = 10 / 32767,
1460
+ // At least one LSB 16-bit data (compare is on absolute value).
1461
+ throttleMs = 3e3,
1462
+ // 3 seconds
1463
+ scope = "media"
1464
+ }) => {
1465
+ const props = {
1466
+ audioGraphOptions,
1467
+ vad: false,
1468
+ asd: false,
1469
+ denoise: false
1470
+ };
1471
+ const detectAudio = onAudioSignalDetected && createAudioSignalDetector(() => !!props.asd, onAudioSignalDetected);
1472
+ const detectVA = onVoiceActivityDetected && createVADetector(onVoiceActivityDetected, () => !!props.vad, {
1473
+ throttleMs,
1474
+ clock
1475
+ });
1476
+ const detectVAFromTimeData = detectVA?.(createVoiceDetectorFromTimeData());
1477
+ const createDenoiseNode = async (denoise) => {
1478
+ if (!denoise) {
1479
+ return void 0;
1480
+ }
1481
+ if (props.denoiseNode) {
1482
+ return props.denoiseNode;
1483
+ }
1484
+ if (denoiseParams?.workletModule) {
1485
+ try {
1486
+ await props.audioGraph?.addWorklet(
1487
+ denoiseParams?.workletModule,
1488
+ denoiseParams?.workletOptions
1489
+ );
1490
+ } catch (error) {
1491
+ logger.error(
1492
+ {
1493
+ scope,
1494
+ error,
1495
+ moduleURL: denoiseParams?.workletModule,
1496
+ options: denoiseParams?.workletOptions
1497
+ },
1498
+ "Failed add worklet"
1499
+ );
1500
+ return void 0;
1501
+ }
1502
+ }
1503
+ try {
1504
+ props.denoiseWasm = await fetchDenoiseWasm(
1505
+ props.denoiseWasm,
1506
+ denoiseParams?.wasmURL
1507
+ );
1508
+ } catch (error) {
1509
+ logger.error(
1510
+ {
1511
+ scope,
1512
+ error,
1513
+ url: denoiseParams?.wasmURL,
1514
+ prevWasm: props.denoiseWasm
1515
+ },
1516
+ "Failed to fetch denoise wasm"
1517
+ );
1518
+ return void 0;
1519
+ }
1520
+ const detectVAFromProbability = detectVA?.(
1521
+ createVoiceDetectorFromProbability()
1522
+ );
1523
+ if (props.denoiseWasm) {
1524
+ const denoise2 = createDenoiseWorkletGraphNode(
1525
+ props.denoiseWasm,
1526
+ (vads) => {
1527
+ detectVAFromProbability?.(avg(vads));
1528
+ }
1529
+ );
1530
+ props.denoiseNode = denoise2;
1531
+ return denoise2;
1532
+ }
1533
+ };
1534
+ const createAnalyzer = () => {
1535
+ const shouldUseAnalyzer = () => !!(props.vad && !props.denoise || props.asd) && (detectAudio || detectVA);
1536
+ if (!shouldUseAnalyzer()) {
1537
+ return;
1538
+ }
1539
+ if (props.analyzer) {
1540
+ return props.analyzer;
1541
+ }
1542
+ const detectSilentAudio = detectAudio?.(
1543
+ createQueue(
1544
+ audioSignalDetectionDuration / analyzerUpdateFrequency
1545
+ ),
1546
+ silentThreshold
1547
+ );
1548
+ const analyzer = createAnalyzerSubscribableGraphNode({
1549
+ updateFrequency: analyzerUpdateFrequency,
1550
+ messageHandler: (analyzer2) => {
1551
+ if (shouldUseAnalyzer()) {
1552
+ if (!props.analyzerBuffer) {
1553
+ props.analyzerBuffer = new Float32Array(fftSize);
1554
+ }
1555
+ analyzer2.getFloatTimeDomainData(props.analyzerBuffer);
1556
+ const data = Array.from(props.analyzerBuffer);
1557
+ detectSilentAudio?.(data);
1558
+ !props.denoise && detectVAFromTimeData?.(data);
1559
+ }
1560
+ },
1561
+ fftSize
1562
+ });
1563
+ props.analyzer = analyzer;
1564
+ return analyzer;
1565
+ };
1566
+ return async (mediaP) => {
1567
+ const media = await mediaP;
1568
+ updateFeatureProps2(media.constraints?.audio, props);
1569
+ const shouldProcessAudio = shouldEnable() && !!media.stream?.getAudioTracks().length && (!!onVoiceActivityDetected || !!onAudioSignalDetected || props.asd || props.vad || props.denoise || !!createNodes);
1570
+ if (!media.stream?.getAudioTracks().length || !shouldProcessAudio) {
1571
+ return media;
1572
+ }
1573
+ try {
1574
+ const source = createStreamSourceGraphNode(media.stream);
1575
+ const destination = createStreamDestinationGraphNode();
1576
+ const otherNodes = createNodes?.(media) ?? [];
1577
+ const analyzer = createAnalyzer();
1578
+ const initialAudioNodeConnection = [
1579
+ [source, ...otherNodes, destination],
1580
+ [source, analyzer]
1581
+ ];
1582
+ logger.debug(
1583
+ { initialAudioNodeConnection, scope },
1584
+ "Initial AudioNodeConnection"
1585
+ );
1586
+ const audioGraph = createAudioGraphProxy(
1587
+ createAudioGraph(
1588
+ initialAudioNodeConnection,
1589
+ props.audioGraphOptions
1590
+ ),
1591
+ {
1592
+ connect: (target, args) => {
1593
+ logger.debug({ scope, target, args }, "connect nodes");
1594
+ },
1595
+ disconnect: (target, args) => {
1596
+ logger.debug({ scope, target, args }, "disconnect nodes");
1597
+ }
1598
+ }
1599
+ );
1600
+ props.audioGraph = audioGraph;
1601
+ const denoiseNode = await createDenoiseNode(props.denoise);
1602
+ const connectDenoise = (node) => {
1603
+ if (node) {
1604
+ audioGraph.disconnect([source, ...otherNodes, destination]);
1605
+ audioGraph.connect([
1606
+ source,
1607
+ node,
1608
+ ...otherNodes,
1609
+ destination
1610
+ ]);
1611
+ }
1612
+ };
1613
+ connectDenoise(denoiseNode);
1614
+ const tracks = [
1615
+ ...destination?.node?.stream.getAudioTracks() ?? [],
1616
+ ...media.stream?.getVideoTracks().map((t) => t.clone()) ?? []
1617
+ ];
1618
+ const stream = new MediaStream(tracks);
1619
+ const applyConstraints3 = applyExtendedConstraints(media, async (constraints) => {
1620
+ if (isEmpty3(constraints.audio)) {
1621
+ return;
1622
+ }
1623
+ const features = updateFeatureProps2(
1624
+ constraints.audio,
1625
+ props
1626
+ );
1627
+ logger.debug(
1628
+ { scope, constraints: constraints.audio, features },
1629
+ "apply audio constraints"
1630
+ );
1631
+ if (isEmpty3(features) || ["closed", "closing"].includes(audioGraph.state)) {
1632
+ return;
1633
+ }
1634
+ try {
1635
+ const denoiseNode2 = await createDenoiseNode(
1636
+ props.denoise
1637
+ );
1638
+ if (denoiseNode2) {
1639
+ if (!source.hasConnectedTo(denoiseNode2)) {
1640
+ connectDenoise(denoiseNode2);
1641
+ }
1642
+ } else {
1643
+ if (props.denoiseNode) {
1644
+ audioGraph.disconnect([
1645
+ source,
1646
+ props.denoiseNode,
1647
+ ...otherNodes,
1648
+ destination
1649
+ ]);
1650
+ audioGraph.connect([
1651
+ source,
1652
+ ...otherNodes,
1653
+ destination
1654
+ ]);
1655
+ audioGraph.releaseInit(props.denoiseNode);
1656
+ props.denoiseNode = void 0;
1657
+ }
1658
+ }
1659
+ const analyzer2 = createAnalyzer();
1660
+ if (analyzer2) {
1661
+ audioGraph.connect([source, analyzer2]);
1662
+ } else {
1663
+ if (props.analyzer) {
1664
+ audioGraph.disconnect([source, props.analyzer]);
1665
+ audioGraph.releaseInit(props.analyzer);
1666
+ props.analyzer = void 0;
1667
+ }
1668
+ }
1669
+ } catch (error) {
1670
+ if (error instanceof Error) {
1671
+ logger.error(
1672
+ {
1673
+ scope,
1674
+ constraints: constraints.audio,
1675
+ features
1676
+ },
1677
+ "failed to apply audio constraints"
1678
+ );
1679
+ }
1680
+ }
1681
+ });
1682
+ const release = async () => {
1683
+ logger.debug({ scope }, "Release Media");
1684
+ stopMediaStream3(stream);
1685
+ await audioGraph.release();
1686
+ await media.release();
1687
+ props.denoiseNode = void 0;
1688
+ props.analyzer = void 0;
1689
+ props.audioGraph = void 0;
1690
+ };
1691
+ const muteAudio = (mute) => {
1692
+ media.muteAudio(mute);
1693
+ muteStreamTrack4(stream)(mute, "audio");
1694
+ };
1695
+ const muteVideo = (mute) => {
1696
+ media.muteVideo(mute);
1697
+ muteStreamTrack4(stream)(mute, "video");
1698
+ };
1699
+ const prevGetSettings = media.getSettings;
1700
+ return wrapToJSON(
1701
+ shallowCopy(media, {
1702
+ stream,
1703
+ applyConstraints: applyConstraints3,
1704
+ muteAudio,
1705
+ muteVideo,
1706
+ release,
1707
+ getSettings: () => {
1708
+ const { audio, video } = prevGetSettings();
1709
+ const denoise = !!props.denoiseNode && source.hasConnectedTo(props.denoiseNode);
1710
+ const asd = !!props.asd;
1711
+ const vad = !!props.vad;
1712
+ const audioSettings = {
1713
+ denoise,
1714
+ asd,
1715
+ vad
1716
+ };
1717
+ const settings = {
1718
+ audio: audio.map((settings2) => ({
1719
+ ...settings2,
1720
+ ...audioSettings
1721
+ })),
1722
+ video
1723
+ };
1724
+ logger.debug(
1725
+ { scope, settings: audioSettings },
1726
+ "get audio processor settings"
1727
+ );
1728
+ return settings;
1729
+ }
1730
+ })
1731
+ );
1732
+ } catch (error) {
1733
+ logger.error(
1734
+ { scope, error },
1735
+ "Unable to use WebAudio, return the raw media instead"
1736
+ );
1737
+ return media;
1738
+ }
1739
+ };
1740
+ };
1741
+
1742
+ // src/audioMixingProcessor.ts
1743
+ import {
1744
+ muteStreamTrack as muteStreamTrack5,
1745
+ stopMediaStream as stopMediaStream4,
1746
+ extractConstraintsWithKeys as extractConstraintsWithKeys4
1747
+ } from "@pexip/media-control";
1748
+ import { isEmpty as isEmpty4 } from "@pexip/utils";
1749
+ import {
1750
+ createAudioGraph as createAudioGraph2,
1751
+ createAudioGraphProxy as createAudioGraphProxy2,
1752
+ createChannelMergerGraphNode,
1753
+ createStreamDestinationGraphNode as createStreamDestinationGraphNode2,
1754
+ createStreamSourceGraphNode as createStreamSourceGraphNode2,
1755
+ resumeAudioOnUnmute
1756
+ } from "@pexip/media-processor";
1757
+ var FEATURE_KEYS3 = ["mixWithAdditionalMedia"];
1758
+ var getAudioConstraints2 = extractConstraintsWithKeys4(FEATURE_KEYS3);
1759
+ var updateFeatureProps3 = (constraints, props) => {
1760
+ const extracted = getAudioConstraints2(constraints);
1761
+ return FEATURE_KEYS3.reduce((accm, key) => {
1762
+ const [feature] = extracted[key];
1763
+ if (feature !== void 0 && props[key] !== feature) {
1764
+ props[key] = feature;
1765
+ return { ...accm, [key]: feature };
1766
+ }
1767
+ return accm;
1768
+ }, {});
1769
+ };
1770
+ var createAudioMixingProcess = (getCurrrentMedia, scope = "mixer") => {
1771
+ const props = {};
1772
+ return async (mediaP) => {
1773
+ const media = await mediaP;
1774
+ updateFeatureProps3(media.constraints?.audio, props);
1775
+ const [mainTrack] = media.stream?.getAudioTracks() ?? [];
1776
+ const displayStream = getCurrrentMedia();
1777
+ const [displayTrack] = displayStream?.getAudioTracks?.() ?? [];
1778
+ const applyConstraints3 = applyExtendedConstraints(media, async (constraints) => {
1779
+ if (isEmpty4(constraints.audio)) {
1780
+ return;
1781
+ }
1782
+ const features = updateFeatureProps3(constraints.audio, props);
1783
+ logger.debug(
1784
+ { scope, constraints: constraints.audio, features },
1785
+ "apply audio mixing constraints"
1786
+ );
1787
+ if (isEmpty4(features) || props.audioGraph && ["closed", "closing"].includes(props.audioGraph.state)) {
1788
+ return;
1789
+ }
1790
+ if (features.mixWithAdditionalMedia) {
1791
+ const newStream = getCurrrentMedia();
1792
+ const [newTrack] = getCurrrentMedia()?.getAudioTracks() ?? [];
1793
+ if (!newTrack) {
1794
+ return;
1795
+ }
1796
+ if (!props.audioGraph || !props.merger) {
1797
+ logger.debug(
1798
+ { scope },
1799
+ "Should update the media stream directly"
1800
+ );
1801
+ return;
1802
+ }
1803
+ if (props.displaySource && props.merger) {
1804
+ if (newStream === props.displaySource.audioNode?.mediaStream) {
1805
+ return;
1806
+ }
1807
+ props.audioGraph.disconnect([
1808
+ props.displaySource,
1809
+ props.merger
1810
+ ]);
1811
+ }
1812
+ if (newStream) {
1813
+ props.displaySource = createStreamSourceGraphNode2(newStream);
1814
+ props.audioGraph.connect([
1815
+ props.displaySource,
1816
+ props.merger
1817
+ ]);
1818
+ }
1819
+ } else if (features.mixWithAdditionalMedia === false) {
1820
+ if (props.displaySource) {
1821
+ props.displaySource.release();
1822
+ props.audioGraph?.disconnect([props.displaySource]);
1823
+ props.displaySource = void 0;
1824
+ }
1825
+ }
1826
+ return Promise.resolve();
1827
+ });
1828
+ if (!media.stream) {
1829
+ return media;
1830
+ }
1831
+ if (!mainTrack && displayTrack) {
1832
+ media.stream?.addTrack(displayTrack);
1833
+ return shallowCopy(media, {
1834
+ muteAudio: () => {
1835
+ },
1836
+ applyConstraints: applyConstraints3
1837
+ });
1838
+ }
1839
+ try {
1840
+ const mainSource = createStreamSourceGraphNode2(media.stream);
1841
+ props.displaySource = displayStream && createStreamSourceGraphNode2(displayStream);
1842
+ props.merger = createChannelMergerGraphNode();
1843
+ const destination = createStreamDestinationGraphNode2({
1844
+ channelCount: 1,
1845
+ channelCountMode: "explicit"
1846
+ });
1847
+ const initialAudioNodeConnection = [
1848
+ [mainSource, props.merger],
1849
+ [props.merger, destination]
1850
+ ];
1851
+ logger.debug(
1852
+ { initialAudioNodeConnection, scope },
1853
+ "Initial AudioNodeConnection"
1854
+ );
1855
+ const audioGraph = createAudioGraphProxy2(
1856
+ createAudioGraph2(initialAudioNodeConnection),
1857
+ {
1858
+ connect: (target, args) => {
1859
+ logger.debug({ scope, target, args }, "connect nodes");
1860
+ },
1861
+ disconnect: (target, args) => {
1862
+ logger.debug({ scope, target, args }, "disconnect nodes");
1863
+ }
1864
+ }
1865
+ );
1866
+ props.audioGraph = audioGraph;
1867
+ const unsubscribes = media.rawStream?.getAudioTracks().map(resumeAudioOnUnmute(audioGraph.context));
1868
+ if (props.displaySource) {
1869
+ props.audioGraph.connect([props.displaySource, props.merger]);
1870
+ }
1871
+ const tracks = [
1872
+ ...destination?.node?.stream.getAudioTracks() ?? [],
1873
+ ...media.stream?.getVideoTracks() ?? []
1874
+ ];
1875
+ const stream = new MediaStream(tracks);
1876
+ const release = async () => {
1877
+ logger.debug({ scope }, "Release Media");
1878
+ unsubscribes?.forEach((unsubscribe) => unsubscribe());
1879
+ stopMediaStream4(stream);
1880
+ await props.audioGraph?.release();
1881
+ await media.release();
1882
+ props.audioGraph = void 0;
1883
+ props.merger = void 0;
1884
+ props.displaySource = void 0;
1885
+ };
1886
+ const muteAudio = (mute) => {
1887
+ media.muteAudio(mute);
1888
+ muteStreamTrack5(mainSource.audioNode?.mediaStream)(
1889
+ mute,
1890
+ "audio"
1891
+ );
1892
+ };
1893
+ const prevGetSettings = media.getSettings;
1894
+ return wrapToJSON(
1895
+ shallowCopy(media, {
1896
+ stream,
1897
+ applyConstraints: applyConstraints3,
1898
+ muteAudio,
1899
+ release,
1900
+ getSettings: () => {
1901
+ const { audio, video } = prevGetSettings();
1902
+ const mixWithAdditionalMedia = !!props.mixWithAdditionalMedia;
1903
+ const audioSettings = {
1904
+ mixWithAdditionalMedia
1905
+ };
1906
+ const settings = {
1907
+ audio: audio.map((settings2) => ({
1908
+ ...settings2,
1909
+ ...audioSettings
1910
+ })),
1911
+ video
1912
+ };
1913
+ logger.debug(
1914
+ { scope, settings: audioSettings },
1915
+ "get audio mixing processor settings"
1916
+ );
1917
+ return settings;
1918
+ }
1919
+ })
1920
+ );
1921
+ } catch (error) {
1922
+ logger.error(
1923
+ { scope, error },
1924
+ "Unable to use WebAudio, return the raw media instead"
1925
+ );
1926
+ return media;
1927
+ }
1928
+ };
1929
+ };
1930
+
1931
+ // src/media.ts
1932
+ var createMediaPropsHandler = (signals) => ({
1933
+ get: (target, p) => {
1934
+ switch (p) {
1935
+ default:
1936
+ return target[p];
1937
+ }
1938
+ },
1939
+ set: (target, p, value) => {
1940
+ if (target[p] === value) {
1941
+ return true;
1942
+ }
1943
+ if (p === "devices") {
1944
+ const nextDevices = value;
1945
+ const changes = getDevicesChanges(
1946
+ target[p].flatMap((device) => device.label ? [device] : []),
1947
+ nextDevices
1948
+ );
1949
+ if (isEmpty5(changes.found) && isEmpty5(changes.lost)) {
1950
+ return true;
1951
+ }
1952
+ }
1953
+ logger.debug(
1954
+ {
1955
+ oldValue: target[p],
1956
+ newValue: value,
1957
+ meta: {
1958
+ module: "Media",
1959
+ props: target
1960
+ }
1961
+ },
1962
+ `Update Props[${p}]`
1963
+ );
1964
+ switch (p) {
1965
+ case "devices": {
1966
+ if (!Array.isArray(value)) {
1967
+ return false;
1968
+ }
1969
+ const devices = value;
1970
+ target[p] = devices;
1971
+ signals?.onDevicesChanged?.emit(devices);
1972
+ target.media.devices = devices;
1973
+ return true;
1974
+ }
1975
+ case "media": {
1976
+ if (!isMedia(value)) {
1977
+ return false;
1978
+ }
1979
+ const currentStatus = target[p].status;
1980
+ target[p] = value;
1981
+ signals?.onMediaChanged?.emit(value);
1982
+ if (currentStatus !== value.status) {
1983
+ signals?.onStatusChanged?.emit(value.status);
1984
+ }
1985
+ return true;
1986
+ }
1987
+ default: {
1988
+ Reflect.set(target, p, value);
1989
+ return true;
1990
+ }
1991
+ }
1992
+ }
1993
+ });
1994
+ var createMedia = ({
1995
+ getMuteState,
1996
+ signals,
1997
+ mediaProcessors,
1998
+ getDefaultConstraints = () => ({})
1999
+ }) => {
2000
+ const initMedia = (status = "initial" /* Initial */, devices = []) => buildMedia(() => ({ status, devices }), signals.onStatusChanged?.emit);
2001
+ const _props = {
2002
+ devices: [],
2003
+ media: initMedia(),
2004
+ discardMedia: false
2005
+ };
2006
+ const props = new Proxy(_props, createMediaPropsHandler(signals));
2007
+ const queue = createAsyncQueue();
2008
+ const logger2 = createModuleLogger({
2009
+ module: "Media",
2010
+ props: _props,
2011
+ get mediaTracks() {
2012
+ return _props.media?.stream?.getTracks();
2013
+ },
2014
+ get rawMediaTracks() {
2015
+ return _props.media?.rawStream?.getTracks();
2016
+ }
2017
+ });
2018
+ const cleanup = async () => {
2019
+ if (isInitialPermissions(props.media.status)) {
2020
+ props.discardMedia = true;
2021
+ }
2022
+ const status = await deriveInitialPermissionStatus(props.media.status);
2023
+ props.media = initMedia(status, props.devices);
2024
+ };
2025
+ const mediaPipeline = createMediaPipeline([
2026
+ createGetUserMediaProcess(
2027
+ requestUserMediaWithRetry(),
2028
+ () => props.devices,
2029
+ { initialMedia: props.media }
2030
+ ),
2031
+ ...mediaProcessors,
2032
+ createMediaProcess((media) => {
2033
+ const trackSubscriptions = media.rawStream?.getTracks().map(
2034
+ (track) => createStreamTrackEventSubscriptions(track, {
2035
+ ended: signals.onStreamTrackEnded?.emit,
2036
+ mute: signals.onStreamTrackMuted?.emit,
2037
+ unmute: signals.onStreamTrackUnmuted?.emit
2038
+ })
2039
+ );
2040
+ const muteTrack = (tracks) => (muted) => {
2041
+ const [track] = tracks;
2042
+ const kind = track?.kind;
2043
+ if (track && (kind === "audio" || kind === "video")) {
2044
+ media[kind === "audio" ? "muteAudio" : "muteVideo"](
2045
+ muted
2046
+ );
2047
+ return tracks.forEach((track2) => {
2048
+ logger2.debug(
2049
+ { trackInResult: track2, intendToMute: muted },
2050
+ `mute ${track2.kind}`
2051
+ );
2052
+ signals.onStreamTrackEnabled?.emit(track2);
2053
+ });
2054
+ }
2055
+ logger2.warn({ tracks, kind }, "trying to mute but no track");
2056
+ };
2057
+ const muteAudio = muteTrack(media.stream?.getAudioTracks() ?? []);
2058
+ const muteVideo = muteTrack(media.stream?.getVideoTracks() ?? []);
2059
+ const { audio, video } = getMuteState();
2060
+ media.audioMuted !== void 0 && muteAudio(audio);
2061
+ media.videoMuted !== void 0 && muteVideo(video);
2062
+ const release = async () => {
2063
+ trackSubscriptions?.forEach((unsubscribe) => unsubscribe());
2064
+ await media.release();
2065
+ await cleanup();
2066
+ };
2067
+ logger2.debug(
2068
+ { finalAudioMute: audio, finalVideoMute: video },
2069
+ "End of media pipeline"
2070
+ );
2071
+ return wrapToJSON(
2072
+ shallowCopy(media, {
2073
+ release,
2074
+ muteAudio,
2075
+ muteVideo
2076
+ })
2077
+ );
2078
+ })
2079
+ ]);
2080
+ const updateMedia = async (constraints) => {
2081
+ if (!constraints.audio && !constraints.video) {
2082
+ throw new Error(MediaDeviceFailure2.MissingConstraintsError);
2083
+ }
2084
+ const shouldRequestAudio = shouldRequestDevice(
2085
+ constraints.audio,
2086
+ props.media?.rawStream?.getAudioTracks() ?? [],
2087
+ props.devices.filter((device) => device.kind === "audioinput")
2088
+ );
2089
+ const shouldRequestVideo = shouldRequestDevice(
2090
+ constraints.video,
2091
+ props.media?.rawStream?.getVideoTracks() ?? [],
2092
+ props.devices.filter((device) => device.kind === "videoinput")
2093
+ );
2094
+ const {
2095
+ audio: [prevAudioSettings],
2096
+ video: [prevVideoSettings]
2097
+ } = props.media.getSettings();
2098
+ const hasRequestedResolution = !props.devices.some(
2099
+ (device) => device.kind === "videoinput" && device.label
2100
+ ) || isRequestedResolution2(constraints.video, props.media?.videoInput);
2101
+ const videoFeatures = updateFeatureProps(constraints.video, {});
2102
+ const audioFeatures = updateFeatureProps2(constraints.audio, {});
2103
+ const mixingFeatures = updateFeatureProps3(constraints.audio, {});
2104
+ const audioFeaturesChanged = hasSettingsChanged(AUDIO_SETTINGS_KEYS)(
2105
+ prevAudioSettings,
2106
+ audioFeatures
2107
+ );
2108
+ const videoFeaturesChanged = hasSettingsChanged(VIDEO_SETTINGS_KEYS)(
2109
+ prevVideoSettings,
2110
+ videoFeatures
2111
+ );
2112
+ const mixingFeaturesChanged = hasSettingsChanged(MIXING_SETTINGS_KEYS)(
2113
+ prevAudioSettings,
2114
+ mixingFeatures
2115
+ );
2116
+ logger2.debug(
2117
+ {
2118
+ constraints,
2119
+ stream: props.media.stream,
2120
+ currentDevices: props.devices,
2121
+ shouldRequestAudio,
2122
+ shouldRequestVideo,
2123
+ sameResolution: hasRequestedResolution,
2124
+ audioFeaturesChanged,
2125
+ videoFeaturesChanged,
2126
+ mixingFeaturesChanged
2127
+ },
2128
+ "Is currently streaming requested stream"
2129
+ );
2130
+ if (shouldRequestAudio || shouldRequestVideo || !hasRequestedResolution || !props.media.stream) {
2131
+ if (props.media) {
2132
+ await props.media.release();
2133
+ }
2134
+ if (props.discardMedia) {
2135
+ props.discardMedia = false;
2136
+ }
2137
+ const media = await mediaPipeline.execute(constraints);
2138
+ if (props.discardMedia) {
2139
+ logger2.debug("Discard media");
2140
+ return await media.release();
2141
+ }
2142
+ props.media = media;
2143
+ } else if (audioFeaturesChanged || videoFeaturesChanged || mixingFeaturesChanged) {
2144
+ props.media.constraints = constraints;
2145
+ await props.media.applyConstraints({
2146
+ audio: audioFeatures,
2147
+ video: videoFeatures
2148
+ });
2149
+ }
2150
+ };
2151
+ const mergeMediaConstraints = (constraints) => ({
2152
+ audio: mergeConstraints(getDefaultConstraints().audio)(
2153
+ constraints.audio
2154
+ ),
2155
+ video: mergeConstraints(getDefaultConstraints().video)(
2156
+ constraints.video
2157
+ )
2158
+ });
2159
+ const getUserMediaAsync = async (constraints) => {
2160
+ queue.enqueue(async () => await updateMedia(constraints), false);
2161
+ await queue.execute();
2162
+ };
2163
+ const getUserMedia3 = (constraints) => {
2164
+ queue.enqueue(
2165
+ async () => await updateMedia(mergeMediaConstraints(constraints))
2166
+ );
2167
+ };
2168
+ const tryAndGetUserMedia = (constraints) => {
2169
+ queue.enqueue(async () => {
2170
+ props.media.status = await getPermissionStatus();
2171
+ logger2.debug(
2172
+ { status: props.media.status },
2173
+ "Current Permission State"
2174
+ );
2175
+ props.devices = await getDevices2();
2176
+ if (isInitialPermissionsGranted(props.media.status)) {
2177
+ return await updateMedia(
2178
+ mergeMediaConstraints(constraints)
2179
+ );
2180
+ }
2181
+ });
2182
+ };
2183
+ const handleNoInputs = (_availableDevices) => {
2184
+ props.media.status = "no-devices-found" /* NoDevicesFound */;
2185
+ };
2186
+ const handleDeviceChange = (devices) => {
2187
+ props.devices = devices;
2188
+ };
2189
+ subscribe((event) => {
2190
+ switch (event.detail.type) {
2191
+ case MediaEventType.NoInputDevices:
2192
+ logger2.debug(event.detail, "NoInputDevices emitted");
2193
+ handleNoInputs(event.detail.devices);
2194
+ break;
2195
+ case MediaEventType.DevicesFound:
2196
+ case MediaEventType.DevicesLost:
2197
+ logger2.debug(event.detail, "DevicesFound/Lost emitted");
2198
+ handleDeviceChange([
2199
+ ...event.detail.authorizedDevices,
2200
+ ...event.detail.unauthorizedDevices
2201
+ ]);
2202
+ break;
2203
+ default:
2204
+ break;
2205
+ }
2206
+ });
2207
+ return {
2208
+ get media() {
2209
+ return props.media;
2210
+ },
2211
+ get devices() {
2212
+ return props.devices;
2213
+ },
2214
+ getUserMedia: getUserMedia3,
2215
+ getUserMediaAsync,
2216
+ tryAndGetUserMedia
2217
+ };
2218
+ };
2219
+
2220
+ // src/previewController.ts
2221
+ import {
2222
+ getUserMedia as getUserMedia2,
2223
+ hasChangedInput,
2224
+ isMediaDeviceInfo
2225
+ } from "@pexip/media-control";
2226
+ import { createAsyncQueue as createAsyncQueue2 } from "@pexip/utils";
2227
+ var DEFAULT_QUEUE_SIZE = 1;
2228
+ var DEFAULT_QUEUE_THROTTLE_MS = 500;
2229
+ var DEFAULT_QUEUE_DELAY_MS = 100;
2230
+ var DEFAULT_QUEUE_DROP_LAST = false;
2231
+ var isPreviewInput = (value) => {
2232
+ if (isMediaDeviceInfo(value) || value === void 0) {
2233
+ return true;
2234
+ }
2235
+ return false;
2236
+ };
2237
+ var extractFeaturesToConstraints = (keysToLookFor, settings) => {
2238
+ if (!settings) {
2239
+ return {};
2240
+ }
2241
+ return keysToLookFor.reduce((set, key) => {
2242
+ if (settings[key] !== void 0) {
2243
+ return { ...set, [key]: settings[key] };
2244
+ }
2245
+ return set;
2246
+ }, {});
2247
+ };
2248
+ var createEventHandler = (eventHandlers) => (key) => (callback) => {
2249
+ eventHandlers[key] = callback;
2250
+ return () => {
2251
+ eventHandlers[key] = void 0;
2252
+ };
2253
+ };
2254
+ var getUM = async (constraints) => [
2255
+ await getUserMedia2(constraints),
2256
+ "permissions-granted" /* PermissionsGranted */
2257
+ ];
2258
+ var createAudioVideoProcessingSettingsChangeDetector = (getCurrentMedia, getPreviewMedia) => {
2259
+ const hasAudioSettingsChanged = hasSettingsChanged(AUDIO_SETTINGS_KEYS);
2260
+ const hasVideoSettingsChanged = hasSettingsChanged(VIDEO_SETTINGS_KEYS);
2261
+ return () => {
2262
+ const currentMedia = getCurrentMedia?.();
2263
+ const previewMedia = getPreviewMedia();
2264
+ const {
2265
+ audio: [mainAudio],
2266
+ video: [mainVideo]
2267
+ } = currentMedia?.getSettings() ?? { audio: [], video: [] };
2268
+ const {
2269
+ audio: [previewAudio],
2270
+ video: [previewVideo]
2271
+ } = previewMedia.getSettings();
2272
+ const changed = hasAudioSettingsChanged(mainAudio, previewAudio) || hasVideoSettingsChanged(mainVideo, previewVideo);
2273
+ return changed;
2274
+ };
2275
+ };
2276
+ var createPreviewStreamController = ({
2277
+ getCurrentDevices,
2278
+ getCurrentMedia,
2279
+ updateMainStream,
2280
+ onEnded,
2281
+ mediaSignal,
2282
+ queueOptions = {
2283
+ size: DEFAULT_QUEUE_SIZE,
2284
+ throttleInMS: DEFAULT_QUEUE_THROTTLE_MS,
2285
+ delayInMS: DEFAULT_QUEUE_DELAY_MS,
2286
+ dropLast: DEFAULT_QUEUE_DROP_LAST
2287
+ },
2288
+ processors
2289
+ }) => {
2290
+ const queue = createAsyncQueue2(queueOptions);
2291
+ const eventHandlers = {};
2292
+ const release = () => {
2293
+ if (isInitial(props.media.status)) {
2294
+ props.discardMedia = true;
2295
+ }
2296
+ props.media = buildMedia(() => ({ release }));
2297
+ return Promise.resolve();
2298
+ };
2299
+ const internalProps = {
2300
+ media: buildMedia(() => ({ release })),
2301
+ updatingPreview: false,
2302
+ updatingMain: false,
2303
+ discardMedia: false,
2304
+ initialized: false
2305
+ };
2306
+ const logger2 = createModuleLogger({
2307
+ module: "PreviewStreamController",
2308
+ eventHandlers,
2309
+ props: internalProps
2310
+ });
2311
+ const cleanUpMainSubscription = () => {
2312
+ if (eventHandlers.unsubscribeMain) {
2313
+ eventHandlers.unsubscribeMain();
2314
+ eventHandlers.unsubscribeMain = void 0;
2315
+ }
2316
+ };
2317
+ const props = new Proxy(internalProps, {
2318
+ get: (target, p) => {
2319
+ return target[p];
2320
+ },
2321
+ set: (target, p, value) => {
2322
+ if (target[p] === value) {
2323
+ return true;
2324
+ }
2325
+ logger2.debug(
2326
+ { oldValue: target[p], newValue: value },
2327
+ `Update Props[${p}]`
2328
+ );
2329
+ switch (p) {
2330
+ case "media": {
2331
+ if (!isMedia(value)) {
2332
+ return false;
2333
+ }
2334
+ target[p] = value;
2335
+ eventHandlers[p]?.(value);
2336
+ cleanUpMainSubscription();
2337
+ return true;
2338
+ }
2339
+ case "audioInput":
2340
+ case "videoInput": {
2341
+ if (!isPreviewInput(value)) {
2342
+ return false;
2343
+ }
2344
+ target[p] = value;
2345
+ eventHandlers[p]?.(value);
2346
+ return true;
2347
+ }
2348
+ case "updatingMain":
2349
+ case "updatingPreview": {
2350
+ if (typeof value !== "boolean") {
2351
+ return false;
2352
+ }
2353
+ target[p] = value;
2354
+ eventHandlers[p]?.(value);
2355
+ return true;
2356
+ }
2357
+ default:
2358
+ Reflect.set(target, p, value);
2359
+ return true;
2360
+ }
2361
+ }
2362
+ });
2363
+ const postMediaPipeline = createMediaPipeline(
2364
+ // Ignore the type conversion between tuple and array
2365
+ processors
2366
+ );
2367
+ const initFromMain = (mainMedia2) => {
2368
+ if (!mainMedia2.stream) {
2369
+ return;
2370
+ }
2371
+ postMediaPipeline.execute(cloneMedia(mainMedia2)).then((media) => {
2372
+ props.media = media;
2373
+ }).catch((error) => {
2374
+ logger2.error({ error }, "Failed to post process media");
2375
+ }).finally(() => {
2376
+ props.initialized = true;
2377
+ if (props.discardMedia) {
2378
+ void props.media.release();
2379
+ }
2380
+ });
2381
+ props.audioInput = mainMedia2.audioInput;
2382
+ props.videoInput = mainMedia2.videoInput;
2383
+ props.originalMainAudioInput = mainMedia2.audioInput;
2384
+ };
2385
+ const mainMedia = getCurrentMedia();
2386
+ if (mainMedia?.stream) {
2387
+ initFromMain(mainMedia);
2388
+ } else {
2389
+ eventHandlers.unsubscribeMain = mediaSignal.add(initFromMain);
2390
+ }
2391
+ const replaceMainStream = async (constraints) => {
2392
+ logger2.debug({ constraints }, "Replacing main stream");
2393
+ props.updatingMain = true;
2394
+ await updateMainStream(constraints);
2395
+ props.updatingMain = false;
2396
+ };
2397
+ const mediaPipeline = createMediaPipeline(() => [
2398
+ createGetUserMediaProcess(getUM, getCurrentDevices, {
2399
+ initialMedia: props.media,
2400
+ scope: "PreviewStreamController"
2401
+ }),
2402
+ ...processors
2403
+ ]);
2404
+ const updatePreviewMedia = async (constraints) => {
2405
+ logger2.debug({ constraints }, "Requesting a new preview stream");
2406
+ await props.media?.release();
2407
+ const media = await mediaPipeline.execute(constraints);
2408
+ if (props.discardMedia) {
2409
+ logger2.debug("Discard media");
2410
+ return await media.release();
2411
+ }
2412
+ props.media = media;
2413
+ };
2414
+ const updateAudioInput = async (input) => {
2415
+ props.audioInput = input;
2416
+ const request = {
2417
+ audio: { device: { exact: input } },
2418
+ video: props.videoInput
2419
+ };
2420
+ try {
2421
+ props.updatingPreview = true;
2422
+ await updatePreviewMedia(request);
2423
+ } catch (error) {
2424
+ if (error instanceof Error) {
2425
+ const errorName = error.name === "Error" ? error.message : error.name;
2426
+ if (errorName === "NotReadableError") {
2427
+ try {
2428
+ logger2.debug(
2429
+ { input },
2430
+ "NotReadableError, trying to sync main stream AudioInput"
2431
+ );
2432
+ await replaceMainStream({
2433
+ audio: input,
2434
+ video: getCurrentMedia()?.videoInput
2435
+ });
2436
+ await updatePreviewMedia(request);
2437
+ } catch (err) {
2438
+ logger2.warn(
2439
+ { error: err, input },
2440
+ "Unable to recover NotReadableError AudioInput for preview"
2441
+ );
2442
+ if (err instanceof Error) {
2443
+ return eventHandlers.audioInputError?.(err);
2444
+ }
2445
+ throw err;
2446
+ }
2447
+ } else {
2448
+ logger2.warn(
2449
+ { error, input },
2450
+ "Unable to update AudioInput for preview"
2451
+ );
2452
+ eventHandlers.audioInputError?.(error);
2453
+ }
2454
+ } else {
2455
+ logger2.error(
2456
+ { error, input },
2457
+ "Unable to update AudioInput for preview"
2458
+ );
2459
+ throw error;
2460
+ }
2461
+ } finally {
2462
+ props.updatingPreview = false;
2463
+ }
2464
+ };
2465
+ const updateVideoInput = async (input) => {
2466
+ props.videoInput = input;
2467
+ try {
2468
+ props.updatingPreview = true;
2469
+ await updatePreviewMedia({
2470
+ audio: props.audioInput,
2471
+ video: { device: { exact: input } }
2472
+ });
2473
+ } catch (error) {
2474
+ if (error instanceof Error) {
2475
+ logger2.warn(
2476
+ { error, input },
2477
+ "Unable to update VideoInput for preview"
2478
+ );
2479
+ return eventHandlers.videoInputError?.(error);
2480
+ }
2481
+ throw error;
2482
+ } finally {
2483
+ props.updatingPreview = false;
2484
+ }
2485
+ };
2486
+ const cleanup = () => {
2487
+ logger2.debug("Cleanup preview controller");
2488
+ props.audioInput = void 0;
2489
+ props.videoInput = void 0;
2490
+ onEnded?.();
2491
+ };
2492
+ const hasChangedAudioInput = () => hasChangedInput(props.originalMainAudioInput, props.audioInput);
2493
+ const hasChangedVideoInput = () => hasChangedInput(getCurrentMedia()?.videoInput, props.videoInput);
2494
+ const hasAudioVideoProcessingSettingsChanged = createAudioVideoProcessingSettingsChangeDetector(
2495
+ getCurrentMedia,
2496
+ () => props.media
2497
+ );
2498
+ const hasChanges = () => hasChangedAudioInput() || hasChangedVideoInput() || hasAudioVideoProcessingSettingsChanged();
2499
+ const applyChanges = async () => {
2500
+ const { audioInput, videoInput } = props;
2501
+ const {
2502
+ audio: [previewAudio],
2503
+ video: [previewVideo]
2504
+ } = props.media.getSettings();
2505
+ await props.media.release();
2506
+ cleanUpMainSubscription();
2507
+ if (hasChanges()) {
2508
+ const audioSettings = extractFeaturesToConstraints(
2509
+ AUDIO_SETTINGS_KEYS,
2510
+ previewAudio
2511
+ );
2512
+ const videoSettings = extractFeaturesToConstraints(
2513
+ VIDEO_SETTINGS_KEYS,
2514
+ previewVideo
2515
+ );
2516
+ logger2.info(
2517
+ { audioInput, videoInput, audioSettings, videoSettings },
2518
+ "Apply changes to main"
2519
+ );
2520
+ try {
2521
+ await replaceMainStream({
2522
+ audio: { device: audioInput, ...audioSettings },
2523
+ video: { device: videoInput, ...videoSettings }
2524
+ });
2525
+ props.originalMainAudioInput = props.audioInput;
2526
+ } catch (error) {
2527
+ if (error instanceof Error) {
2528
+ logger2.warn({ error }, "Unable to apply changes to main");
2529
+ return eventHandlers.applyChangesError?.(error);
2530
+ }
2531
+ throw error;
2532
+ }
2533
+ }
2534
+ cleanup();
2535
+ };
2536
+ const revertChanges = async () => {
2537
+ await props.media.release();
2538
+ cleanUpMainSubscription();
2539
+ const mainMedia2 = getCurrentMedia();
2540
+ const mainStream = mainMedia2?.stream;
2541
+ if (mainMedia2 && mainStream) {
2542
+ const mainMuted = !!mainStream.getTracks().some((t) => t.muted);
2543
+ const current = mainMedia2.audioInput;
2544
+ const prev = props.originalMainAudioInput;
2545
+ const audioChanged = hasChangedInput(prev, current);
2546
+ if (mainMuted || audioChanged) {
2547
+ logger2.info(
2548
+ {
2549
+ mainMuted,
2550
+ audioChanged,
2551
+ currentAudioInput: current,
2552
+ prevAudioInput: prev,
2553
+ mainMedia: mainMedia2
2554
+ },
2555
+ "Reverting Changes to main"
2556
+ );
2557
+ try {
2558
+ await replaceMainStream({
2559
+ audio: prev,
2560
+ video: getCurrentMedia()?.videoInput
2561
+ });
2562
+ } catch (error) {
2563
+ if (error instanceof Error) {
2564
+ logger2.warn(
2565
+ { error },
2566
+ "Unable to revert changes to main"
2567
+ );
2568
+ return eventHandlers.revertChangesError?.(error);
2569
+ }
2570
+ throw error;
2571
+ }
2572
+ }
2573
+ }
2574
+ cleanup();
2575
+ };
2576
+ const updateInput = (hasChanged, updater) => (input) => {
2577
+ if (hasChanged(input)) {
2578
+ queue.enqueue(async () => {
2579
+ await updater(input);
2580
+ });
2581
+ }
2582
+ };
2583
+ const toEvenHandler = createEventHandler(eventHandlers);
2584
+ return {
2585
+ get media() {
2586
+ return props.media;
2587
+ },
2588
+ get audioInputChanged() {
2589
+ return hasChangedAudioInput();
2590
+ },
2591
+ get videoInputChanged() {
2592
+ return hasChangedVideoInput();
2593
+ },
2594
+ get inputChanged() {
2595
+ return hasChanges();
2596
+ },
2597
+ get audioInput() {
2598
+ return props.audioInput;
2599
+ },
2600
+ get videoInput() {
2601
+ return props.videoInput;
2602
+ },
2603
+ get updatingPreview() {
2604
+ return props.updatingPreview;
2605
+ },
2606
+ get updatingMain() {
2607
+ return props.updatingMain;
2608
+ },
2609
+ updateAudioInput: updateInput(
2610
+ (input) => props.initialized && hasChangedInput(props.audioInput, input),
2611
+ updateAudioInput
2612
+ ),
2613
+ updateVideoInput: updateInput(
2614
+ (input) => props.initialized && hasChangedInput(props.videoInput, input),
2615
+ updateVideoInput
2616
+ ),
2617
+ onMediaChanged: toEvenHandler("media"),
2618
+ onAudioInputChanged: toEvenHandler("audioInput"),
2619
+ onVideoInputChanged: toEvenHandler("videoInput"),
2620
+ onAudioInputError: toEvenHandler("audioInputError"),
2621
+ onVideoInputError: toEvenHandler("videoInputError"),
2622
+ onApplyChangesError: toEvenHandler("applyChangesError"),
2623
+ onRevertChangesError: toEvenHandler("revertChangesError"),
2624
+ onUpdatingPreview: toEvenHandler("updatingPreview"),
2625
+ onUpdatingMain: toEvenHandler("updatingMain"),
2626
+ applyChanges,
2627
+ revertChanges
2628
+ };
2629
+ };
2630
+
2631
+ // src/signals.ts
2632
+ import { createSignal } from "@pexip/signal";
2633
+ var createMediaSignal = (name, crucial = true, variant = "generic") => createSignal({
2634
+ name: `media/${name}`,
2635
+ allowEmittingWithoutObserver: !crucial,
2636
+ variant
2637
+ });
2638
+ var REQUIRED_SIGNAL_KEYS = [
2639
+ "onMediaChanged",
2640
+ "onVAD",
2641
+ "onSilentDetected"
2642
+ ];
2643
+ var createMediaSignals = (more, scope = "") => {
2644
+ const signalScope = scope && [scope, ":"].join("");
2645
+ return [...REQUIRED_SIGNAL_KEYS, ...more].reduce(
2646
+ (signals, key) => ({
2647
+ ...signals,
2648
+ [key]: createMediaSignal(`${signalScope}${key}`)
2649
+ }),
2650
+ {}
2651
+ );
2652
+ };
2653
+ export {
2654
+ DeniedDevices,
2655
+ REQUIRED_SIGNAL_KEYS,
2656
+ UserMediaStatus,
2657
+ areBothGranted,
2658
+ createAudioMixingProcess,
2659
+ createAudioStreamProcess,
2660
+ createMedia,
2661
+ createMediaSignal,
2662
+ createMediaSignals,
2663
+ createPreviewStreamController,
2664
+ createVideoStreamProcess,
2665
+ deriveInitialPermissionStatus,
2666
+ getPermissionStatus,
2667
+ hasNoAudioDevices,
2668
+ hasNoDevice,
2669
+ hasNoVideoDevices,
2670
+ isAudioDeviceInUse,
2671
+ isDeviceInUse,
2672
+ isFallback,
2673
+ isFallbackAudio,
2674
+ isFallbackVideo,
2675
+ isGranted,
2676
+ isGrantedAudio,
2677
+ isGrantedOnlyAudio,
2678
+ isGrantedOnlyAudioNoVideoDevices,
2679
+ isGrantedOnlyVideo,
2680
+ isGrantedOnlyVideoNoAudioDevices,
2681
+ isGrantedVideo,
2682
+ isInitial,
2683
+ isInitialPermissions,
2684
+ isInitialPermissionsGranted,
2685
+ isInitialPermissionsNotGranted,
2686
+ isOnlyAudioError,
2687
+ isOnlyVideoError,
2688
+ isOverConstrained,
2689
+ isPromptAudio,
2690
+ isPromptVideo,
2691
+ isRejected,
2692
+ isRejectedOnlyAudio,
2693
+ isRejectedOnlyVideo,
2694
+ isUnknownError,
2695
+ isVideoDeviceInUse,
2696
+ setLogger,
2697
+ toDeniedDevices
2698
+ };