@pexip/media 17.3.0 → 18.0.0

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