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