@pexip/media-control 17.3.0 → 17.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/baseLogger.d.ts +37 -0
- package/dist/baseLogger.js +29 -0
- package/dist/constants.d.ts +43 -0
- package/dist/constants.js +26 -0
- package/dist/constraints.d.ts +174 -0
- package/dist/constraints.js +653 -0
- package/dist/devices.d.ts +416 -0
- package/dist/devices.js +766 -0
- package/dist/displayMedia.d.ts +10 -0
- package/dist/displayMedia.js +31 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +105 -0
- package/dist/eventEmitter.d.ts +119 -0
- package/dist/eventEmitter.js +81 -0
- package/dist/getMediaStream.d.ts +11 -0
- package/dist/getMediaStream.js +41 -0
- package/dist/index.d.ts +18 -985
- package/dist/index.js +105 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +5 -0
- package/dist/streamTrackMap.d.ts +9 -0
- package/dist/streamTrackMap.js +79 -0
- package/dist/streams.d.ts +26 -0
- package/dist/streams.js +99 -0
- package/dist/typeGuards.d.ts +89 -0
- package/dist/typeGuards.js +185 -0
- package/dist/types.d.ts +265 -0
- package/dist/types.js +88 -0
- package/dist/utils.d.ts +37 -0
- package/dist/utils.js +47 -0
- package/package.json +5 -5
- package/dist/index.mjs +0 -1706
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import { isBoolean, isConstrainBooleanKeys, isConstrainBooleanParameters, isConstrainDOMStringParameters, isConstrainDoubleKeys, isConstrainDoubleRange, isConstrainRange, isConstrainStringKeys, isConstrainULongKeys, isConstrainULongRange, isConstraintDOMString, isConstraintDeviceParameters, isConstraintSetDevice, isDeviceConstraint, isExtendedConstrainBooleanKeys, isExtendedConstrainDoubleKeys, isExtendedConstrainStringKeys, isExtendedConstrainULongKeys, isFacingMode, isFloat, isInputConstraintSet, isInteger, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaTrackConstraintSetKey, isMediaTrackConstraints, isNumber, isUndefined, } from './typeGuards';
|
|
2
|
+
import { logger } from './logger';
|
|
3
|
+
import { findDevice } from './utils';
|
|
4
|
+
/**
|
|
5
|
+
* Check if provided constraint is an `exact` device constraint
|
|
6
|
+
*/
|
|
7
|
+
export const isExactDeviceConstraint = (constraint) => {
|
|
8
|
+
if (isInputConstraintSet(constraint)) {
|
|
9
|
+
return ((!!constraint.device &&
|
|
10
|
+
typeof constraint.device === 'object' &&
|
|
11
|
+
'exact' in constraint.device) ||
|
|
12
|
+
(!!constraint.deviceId &&
|
|
13
|
+
typeof constraint.deviceId === 'object' &&
|
|
14
|
+
'exact' in constraint.deviceId));
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
18
|
+
const getConstraintsSetFilter = (supportedConstraints) => (trackConstraints) => {
|
|
19
|
+
if (!isMediaTrackConstraints(trackConstraints)) {
|
|
20
|
+
return trackConstraints;
|
|
21
|
+
}
|
|
22
|
+
const constraints = Object.entries(trackConstraints).reduce((acc, [key, val]) => key === 'advanced' ||
|
|
23
|
+
supportedConstraints.has(key)
|
|
24
|
+
? { ...acc, [key]: val }
|
|
25
|
+
: acc, {});
|
|
26
|
+
// If we don't have any valid constraints it seems sensible to fallback to a boolean constraint -ea
|
|
27
|
+
if (Object.keys(constraints).length === 0) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return constraints;
|
|
31
|
+
};
|
|
32
|
+
const getDefinedOnly = (obj) => {
|
|
33
|
+
return Object.entries(obj).reduce((acc, [key, val]) => typeof val === 'undefined' ? acc : { ...acc, [key]: val }, {});
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Use navigator.mediaDevices.getSupportedConstraints to remove constraints not supported by agent.
|
|
37
|
+
* Creates a copy of MediaStreamConstraints with only supported properties.
|
|
38
|
+
*/
|
|
39
|
+
export const removeUnsupportedConstraints = (constraints) => {
|
|
40
|
+
if (!(navigator && 'getSupportedConstraints' in navigator.mediaDevices)) {
|
|
41
|
+
return constraints;
|
|
42
|
+
}
|
|
43
|
+
const supportedConstraints = Object.entries(navigator.mediaDevices.getSupportedConstraints());
|
|
44
|
+
logger.debug({ supportedConstraints }, 'Supported constraints');
|
|
45
|
+
const constraintsSetFilter = getConstraintsSetFilter(supportedConstraints.reduce((set, [key, value]) =>
|
|
46
|
+
// FIXME: Cast inserted to unblock typescript upgrade, will have to be verified and properly fixed.
|
|
47
|
+
value && isMediaTrackConstraintSetKey(key)
|
|
48
|
+
? set.add(key)
|
|
49
|
+
: set, new Set()));
|
|
50
|
+
return getDefinedOnly({
|
|
51
|
+
audio: constraintsSetFilter(constraints.audio),
|
|
52
|
+
peerIdentity: constraints.peerIdentity,
|
|
53
|
+
video: constraintsSetFilter(constraints.video),
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Merge base constraints with provided base constraints and another constraints
|
|
58
|
+
*/
|
|
59
|
+
export const mergeConstraints = (baseConstraints) => (constraints) => {
|
|
60
|
+
if (!isInputConstraintSet(baseConstraints)) {
|
|
61
|
+
if (isDeviceConstraint(constraints)) {
|
|
62
|
+
return { device: constraints };
|
|
63
|
+
}
|
|
64
|
+
return constraints ?? false;
|
|
65
|
+
}
|
|
66
|
+
if (!constraints) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (isDeviceConstraint(constraints)) {
|
|
70
|
+
const result = {
|
|
71
|
+
...baseConstraints,
|
|
72
|
+
device: constraints,
|
|
73
|
+
};
|
|
74
|
+
delete result.facingMode;
|
|
75
|
+
delete result.deviceId;
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
if (isInputConstraintSet(constraints)) {
|
|
79
|
+
const combined = { ...baseConstraints, ...constraints };
|
|
80
|
+
const hasDeviceId = isConstraintDOMString(combined.deviceId) ||
|
|
81
|
+
isConstrainDOMStringParameters(combined.deviceId);
|
|
82
|
+
const hasDevice = isDeviceConstraint(combined.device) ||
|
|
83
|
+
isConstraintDeviceParameters(combined.device);
|
|
84
|
+
if (hasDeviceId && hasDevice) {
|
|
85
|
+
const result = {
|
|
86
|
+
...combined,
|
|
87
|
+
};
|
|
88
|
+
delete result.facingMode;
|
|
89
|
+
delete result.deviceId;
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
if (hasDevice || hasDeviceId) {
|
|
93
|
+
delete combined.facingMode;
|
|
94
|
+
return combined;
|
|
95
|
+
}
|
|
96
|
+
return combined;
|
|
97
|
+
}
|
|
98
|
+
return baseConstraints;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Normalize Device
|
|
102
|
+
* Convert any valid `MediaDeviceInfoLike` into `MediaDeviceInfoLike[]`
|
|
103
|
+
*
|
|
104
|
+
* Check test cases for the details
|
|
105
|
+
*/
|
|
106
|
+
export const normalizeDevice = (device) => {
|
|
107
|
+
if (!device) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
if (isMediaDeviceInfo(device)) {
|
|
111
|
+
return [device];
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(device)) {
|
|
114
|
+
const devices = device.filter(isMediaDeviceInfo);
|
|
115
|
+
if (devices.length) {
|
|
116
|
+
return devices;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Normalize Device Constraint
|
|
123
|
+
* Convert Device Constraints into a form of `ConstraintDeviceParameters`
|
|
124
|
+
* e.g.
|
|
125
|
+
* `{ideal?: MediaDeviceInfoLike[], exact?: MediaDeviceInfoLike[]}`
|
|
126
|
+
*
|
|
127
|
+
* Check test cases for the details
|
|
128
|
+
*
|
|
129
|
+
* @returns
|
|
130
|
+
* `undefined` if nothing is meaningful for the constraint, otherwise,
|
|
131
|
+
* a normalized form of `ConstraintDeviceParameters`
|
|
132
|
+
*/
|
|
133
|
+
export const normalizeDeviceConstraint = (constraints) => {
|
|
134
|
+
if (!constraints) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
if (isConstraintDeviceParameters(constraints)) {
|
|
138
|
+
return Object.keys(constraints)
|
|
139
|
+
.filter(k => ['ideal', 'exact'].includes(k))
|
|
140
|
+
.reduce((cs, k) => {
|
|
141
|
+
const key = k;
|
|
142
|
+
const value = constraints[key];
|
|
143
|
+
const devices = normalizeDevice(value);
|
|
144
|
+
if (devices) {
|
|
145
|
+
return { ...(cs ?? {}), [key]: devices };
|
|
146
|
+
}
|
|
147
|
+
return cs;
|
|
148
|
+
}, undefined);
|
|
149
|
+
}
|
|
150
|
+
const normalized = normalizeDevice(constraints);
|
|
151
|
+
return normalized && { ideal: normalized };
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Convert `InputConstraintSet['device']` into
|
|
155
|
+
* `MediaTrackConstraintSet['deviceId']` in a normalized form
|
|
156
|
+
*/
|
|
157
|
+
export const toDeviceIdConstraintSet = (constraint) => {
|
|
158
|
+
const [devices, param] = extractConstrainDevice({ device: constraint });
|
|
159
|
+
return devices && { [param]: devices.map(device => device.deviceId) };
|
|
160
|
+
};
|
|
161
|
+
export const toArray = (t) => {
|
|
162
|
+
const r = Array.isArray(t) ? t : [t];
|
|
163
|
+
return r.filter(Boolean);
|
|
164
|
+
};
|
|
165
|
+
export const NONE = [undefined, 'ideal'];
|
|
166
|
+
export const extractConstrainString = (key) => (constraints) => {
|
|
167
|
+
const constraint = constraints[key];
|
|
168
|
+
if (!constraint) {
|
|
169
|
+
return NONE;
|
|
170
|
+
}
|
|
171
|
+
if (isConstraintDOMString(constraint)) {
|
|
172
|
+
return [toArray(constraint), 'ideal'];
|
|
173
|
+
}
|
|
174
|
+
if (isConstrainDOMStringParameters(constraint)) {
|
|
175
|
+
// `exact` takes priority
|
|
176
|
+
if (constraint.exact) {
|
|
177
|
+
const normalized = toArray(constraint.exact);
|
|
178
|
+
if (normalized.length) {
|
|
179
|
+
return [normalized, 'exact'];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (constraint.ideal) {
|
|
183
|
+
return [toArray(constraint.ideal), 'ideal'];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return NONE;
|
|
187
|
+
};
|
|
188
|
+
export const extractConstrainBoolean = (key) => (constraints) => {
|
|
189
|
+
const constraint = constraints[key];
|
|
190
|
+
if (isUndefined(constraint)) {
|
|
191
|
+
return NONE;
|
|
192
|
+
}
|
|
193
|
+
if (isBoolean(constraint)) {
|
|
194
|
+
return [constraint, 'ideal'];
|
|
195
|
+
}
|
|
196
|
+
if (isConstrainBooleanParameters(constraint)) {
|
|
197
|
+
const { ideal, exact } = constraint;
|
|
198
|
+
// `exact` takes priority
|
|
199
|
+
if (isBoolean(exact)) {
|
|
200
|
+
return [exact, 'exact'];
|
|
201
|
+
}
|
|
202
|
+
if (isBoolean(ideal)) {
|
|
203
|
+
return [ideal, 'ideal'];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return NONE;
|
|
207
|
+
};
|
|
208
|
+
export const extractConstrainNumber = (key) => (constraints) => {
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- @typescript-eslint and Typescript 5 issue.
|
|
210
|
+
const constraint = constraints[key];
|
|
211
|
+
if (constraint === undefined || constraint === null) {
|
|
212
|
+
return NONE;
|
|
213
|
+
}
|
|
214
|
+
if (isFloat(constraint) || isInteger(constraint)) {
|
|
215
|
+
return [constraint, 'ideal'];
|
|
216
|
+
}
|
|
217
|
+
if (isConstrainULongRange(constraint) ||
|
|
218
|
+
isConstrainDoubleRange(constraint)) {
|
|
219
|
+
const { exact, ideal, min, max } = constraint;
|
|
220
|
+
// all 'min', 'max', and 'exact' constraints in the basic Constraint
|
|
221
|
+
// structure are together treated as the required constraints
|
|
222
|
+
if (isFloat(min) ||
|
|
223
|
+
isFloat(max) ||
|
|
224
|
+
isInteger(min) ||
|
|
225
|
+
isInteger(max)) {
|
|
226
|
+
return [{ min, max, ideal, exact }, 'min-max'];
|
|
227
|
+
}
|
|
228
|
+
if (isFloat(exact) || isInteger(exact)) {
|
|
229
|
+
return [exact, 'exact'];
|
|
230
|
+
}
|
|
231
|
+
if (isFloat(ideal) || isInteger(ideal)) {
|
|
232
|
+
return [ideal, 'ideal'];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return NONE;
|
|
236
|
+
};
|
|
237
|
+
export const extractConstrainDevice = (constraints) => {
|
|
238
|
+
if (!constraints ||
|
|
239
|
+
isBoolean(constraints) ||
|
|
240
|
+
(Array.isArray(constraints) && !constraints.length)) {
|
|
241
|
+
return NONE;
|
|
242
|
+
}
|
|
243
|
+
const { device } = isInputConstraintSet(constraints)
|
|
244
|
+
? constraints
|
|
245
|
+
: { device: constraints };
|
|
246
|
+
if (isMediaDeviceInfo(device) || isMediaDeviceInfoArray(device)) {
|
|
247
|
+
const normalized = normalizeDevice(device);
|
|
248
|
+
if (normalized) {
|
|
249
|
+
return [normalized, 'ideal'];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (isConstraintDeviceParameters(device)) {
|
|
253
|
+
const { exact, ideal } = normalizeDeviceConstraint(device) ?? {};
|
|
254
|
+
if (exact) {
|
|
255
|
+
return [exact, 'exact'];
|
|
256
|
+
}
|
|
257
|
+
if (ideal) {
|
|
258
|
+
return [ideal, 'ideal'];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return NONE;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Compare float point number a and b to see if they are closed to be considered
|
|
265
|
+
* as having the same value
|
|
266
|
+
*
|
|
267
|
+
* @param a - Floating point number a
|
|
268
|
+
* @param b - Floating point number b
|
|
269
|
+
* @param numDigits - The number of digits to check after the decimal point @defaultValue 5 digits
|
|
270
|
+
*/
|
|
271
|
+
export const closedTo = (a, b, numDigits = 5) => {
|
|
272
|
+
const multiplier = Math.pow(10, numDigits);
|
|
273
|
+
return Math.round(a * multiplier) === Math.round(b * multiplier);
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Check if provided num is between min (inclusive) and max (inclusive)
|
|
277
|
+
*
|
|
278
|
+
* @param min - Lower boundary of the checking
|
|
279
|
+
* @param num - The number used for the checking
|
|
280
|
+
* @param max - Upper boundary of the checking
|
|
281
|
+
*/
|
|
282
|
+
export const between = (min, num, max) => {
|
|
283
|
+
if (!isUndefined(min)) {
|
|
284
|
+
if (!isUndefined(max)) {
|
|
285
|
+
return num >= min && num <= max;
|
|
286
|
+
}
|
|
287
|
+
return num >= min;
|
|
288
|
+
}
|
|
289
|
+
if (!isUndefined(max)) {
|
|
290
|
+
return num <= max;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
};
|
|
294
|
+
export const getValueFromConstrainNumber = (constraint) => {
|
|
295
|
+
if (isNumber(constraint)) {
|
|
296
|
+
return constraint;
|
|
297
|
+
}
|
|
298
|
+
const { min, max, ideal, exact } = constraint;
|
|
299
|
+
if (exact !== undefined) {
|
|
300
|
+
const withinRange = between(min, exact, max);
|
|
301
|
+
if (withinRange) {
|
|
302
|
+
return exact;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (ideal !== undefined) {
|
|
306
|
+
const withinRange = between(min, ideal, max);
|
|
307
|
+
if (withinRange) {
|
|
308
|
+
return ideal;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const value = max ?? min;
|
|
312
|
+
if (value !== undefined) {
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
throw Error('Constrain Number is undefined');
|
|
316
|
+
};
|
|
317
|
+
export const getFacingModeFromConstraintString = (constraint) => {
|
|
318
|
+
if (isFacingMode(constraint)) {
|
|
319
|
+
return constraint;
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Compare the provided constraint and num and see if the num satisfy the
|
|
325
|
+
* constraint
|
|
326
|
+
*
|
|
327
|
+
* @param constraint - The constraint to be used
|
|
328
|
+
* @param num - The num to be used to check
|
|
329
|
+
*
|
|
330
|
+
* @returns `true` means satisfy otherwise `false`
|
|
331
|
+
*/
|
|
332
|
+
export const satisfyConstrainNumber = (constraint, num) => {
|
|
333
|
+
if (isUndefined(constraint) || isUndefined(num)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
if (isInteger(constraint)) {
|
|
337
|
+
return num === constraint;
|
|
338
|
+
}
|
|
339
|
+
if (isFloat(constraint)) {
|
|
340
|
+
return closedTo(constraint, num);
|
|
341
|
+
}
|
|
342
|
+
if (isConstrainRange(constraint)) {
|
|
343
|
+
const { min, max, ideal, exact } = constraint;
|
|
344
|
+
const withinRange = between(min, num, max);
|
|
345
|
+
if (isFloat(exact)) {
|
|
346
|
+
return withinRange && closedTo(exact, num);
|
|
347
|
+
}
|
|
348
|
+
if (isInteger(exact)) {
|
|
349
|
+
return withinRange && exact === num;
|
|
350
|
+
}
|
|
351
|
+
if (isFloat(ideal)) {
|
|
352
|
+
return withinRange && closedTo(ideal, num);
|
|
353
|
+
}
|
|
354
|
+
if (isInteger(ideal)) {
|
|
355
|
+
return withinRange && ideal === num;
|
|
356
|
+
}
|
|
357
|
+
return withinRange;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* Resolve *constraints* and mediaTrackConstraints by checking the type and
|
|
363
|
+
* try to return the best possible from mediaTrackConstraints.
|
|
364
|
+
*
|
|
365
|
+
* @internal
|
|
366
|
+
*/
|
|
367
|
+
export const resolveMediaDeviceConstraints = (constraints, base) => {
|
|
368
|
+
const merge = mergeConstraints(base);
|
|
369
|
+
// Handle InputConstraintSet
|
|
370
|
+
if (isInputConstraintSet(constraints)) {
|
|
371
|
+
if (isConstraintSetDevice(constraints.device)) {
|
|
372
|
+
return merge(Object.keys(constraints).reduce((cs, k) => {
|
|
373
|
+
const key = k;
|
|
374
|
+
if (key === 'device') {
|
|
375
|
+
return {
|
|
376
|
+
...cs,
|
|
377
|
+
deviceId: toDeviceIdConstraintSet(constraints.device),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return { ...cs, [key]: constraints[key] };
|
|
381
|
+
}, {}));
|
|
382
|
+
}
|
|
383
|
+
return merge(constraints);
|
|
384
|
+
}
|
|
385
|
+
if (isConstraintSetDevice(constraints)) {
|
|
386
|
+
const deviceId = toDeviceIdConstraintSet(constraints);
|
|
387
|
+
return merge({ deviceId });
|
|
388
|
+
}
|
|
389
|
+
if (constraints && isMediaTrackConstraints(base)) {
|
|
390
|
+
return base;
|
|
391
|
+
}
|
|
392
|
+
return !!constraints;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* @remarks
|
|
396
|
+
* Constraint multiple (exact) devices is not supported
|
|
397
|
+
*
|
|
398
|
+
* @internal
|
|
399
|
+
*/
|
|
400
|
+
export const getMediaConstraints = ({ audio, video, defaultConstraints, }) => {
|
|
401
|
+
return removeUnsupportedConstraints({
|
|
402
|
+
audio: resolveMediaDeviceConstraints(audio, defaultConstraints.audio),
|
|
403
|
+
video: resolveMediaDeviceConstraints(video, defaultConstraints.video),
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
407
|
+
* Call applyConstraints foreach track accordingly
|
|
408
|
+
*
|
|
409
|
+
* @param tracks - Tracks to be applied
|
|
410
|
+
* @param constraints - constraints to be applied
|
|
411
|
+
*/
|
|
412
|
+
export const applyConstraints = async (tracks, constraints) => {
|
|
413
|
+
if (!tracks ||
|
|
414
|
+
tracks.length === 0 ||
|
|
415
|
+
Object.keys(constraints).length === 0) {
|
|
416
|
+
return Promise.resolve();
|
|
417
|
+
}
|
|
418
|
+
const { audio, video } = removeUnsupportedConstraints({
|
|
419
|
+
audio: resolveMediaDeviceConstraints(constraints.audio),
|
|
420
|
+
video: resolveMediaDeviceConstraints(constraints.video),
|
|
421
|
+
});
|
|
422
|
+
await Promise.all(tracks.flatMap(track => {
|
|
423
|
+
if (track.kind === 'audio' && isMediaTrackConstraints(audio)) {
|
|
424
|
+
return [track.applyConstraints(audio)];
|
|
425
|
+
}
|
|
426
|
+
if (track.kind === 'video' && isMediaTrackConstraints(video)) {
|
|
427
|
+
return [track.applyConstraints(video)];
|
|
428
|
+
}
|
|
429
|
+
return [];
|
|
430
|
+
}));
|
|
431
|
+
};
|
|
432
|
+
export const findDeviceFrom = (devicesToFind, deviceList) => {
|
|
433
|
+
const devicesFound = devicesToFind.flatMap(device => {
|
|
434
|
+
const found = findDevice(device)(deviceList);
|
|
435
|
+
if (found) {
|
|
436
|
+
return [found];
|
|
437
|
+
}
|
|
438
|
+
return [];
|
|
439
|
+
});
|
|
440
|
+
if (devicesFound.length) {
|
|
441
|
+
return devicesFound;
|
|
442
|
+
}
|
|
443
|
+
return undefined;
|
|
444
|
+
};
|
|
445
|
+
export const findDeviceFromDeviceConstraints = (device, devices) => {
|
|
446
|
+
const normalized = normalizeDeviceConstraint(device);
|
|
447
|
+
if (normalized) {
|
|
448
|
+
const { ideal, exact } = normalized;
|
|
449
|
+
if (exact) {
|
|
450
|
+
return findDeviceFrom(exact, devices);
|
|
451
|
+
}
|
|
452
|
+
if (ideal) {
|
|
453
|
+
return findDeviceFrom(ideal, devices);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
export const relaxDevice = (device, devices) => {
|
|
458
|
+
const found = findDeviceFromDeviceConstraints(device, devices);
|
|
459
|
+
if (found) {
|
|
460
|
+
return found;
|
|
461
|
+
}
|
|
462
|
+
// Return the original with normalized form
|
|
463
|
+
return normalizeDevice(device);
|
|
464
|
+
};
|
|
465
|
+
export const resolveOnlyDevice = (devices) => {
|
|
466
|
+
switch (devices.length) {
|
|
467
|
+
case 1:
|
|
468
|
+
return devices[0];
|
|
469
|
+
case 0:
|
|
470
|
+
return undefined;
|
|
471
|
+
default:
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
export function extractConstraints(key) {
|
|
476
|
+
return constraints => {
|
|
477
|
+
if (!constraints ||
|
|
478
|
+
typeof constraints === 'boolean' ||
|
|
479
|
+
(Array.isArray(constraints) && !constraints.length)) {
|
|
480
|
+
return NONE;
|
|
481
|
+
}
|
|
482
|
+
if (key === 'device' &&
|
|
483
|
+
(isMediaDeviceInfo(constraints) ||
|
|
484
|
+
isMediaDeviceInfoArray(constraints))) {
|
|
485
|
+
return extractConstrainDevice(constraints);
|
|
486
|
+
}
|
|
487
|
+
if (isInputConstraintSet(constraints)) {
|
|
488
|
+
if (key === 'device') {
|
|
489
|
+
return extractConstrainDevice(constraints);
|
|
490
|
+
}
|
|
491
|
+
if (isConstrainStringKeys(key) ||
|
|
492
|
+
isExtendedConstrainStringKeys(key)) {
|
|
493
|
+
return extractConstrainString(key)(constraints);
|
|
494
|
+
}
|
|
495
|
+
if (isConstrainDoubleKeys(key) ||
|
|
496
|
+
isConstrainULongKeys(key) ||
|
|
497
|
+
isExtendedConstrainULongKeys(key) ||
|
|
498
|
+
isExtendedConstrainDoubleKeys(key)) {
|
|
499
|
+
return extractConstrainNumber(key)(constraints);
|
|
500
|
+
}
|
|
501
|
+
if (isConstrainBooleanKeys(key) ||
|
|
502
|
+
isExtendedConstrainBooleanKeys(key)) {
|
|
503
|
+
return extractConstrainBoolean(key)(constraints);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return [undefined, 'ideal'];
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Extract the constraints with provided keys
|
|
511
|
+
*
|
|
512
|
+
* @param keys - The keys to be used for the extraction
|
|
513
|
+
* @param constraints - The constraints to be used for the extraction
|
|
514
|
+
*
|
|
515
|
+
* @returns an object with the provided key and the value-param tuple
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
*
|
|
519
|
+
* ```typescript
|
|
520
|
+
* const device = {deviceId: 'xxxx', label: 'abc', kind: 'audioinput'};
|
|
521
|
+
* const constraints = { device, noiseSuppression: true };
|
|
522
|
+
* const extract = extractConstraintsWithKeys(['device', 'noiseSuppression']);
|
|
523
|
+
* const {
|
|
524
|
+
* device: [devices, deviceParam],
|
|
525
|
+
* noiseSuppression: [noiseSuppression, noiseSuppressionParam],
|
|
526
|
+
* } = extract(constraints);
|
|
527
|
+
* expect(devices).toEqual([device]);
|
|
528
|
+
* expect(deviceParam).toEqual('ideal');
|
|
529
|
+
* expect(noiseSuppression).toEqual(true);
|
|
530
|
+
* expect(noiseSuppressionParam).toEqual('ideal');
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
export const extractConstraintsWithKeys = (keys) => (constraints) => keys.reduce((result, key) => {
|
|
534
|
+
if (key === 'device') {
|
|
535
|
+
return {
|
|
536
|
+
...result,
|
|
537
|
+
[key]: extractConstraints(key)(constraints),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (isConstrainULongKeys(key) ||
|
|
541
|
+
isConstrainDoubleKeys(key) ||
|
|
542
|
+
isExtendedConstrainULongKeys(key) ||
|
|
543
|
+
isExtendedConstrainDoubleKeys(key)) {
|
|
544
|
+
return {
|
|
545
|
+
...result,
|
|
546
|
+
[key]: extractConstraints(key)(constraints),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
if (isConstrainStringKeys(key) ||
|
|
550
|
+
isExtendedConstrainStringKeys(key)) {
|
|
551
|
+
return {
|
|
552
|
+
...result,
|
|
553
|
+
[key]: extractConstraints(key)(constraints),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
if (isConstrainBooleanKeys(key) ||
|
|
557
|
+
isExtendedConstrainBooleanKeys(key)) {
|
|
558
|
+
return {
|
|
559
|
+
...result,
|
|
560
|
+
[key]: extractConstraints(key)(constraints),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}, {});
|
|
565
|
+
/**
|
|
566
|
+
* Extract deviceId from constraints
|
|
567
|
+
*/
|
|
568
|
+
export const extractDeviceId = extractConstrainString('deviceId');
|
|
569
|
+
/**
|
|
570
|
+
* Find device from the device list with provided constraints
|
|
571
|
+
*
|
|
572
|
+
* @param constraints - The constraints to be used for the lookup
|
|
573
|
+
* @param devices - The devices to be used for the lookup
|
|
574
|
+
*
|
|
575
|
+
* @returns `true` means it can be any devices, `undefined` means not found,
|
|
576
|
+
* otherwise, the matched device will be returned
|
|
577
|
+
*/
|
|
578
|
+
export const findDeviceFromConstraints = (constraints, devices) => {
|
|
579
|
+
if (typeof constraints === 'boolean') {
|
|
580
|
+
return constraints ? resolveOnlyDevice(devices) : constraints;
|
|
581
|
+
}
|
|
582
|
+
if (constraints === undefined ||
|
|
583
|
+
!devices.length ||
|
|
584
|
+
devices.some(device => !device.label) ||
|
|
585
|
+
(Array.isArray(constraints) && !constraints.length)) {
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
const [constrainDevices, deviceParam] = extractConstraints('device')(constraints);
|
|
589
|
+
// Device takes priority
|
|
590
|
+
if (constrainDevices) {
|
|
591
|
+
const [device] = findDeviceFrom(constrainDevices, devices) ?? [];
|
|
592
|
+
if (device) {
|
|
593
|
+
return device;
|
|
594
|
+
}
|
|
595
|
+
if (deviceParam === 'exact') {
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const [ConstrainDeviceIds, deviceIdParam] = extractConstraints('deviceId')(constraints);
|
|
600
|
+
if (ConstrainDeviceIds) {
|
|
601
|
+
const deviceFound = devices.find(device => {
|
|
602
|
+
const id = ConstrainDeviceIds.find(id => id && device.label && device.deviceId === id);
|
|
603
|
+
return !!id;
|
|
604
|
+
});
|
|
605
|
+
if (deviceFound) {
|
|
606
|
+
return deviceFound;
|
|
607
|
+
}
|
|
608
|
+
if (deviceIdParam === 'exact') {
|
|
609
|
+
return undefined;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return resolveOnlyDevice(devices);
|
|
613
|
+
};
|
|
614
|
+
/**
|
|
615
|
+
* Relax input constraints to enable looking up the device by `deviceId` as well
|
|
616
|
+
* as `label` as a fallback as a best effort to get a similar device when
|
|
617
|
+
* possible.
|
|
618
|
+
*
|
|
619
|
+
* @param input - The input constraints to be relaxed
|
|
620
|
+
* @param devices - The current device list
|
|
621
|
+
*/
|
|
622
|
+
export const relaxInputConstraint = (input, devices) => {
|
|
623
|
+
if (devices.length === 0 || !input || typeof input === 'boolean') {
|
|
624
|
+
return input;
|
|
625
|
+
}
|
|
626
|
+
const [device, param] = extractConstrainDevice(input);
|
|
627
|
+
const relaxedDevice = relaxDevice(device, devices);
|
|
628
|
+
const otherConstraints = isInputConstraintSet(input) ? input : {};
|
|
629
|
+
return relaxedDevice
|
|
630
|
+
? { ...otherConstraints, device: { [param]: relaxedDevice } }
|
|
631
|
+
: input;
|
|
632
|
+
};
|
|
633
|
+
export const getConstraintsHandlers = () => {
|
|
634
|
+
let defaultConstraints = {
|
|
635
|
+
audio: {
|
|
636
|
+
echoCancellation: { ideal: true },
|
|
637
|
+
noiseSuppression: { ideal: true },
|
|
638
|
+
},
|
|
639
|
+
video: {
|
|
640
|
+
frameRate: { ideal: 30 },
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
const setDefaultConstraints = (newConstraints) => {
|
|
644
|
+
defaultConstraints = { ...defaultConstraints, ...newConstraints };
|
|
645
|
+
};
|
|
646
|
+
const getDefaultConstraints = () => {
|
|
647
|
+
return defaultConstraints;
|
|
648
|
+
};
|
|
649
|
+
return {
|
|
650
|
+
getDefaultConstraints,
|
|
651
|
+
setDefaultConstraints,
|
|
652
|
+
};
|
|
653
|
+
};
|