@pexip/media-control 18.5.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 19.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 878fcca: Index devices
8
+
9
+ Change to device index instead of Array to store device list, and added the
10
+ following functions:
11
+
12
+ - `function createIndexedDevices(MediaDeviceInfoLike[]): IndexedDevices`
13
+ - `function isIndexedDevices(t: unknown): t is IndexedDevices`
14
+ - `function getDeviceId(device: Pick<MediaDeviceInfoLike, 'groupId' | 'deviceId' | 'kind'>): string`
15
+ - `function reverseDeviceId(id: string): MediaDeviceInfoLike`
16
+
17
+ All the device lookup functions are updated to use `IndexedDevices`.
18
+
19
+ Also, the artificial `devicechange` before and after `getUserMedia` call are
20
+ removed.
21
+
22
+ ### Minor Changes
23
+
24
+ - 6c42e61: Use `exact` constraint when the media device is connected and `ideal`
25
+ if it is not found.
26
+ - e362a65: Add `isTrackMutedOrEnded` function
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies [164a3e2]
31
+ - Updated dependencies [b13010c]
32
+ - Updated dependencies [66a8f72]
33
+ - @pexip/utils@16.13.0
34
+
3
35
  ## 18.5.0
4
36
 
5
37
  ### Patch Changes
@@ -40,4 +40,5 @@ export declare const INCLUDE_EXCLUDE_CONSTRAINT: {
40
40
  readonly Exclude: "exclude";
41
41
  };
42
42
  export type IncludeExcludeConstraint = ExtractObjectValueTypes<typeof INCLUDE_EXCLUDE_CONSTRAINT>;
43
+ export declare const DEVICE_ID_SEPARATOR = ":";
43
44
  export {};
package/dist/constants.js CHANGED
@@ -24,3 +24,4 @@ export const INCLUDE_EXCLUDE_CONSTRAINT = {
24
24
  Include: 'include',
25
25
  Exclude: 'exclude',
26
26
  };
27
+ export const DEVICE_ID_SEPARATOR = ':';
@@ -1,4 +1,4 @@
1
- import type { FacingMode, InputConstraintSet, InputDeviceConstraint, MediaConstraintRequirement, MediaDeviceInfoLike, MediaDeviceRequest, NormalizedConstraintDeviceParameters } from './types';
1
+ import type { FacingMode, InputConstraintSet, InputDeviceConstraint, MediaConstraintRequirement, MediaDeviceInfoLike, MediaDeviceRequest, NormalizedConstraintDeviceParameters, IndexedDevices } from './types';
2
2
  import type { ConstrainBooleanKeys, ConstrainDoubleKeys, ConstrainParamKeys, ConstrainRangeParamKeys, ConstrainStringKeys, ConstrainULongKeys, ExtendedConstrainBooleanKeys, ExtendedConstrainDoubleKeys, ExtendedConstrainStringKeys, ExtendedConstrainULongKeys } from './typeGuards';
3
3
  /**
4
4
  * Check if provided constraint is an `exact` device constraint
@@ -12,7 +12,7 @@ export declare const removeUnsupportedConstraints: (constraints: MediaStreamCons
12
12
  /**
13
13
  * Merge base constraints with provided base constraints and another constraints
14
14
  */
15
- export declare const mergeConstraints: (baseConstraints: InputConstraintSet | boolean | undefined) => (constraints: InputDeviceConstraint | undefined) => InputConstraintSet | boolean;
15
+ export declare const mergeConstraints: (baseConstraints: InputConstraintSet | boolean | undefined) => (constraints: InputDeviceConstraint | undefined) => InputConstraintSet | boolean | undefined;
16
16
  /**
17
17
  * Normalize Device
18
18
  * Convert any valid `MediaDeviceInfoLike` into `MediaDeviceInfoLike[]`
@@ -52,7 +52,7 @@ export type ConstrainNumberTuple = [
52
52
  ];
53
53
  export declare const extractConstrainNumber: (key: ConstrainULongKeys | ConstrainDoubleKeys | ExtendedConstrainULongKeys | ExtendedConstrainDoubleKeys) => (constraints: InputConstraintSet) => ConstrainNumberTuple | ConstrainNoneTuple;
54
54
  export type ConstrainDeviceTuple = [MediaDeviceInfoLike[], ConstrainParamKeys];
55
- export declare const extractConstrainDevice: (constraints: InputDeviceConstraint | undefined) => ConstrainDeviceTuple | ConstrainNoneTuple;
55
+ export declare const extractConstrainDevice: (constraints: InputDeviceConstraint | undefined, initialParam?: ConstrainParamKeys) => ConstrainDeviceTuple | ConstrainNoneTuple;
56
56
  /**
57
57
  * Compare float point number a and b to see if they are closed to be considered
58
58
  * as having the same value
@@ -88,7 +88,7 @@ export declare const satisfyConstrainNumber: (constraint: ConstrainNumber | unde
88
88
  *
89
89
  * @internal
90
90
  */
91
- export declare const resolveMediaDeviceConstraints: (constraints?: InputDeviceConstraint, base?: MediaTrackConstraints | boolean) => MediaTrackConstraints | boolean;
91
+ export declare const resolveMediaDeviceConstraints: (constraints?: InputDeviceConstraint, base?: MediaTrackConstraints | boolean) => MediaTrackConstraints | boolean | undefined;
92
92
  /**
93
93
  * @remarks
94
94
  * Constraint multiple (exact) devices is not supported
@@ -103,10 +103,10 @@ export declare const getMediaConstraints: ({ audio, video, defaultConstraints, }
103
103
  * @param constraints - constraints to be applied
104
104
  */
105
105
  export declare const applyConstraints: (tracks: MediaStreamTrack[] | undefined, constraints: MediaDeviceRequest) => Promise<void>;
106
- export declare const findDeviceFrom: (devicesToFind: MediaDeviceInfoLike[], deviceList: MediaDeviceInfoLike[]) => MediaDeviceInfoLike[] | undefined;
107
- export declare const findDeviceFromDeviceConstraints: (device: InputConstraintSet['device'], devices: MediaDeviceInfoLike[]) => MediaDeviceInfoLike[] | undefined;
108
- export declare const relaxDevice: (device: InputConstraintSet['device'], devices: MediaDeviceInfoLike[]) => MediaDeviceInfoLike[] | undefined;
109
- export declare const resolveOnlyDevice: (devices: MediaDeviceInfoLike[]) => true | MediaDeviceInfoLike | undefined;
106
+ export declare const findDeviceFrom: (devicesToFind: MediaDeviceInfoLike[], devices: IndexedDevices) => MediaDeviceInfoLike[];
107
+ export declare const findDeviceFromDeviceConstraints: (device: InputConstraintSet['device'], devices: IndexedDevices) => MediaDeviceInfoLike[] | undefined;
108
+ export declare const relaxDevice: (device: InputConstraintSet['device'], devices: IndexedDevices) => MediaDeviceInfoLike[] | undefined;
109
+ export declare const resolveOnlyDevice: (devices: IndexedDevices) => true | MediaDeviceInfoLike | undefined;
110
110
  type ConstrainNumberKeys = ConstrainULongKeys | ConstrainDoubleKeys | ExtendedConstrainDoubleKeys | ExtendedConstrainULongKeys;
111
111
  type ConstrainTuples = ConstrainDeviceTuple | ConstrainNumberTuple | ConstrainStringTuple | ConstrainBooleanTuple | ConstrainNoneTuple;
112
112
  type ExtractConstraintFn<R extends ConstrainTuples> = (constraints: InputDeviceConstraint | undefined) => R;
@@ -118,7 +118,7 @@ type ConstrainStrings = Record<ConstrainStringKeys | ExtendedConstrainStringKeys
118
118
  type ConstrainNumbers = Record<ConstrainNumberKeys, ConstrainNumberTuple | ConstrainNoneTuple>;
119
119
  type ConstrainBooleans = Record<ConstrainBooleanKeys | ExtendedConstrainBooleanKeys, ConstrainBooleanTuple | ConstrainNoneTuple>;
120
120
  type ConstrainDevices = Record<'device', ConstrainDeviceTuple | ConstrainNoneTuple>;
121
- type Constraints = ConstrainStrings & ConstrainNumbers & ConstrainBooleans & ConstrainDevices;
121
+ export type Constraints = ConstrainStrings & ConstrainNumbers & ConstrainBooleans & ConstrainDevices;
122
122
  /**
123
123
  * Extract the constraints with provided keys
124
124
  *
@@ -157,16 +157,17 @@ export declare const extractDeviceId: (constraints: InputConstraintSet) => Const
157
157
  * @returns `true` means it can be any devices, `undefined` means not found,
158
158
  * otherwise, the matched device will be returned
159
159
  */
160
- export declare const findDeviceFromConstraints: (constraints: InputDeviceConstraint | undefined, devices: MediaDeviceInfoLike[]) => boolean | MediaDeviceInfoLike | undefined;
160
+ export declare const findDeviceFromConstraints: (constraints: InputDeviceConstraint | undefined, devices: IndexedDevices) => boolean | MediaDeviceInfoLike | undefined;
161
161
  /**
162
162
  * Relax input constraints to enable looking up the device by `deviceId` as well
163
163
  * as `label` as a fallback as a best effort to get a similar device when
164
- * possible.
164
+ * possible. If the the device is found we will use the `exact` constraint otherwise
165
+ * we will use the `ideal`.
165
166
  *
166
167
  * @param input - The input constraints to be relaxed
167
168
  * @param devices - The current device list
168
169
  */
169
- export declare const relaxInputConstraint: (input: InputDeviceConstraint | undefined, devices: MediaDeviceInfoLike[]) => InputDeviceConstraint | undefined;
170
+ export declare const relaxInputConstraint: (kind: 'audioinput' | 'videoinput', input: InputDeviceConstraint | undefined, devices: IndexedDevices) => InputDeviceConstraint | undefined;
170
171
  export declare const getConstraintsHandlers: () => {
171
172
  getDefaultConstraints: () => MediaStreamConstraints;
172
173
  setDefaultConstraints: (newConstraints: MediaStreamConstraints) => void;
@@ -1,6 +1,5 @@
1
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
2
  import { logger } from './logger';
3
- import { findDevice } from './utils';
4
3
  /**
5
4
  * Check if provided constraint is an `exact` device constraint
6
5
  */
@@ -61,9 +60,12 @@ export const mergeConstraints = (baseConstraints) => (constraints) => {
61
60
  if (isDeviceConstraint(constraints)) {
62
61
  return { device: constraints };
63
62
  }
64
- return constraints ?? false;
63
+ return constraints;
65
64
  }
66
- if (!constraints) {
65
+ if (constraints === undefined) {
66
+ return undefined;
67
+ }
68
+ if (constraints === false) {
67
69
  return false;
68
70
  }
69
71
  if (isDeviceConstraint(constraints)) {
@@ -165,7 +167,7 @@ export const toArray = (t) => {
165
167
  export const NONE = [undefined, 'ideal'];
166
168
  export const extractConstrainString = (key) => (constraints) => {
167
169
  const constraint = constraints[key];
168
- if (!constraint) {
170
+ if (constraint === undefined) {
169
171
  return NONE;
170
172
  }
171
173
  if (isConstraintDOMString(constraint)) {
@@ -234,7 +236,7 @@ export const extractConstrainNumber = (key) => (constraints) => {
234
236
  }
235
237
  return NONE;
236
238
  };
237
- export const extractConstrainDevice = (constraints) => {
239
+ export const extractConstrainDevice = (constraints, initialParam = 'ideal') => {
238
240
  if (!constraints ||
239
241
  isBoolean(constraints) ||
240
242
  (Array.isArray(constraints) && !constraints.length)) {
@@ -246,7 +248,7 @@ export const extractConstrainDevice = (constraints) => {
246
248
  if (isMediaDeviceInfo(device) || isMediaDeviceInfoArray(device)) {
247
249
  const normalized = normalizeDevice(device);
248
250
  if (normalized) {
249
- return [normalized, 'ideal'];
251
+ return [normalized, initialParam];
250
252
  }
251
253
  }
252
254
  if (isConstraintDeviceParameters(device)) {
@@ -389,7 +391,7 @@ export const resolveMediaDeviceConstraints = (constraints, base) => {
389
391
  if (constraints && isMediaTrackConstraints(base)) {
390
392
  return base;
391
393
  }
392
- return !!constraints;
394
+ return constraints === undefined ? constraints : !!constraints;
393
395
  };
394
396
  /**
395
397
  * @remarks
@@ -429,43 +431,43 @@ export const applyConstraints = async (tracks, constraints) => {
429
431
  return [];
430
432
  }));
431
433
  };
432
- export const findDeviceFrom = (devicesToFind, deviceList) => {
433
- const devicesFound = devicesToFind.flatMap(device => {
434
- const found = findDevice(device)(deviceList);
435
- if (found) {
436
- return [found];
434
+ export const findDeviceFrom = (devicesToFind, devices) => devicesToFind.flatMap(device => devices.get(device));
435
+ const findDeviceFromDeviceId = (constrainDeviceIds, devices) => {
436
+ for (const deviceId of constrainDeviceIds) {
437
+ const device = devices.get({ deviceId, kind: 'audioinput' }).at(0) ??
438
+ devices.get({ deviceId, kind: 'videoinput' }).at(0) ??
439
+ devices.get({ deviceId, kind: 'audiooutput' }).at(0);
440
+ if (device) {
441
+ return device;
437
442
  }
438
- return [];
439
- });
440
- if (devicesFound.length) {
441
- return devicesFound;
442
443
  }
443
- return undefined;
444
444
  };
445
445
  export const findDeviceFromDeviceConstraints = (device, devices) => {
446
446
  const normalized = normalizeDeviceConstraint(device);
447
447
  if (normalized) {
448
448
  const { ideal, exact } = normalized;
449
449
  if (exact) {
450
- return findDeviceFrom(exact, devices);
450
+ const found = findDeviceFrom(exact, devices);
451
+ return found.length ? found : undefined;
451
452
  }
452
453
  if (ideal) {
453
- return findDeviceFrom(ideal, devices);
454
+ const found = findDeviceFrom(ideal, devices);
455
+ return found.length ? found : undefined;
454
456
  }
455
457
  }
456
458
  };
457
459
  export const relaxDevice = (device, devices) => {
458
460
  const found = findDeviceFromDeviceConstraints(device, devices);
459
- if (found) {
461
+ if (found?.length) {
460
462
  return found;
461
463
  }
462
464
  // Return the original with normalized form
463
465
  return normalizeDevice(device);
464
466
  };
465
467
  export const resolveOnlyDevice = (devices) => {
466
- switch (devices.length) {
468
+ switch (devices.size('audioinput') + devices.size('videoinput')) {
467
469
  case 1:
468
- return devices[0];
470
+ return devices.first('audioinput') ?? devices.first('videoinput');
469
471
  case 0:
470
472
  return undefined;
471
473
  default:
@@ -580,8 +582,7 @@ export const findDeviceFromConstraints = (constraints, devices) => {
580
582
  return constraints ? resolveOnlyDevice(devices) : constraints;
581
583
  }
582
584
  if (constraints === undefined ||
583
- !devices.length ||
584
- devices.some(device => !device.label) ||
585
+ !(devices.size('audioinput') + devices.size('videoinput')) ||
585
586
  (Array.isArray(constraints) && !constraints.length)) {
586
587
  return undefined;
587
588
  }
@@ -598,10 +599,7 @@ export const findDeviceFromConstraints = (constraints, devices) => {
598
599
  }
599
600
  const [ConstrainDeviceIds, deviceIdParam] = extractConstraints('deviceId')(constraints);
600
601
  if (ConstrainDeviceIds) {
601
- const deviceFound = devices.find(device => {
602
- const id = ConstrainDeviceIds.find(id => id && device.label && device.deviceId === id);
603
- return !!id;
604
- });
602
+ const deviceFound = findDeviceFromDeviceId(ConstrainDeviceIds, devices);
605
603
  if (deviceFound) {
606
604
  return deviceFound;
607
605
  }
@@ -614,16 +612,33 @@ export const findDeviceFromConstraints = (constraints, devices) => {
614
612
  /**
615
613
  * Relax input constraints to enable looking up the device by `deviceId` as well
616
614
  * as `label` as a fallback as a best effort to get a similar device when
617
- * possible.
615
+ * possible. If the the device is found we will use the `exact` constraint otherwise
616
+ * we will use the `ideal`.
618
617
  *
619
618
  * @param input - The input constraints to be relaxed
620
619
  * @param devices - The current device list
621
620
  */
622
- export const relaxInputConstraint = (input, devices) => {
623
- if (devices.length === 0 || !input || typeof input === 'boolean') {
621
+ export const relaxInputConstraint = (kind, input, devices) => {
622
+ if (devices.size(kind) === 0 || !input || typeof input === 'boolean') {
624
623
  return input;
625
624
  }
626
- const [device, param] = extractConstrainDevice(input);
625
+ let found = false;
626
+ if (isMediaDeviceInfo(input)) {
627
+ found = Boolean(devices.get(input).length);
628
+ }
629
+ else if (isMediaDeviceInfoArray(input)) {
630
+ found = Boolean(input.some(device => devices.get(device).length > 0));
631
+ }
632
+ else if (isInputConstraintSet(input)) {
633
+ found = Boolean(findDeviceFromDeviceConstraints(input.device, devices));
634
+ }
635
+ else {
636
+ const [ConstrainDeviceIds] = extractConstraints('deviceId')(input);
637
+ if (ConstrainDeviceIds) {
638
+ found = Boolean(findDeviceFromDeviceId(ConstrainDeviceIds, devices));
639
+ }
640
+ }
641
+ const [device, param] = extractConstrainDevice(input, found ? 'exact' : 'ideal');
627
642
  const relaxedDevice = relaxDevice(device, devices);
628
643
  const otherConstraints = isInputConstraintSet(input) ? input : {};
629
644
  return relaxedDevice
package/dist/devices.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { MediaDeviceInfoLike, MediaInput, MediaDeviceRequest, InputDeviceConstraint, InputConstraintSet, InputDevicePermission, FacingMode } from './types';
2
- import { MediaDeviceKinds } from './types';
1
+ import type { FacingMode, IndexedDevices, InputConstraintSet, InputDeviceConstraint, InputDevicePermission, MediaDeviceInfoLike, MediaDeviceRequest, MediaDeviceTrackInfo, MediaInput } from './types';
3
2
  /**
4
3
  * DeviceChange Event
5
4
  *
@@ -31,6 +30,7 @@ interface ToKeyOptions {
31
30
  * @beta
32
31
  */
33
32
  export declare const toKey: ({ id, kind, label }: ToKeyOptions) => string;
33
+ export declare const toMediaDeviceInputKind: (track: Pick<MediaStreamTrack, 'kind'>) => 'audioinput' | 'videoinput';
34
34
  /**
35
35
  * Future proofing: in case we want to alter the values or type returned
36
36
  * by {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices | enumerateDevices}
@@ -66,27 +66,7 @@ export declare const createTrackDevicesChanges: (prevDevices?: MediaDeviceInfoLi
66
66
  * @beta
67
67
  */
68
68
  export declare const deviceChanged: (fn: (event: DeviceChangedChanges) => void) => () => void;
69
- export declare const extractDeviceInfo: (track: MediaStreamTrack) => {
70
- kind: MediaDeviceKinds;
71
- deviceId: string;
72
- groupId: string;
73
- label: string;
74
- settings: {
75
- aspectRatio?: number | undefined;
76
- autoGainControl?: boolean | undefined;
77
- channelCount?: number | undefined;
78
- displaySurface?: string | undefined;
79
- echoCancellation?: boolean | undefined;
80
- facingMode?: string | undefined;
81
- frameRate?: number | undefined;
82
- height?: number | undefined;
83
- noiseSuppression?: boolean | undefined;
84
- sampleRate?: number | undefined;
85
- sampleSize?: number | undefined;
86
- width?: number | undefined;
87
- latency?: number | undefined;
88
- };
89
- };
69
+ export declare const extractDeviceInfo: (track: MediaStreamTrack) => MediaDeviceTrackInfo;
90
70
  /**
91
71
  * Convert `MediaStreamTrack` to `MediaDeviceInfoLike`
92
72
  *
@@ -128,7 +108,7 @@ export declare const toMediaDeviceInfo: (info: MediaDeviceInfoLike) => {
128
108
  *
129
109
  * @beta
130
110
  */
131
- export declare const findMediaInputFromMediaStreamTrack: (devices: MediaDeviceInfoLike[]) => (track?: MediaStreamTrack | undefined) => MediaDeviceInfoLike | undefined;
111
+ export declare const findMediaInputFromMediaStreamTrack: (devices: IndexedDevices) => (track?: MediaStreamTrack | undefined) => MediaDeviceInfoLike | undefined;
132
112
  /**
133
113
  * Find media input from media stream
134
114
  *
@@ -140,31 +120,48 @@ export declare const findMediaInputFromMediaStreamTrack: (devices: MediaDeviceIn
140
120
  *
141
121
  * @beta
142
122
  */
143
- export declare const findMediaInputFromStream: (devices: MediaDeviceInfoLike[]) => (stream: MediaStream | undefined) => MediaInput;
123
+ export declare const findMediaInputFromStream: (devices: IndexedDevices) => (stream: MediaStream | undefined) => MediaInput;
144
124
  export declare const compareDeviceConstraintAndTrackDevice: (device: MediaDeviceInfoLike | undefined) => (constraint: InputConstraintSet['device']) => boolean;
145
- export declare const isRequestedResolution: (request: InputDeviceConstraint | undefined, response: MediaDeviceInfoLike | undefined) => boolean;
146
- export declare const isRequestedInputDevice: (request: InputDeviceConstraint | undefined, response: MediaDeviceInfoLike | undefined) => boolean;
125
+ export declare const isRequestedResolution: (request: InputDeviceConstraint | undefined, response: MediaTrackSettings | undefined) => boolean;
126
+ export declare const isRequestedInputDevice: (request: InputDeviceConstraint | undefined, response: MediaDeviceInfoLike | MediaDeviceTrackInfo | undefined) => boolean;
147
127
  /**
148
128
  * Compare request and the input to see if the request has been fulfilled
149
129
  */
150
130
  export declare const isRequestedInputTrack: (request: InputDeviceConstraint | undefined, current: MediaStreamTrack | MediaDeviceInfoLike | undefined) => boolean;
151
- export declare const hasRequestingDevice: (request: InputDeviceConstraint | undefined, kind: MediaDeviceKind, currentDevices: MediaDeviceInfoLike[]) => boolean;
152
131
  /**
153
- * Decide if we should send a new gUM request based on the inputs
132
+ * Check if there is a requesting device with respecting to the current devices
154
133
  *
155
134
  * @param request - Requesting input device constraint
156
- * @param tracks - The current media stream tracks
157
- * @param currentDevices - The current list of device of the same kind of the
158
- * request
135
+ * @param kind - The kind of the device
136
+ * @param currentDevices - The current indexed devices
137
+ */
138
+ export declare const hasRequestingDevice: (request: InputDeviceConstraint | undefined, kind: MediaDeviceKind, currentDevices: IndexedDevices) => boolean;
139
+ /**
140
+ * Check if the track is muted or 'ended'
141
+ * @param track - The media stream tracks
142
+ */
143
+ export declare const isTrackMutedOrEnded: (track: MediaStreamTrack) => boolean;
144
+ interface DeviceRequestParams {
145
+ kind: 'audioinput' | 'videoinput';
146
+ permission: PermissionState;
147
+ request: InputDeviceConstraint | undefined;
148
+ tracks: MediaStreamTrack[];
149
+ currentDevices: IndexedDevices;
150
+ }
151
+ /**
152
+ * Decide if we should send a new gUM request
153
+ *
154
+ * @param deviceRequestParams - @see DeviceRequestParams
159
155
  *
160
156
  * @returns `true` means the request should be conducted, otherwise `false`.
161
157
  */
162
- export declare const shouldRequestDevice: (request: InputDeviceConstraint | undefined, tracks: MediaStreamTrack[], currentDevices: MediaDeviceInfoLike[]) => boolean;
163
- export declare const resolveInputDevice: (tracksOrDevice: MediaDeviceInfoLike | MediaStreamTrack[] | undefined, findInput: ReturnType<typeof findMediaInputFromMediaStreamTrack>) => MediaDeviceInfo | MediaDeviceInfoLike | MediaStreamTrack | undefined;
158
+ export declare const shouldRequestDevice: ({ kind, permission, request, tracks, currentDevices, }: DeviceRequestParams) => boolean;
159
+ export declare const resolveInputDevice: (tracksOrDevice: MediaDeviceInfoLike | MediaStreamTrack[] | undefined, findInput: ReturnType<typeof findMediaInputFromMediaStreamTrack>) => MediaDeviceInfoLike | MediaStreamTrack | undefined;
160
+ export declare const isStreamingRequestingDevice: (request: InputDeviceConstraint | undefined, trackOrDevice: MediaDeviceInfoLike | MediaStreamTrack[] | undefined, devices: IndexedDevices) => boolean;
164
161
  export declare const isStreamingRequestedDevicesBase: (request: MediaDeviceRequest, tracksOrDevices: {
165
162
  audio?: MediaDeviceInfoLike | MediaStreamTrack[];
166
163
  video?: MediaDeviceInfoLike | MediaStreamTrack[];
167
- }, devices: MediaDeviceInfoLike[]) => {
164
+ }, devices: IndexedDevices) => {
168
165
  audio: boolean;
169
166
  video: boolean;
170
167
  };
@@ -178,7 +175,7 @@ export declare const isStreamingRequestedDevicesBase: (request: MediaDeviceReque
178
175
  * audio - The stream is using the same device as requested if `true`
179
176
  * video - The stream is using the same device as requested if `true`
180
177
  */
181
- export declare const isStreamingRequestedDevices: (request: MediaDeviceRequest, stream: MediaStream | undefined, devices: MediaDeviceInfoLike[]) => {
178
+ export declare const isStreamingRequestedDevices: (request: MediaDeviceRequest, stream: MediaStream | undefined, devices: IndexedDevices) => {
182
179
  audio: boolean;
183
180
  video: boolean;
184
181
  };
@@ -202,19 +199,24 @@ export declare const findPermissionGrantedDevices: (devices: MediaDeviceInfoLike
202
199
  * stream and there is no device with label, 'prompt' will be returned
203
200
  * otherwise, 'denied'.
204
201
  *
205
- * @param devices - The current devices
206
- * @param anyActiveStream - Has ever got an active stream to help the fallback to
202
+ * @param anyGrantedDevice - Any permission granted device
203
+ * @param anyLiveTrack - Has ever got a live track
207
204
  * guess the state more accurately
208
205
  */
209
- export declare const toPermissionState: (devices: MediaDeviceInfoLike[], anyActiveStream?: boolean) => PermissionState;
206
+ export declare const toPermissionState: (anyGrantedDevice: boolean, anyLiveTrack?: boolean) => PermissionState;
207
+ export declare const queryPermissionFallback: (name: 'camera' | 'microphone', everActiveTrack?: boolean, getDevices?: () => Promise<MediaDeviceInfo[]>) => Promise<PermissionState>;
208
+ export declare const queryPermission: (name: 'camera' | 'microphone', everActiveTrack?: boolean, getDevices?: () => Promise<MediaDeviceInfo[]>) => Promise<PermissionState>;
210
209
  /**
211
210
  * A wrapper for `navigator.permissions.query` with fallback to use
212
211
  * `navigator.mediaDevices.enumerateDevices` to guess the `PermissionState`
213
212
  *
214
- * @param anyActiveStream - Has ever got an active stream to help the fallback to
213
+ * @param anyActiveAudioTrack - Has ever got an active audio to help the fallback to
214
+ * guess the state more accurately
215
+ * @param anyActiveVideoTrack - Has ever got an active video to help the fallback to
215
216
  * guess the state more accurately
217
+ * @param getDevices - A function to get the list of devices, e.g. navigator.mediaDevices.enumerateDevices
216
218
  */
217
- export declare const getInputDevicePermissionState: (anyActiveStream?: boolean) => Promise<InputDevicePermission>;
219
+ export declare const getInputDevicePermissionState: (anyActiveAudioTrack?: boolean, anyActiveVideoTrack?: boolean, getDevices?: () => Promise<MediaDeviceInfo[]>) => Promise<InputDevicePermission>;
218
220
  /**
219
221
  * Find current audio output id to be used to set as sinkId
220
222
  *
package/dist/devices.js CHANGED
@@ -1,14 +1,25 @@
1
+ import { assert } from '@pexip/utils';
1
2
  import { MediaDeviceKinds } from './types';
2
- import { extractConstrainNumber, extractConstrainString, normalizeDeviceConstraint, toArray, relaxInputConstraint, satisfyConstrainNumber, extractConstraintsWithKeys, } from './constraints';
3
- import { isMediaDeviceInfo, isConstraintSetDevice, isInputConstraintSet, isConstrainDOMStringParameters, isConstraintDOMString, isMediaStreamTrack, } from './typeGuards';
4
- import { findDevice, compareDevices, isSameDeviceKind, not, isAudioInput, isVideoInput, isAudioOutput, isDeviceGranted, } from './utils';
5
- const SEPARATOR = ':';
3
+ import { extractConstrainNumber, extractConstrainString, extractConstraintsWithKeys, normalizeDeviceConstraint, satisfyConstrainNumber, toArray, } from './constraints';
4
+ import { isConstrainDOMStringParameters, isConstraintDOMString, isConstraintSetDevice, isInputConstraintSet, isMediaDeviceInfo, isMediaDeviceTrackInfo, isMediaStreamTrack, } from './typeGuards';
5
+ import { compareDevices, findDevice, isAudioInput, isAudioOutput, isDeviceGranted, isSameDeviceKind, isVideoInput, not, } from './utils';
6
+ import { DEVICE_ID_SEPARATOR } from './constants';
6
7
  /**
7
8
  * Convert provided info to key for Map
8
9
  *
9
10
  * @beta
10
11
  */
11
- export const toKey = ({ id, kind, label }) => [kind, id, label].join(SEPARATOR);
12
+ export const toKey = ({ id, kind, label }) => [kind, id, label].join(DEVICE_ID_SEPARATOR);
13
+ export const toMediaDeviceInputKind = (track) => {
14
+ switch (track.kind) {
15
+ case 'audio':
16
+ return 'audioinput';
17
+ case 'video':
18
+ return 'videoinput';
19
+ default:
20
+ throw new Error('Invalid track kind');
21
+ }
22
+ };
12
23
  /**
13
24
  * Future proofing: in case we want to alter the values or type returned
14
25
  * by {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices | enumerateDevices}
@@ -108,6 +119,7 @@ export const extractDeviceInfo = (track) => {
108
119
  ? MediaDeviceKinds.AUDIOINPUT
109
120
  : MediaDeviceKinds.VIDEOINPUT;
110
121
  return {
122
+ id: track.id,
111
123
  kind,
112
124
  deviceId: deviceId ?? '',
113
125
  groupId: groupId ?? '',
@@ -155,13 +167,6 @@ export const compareMediaDeviceToMediaTrack = (track) => (device) => {
155
167
  }
156
168
  return sameKind && sameLabel;
157
169
  };
158
- const toMediaDeviceInfoLikeJSON = (info) => ({
159
- deviceId: info.deviceId,
160
- groupId: info.groupId,
161
- kind: info.kind,
162
- label: info.label,
163
- settings: info.settings,
164
- });
165
170
  export const toMediaDeviceInfo = (info) => ({
166
171
  deviceId: info.deviceId,
167
172
  groupId: info.groupId,
@@ -180,25 +185,20 @@ export const toMediaDeviceInfo = (info) => ({
180
185
  * @beta
181
186
  */
182
187
  export const findMediaInputFromMediaStreamTrack = (devices) => (track) => {
183
- if (!devices.length || !track) {
188
+ if (!track) {
184
189
  return undefined;
185
190
  }
186
- const settings = track.getSettings();
187
- const devicesWithSameKind = devices.filter(d => d.kind === `${track.kind}input`);
188
- // There is no need to compare since it is only device
189
- const onlyDevice = devicesWithSameKind[0];
190
- if (devicesWithSameKind.length === 1 && onlyDevice) {
191
- onlyDevice.settings = settings;
192
- onlyDevice.toJSON = () => toMediaDeviceInfoLikeJSON(onlyDevice);
193
- return onlyDevice;
194
- }
195
- const device = devicesWithSameKind.find(compareMediaDeviceToMediaTrack(track));
196
- if (device) {
197
- device.settings = settings;
198
- device.toJSON = () => toMediaDeviceInfoLikeJSON(device);
199
- return device;
191
+ const kind = toMediaDeviceInputKind(track);
192
+ if (!devices.size(kind)) {
193
+ return undefined;
200
194
  }
201
- return device;
195
+ const settings = track.getSettings();
196
+ const devicesFound = devices.get({
197
+ kind,
198
+ label: track.label,
199
+ deviceId: settings.deviceId,
200
+ });
201
+ return devicesFound[0];
202
202
  };
203
203
  /**
204
204
  * Find media input from media stream
@@ -212,7 +212,7 @@ export const findMediaInputFromMediaStreamTrack = (devices) => (track) => {
212
212
  * @beta
213
213
  */
214
214
  export const findMediaInputFromStream = (devices) => (stream) => {
215
- if (!stream || !devices.length) {
215
+ if (!stream) {
216
216
  return {};
217
217
  }
218
218
  const findMediaInput = findMediaInputFromMediaStreamTrack(devices);
@@ -228,7 +228,9 @@ export const compareDeviceConstraintAndTrackDevice = (device) => (constraint) =>
228
228
  const normalized = normalizeDeviceConstraint(constraint);
229
229
  if (normalized) {
230
230
  const requestingDevices = Object.values(normalized).flatMap(t => (Array.isArray(t) ? t : [t]));
231
- const compare = compareDevices(device, device.label ? 'label' : 'deviceId');
231
+ const compare = compareDevices(device,
232
+ // Always use `deviceId` for exact constraints
233
+ !!normalized.exact || !device.label ? 'deviceId' : 'label');
232
234
  return requestingDevices.some(compare);
233
235
  }
234
236
  return false;
@@ -240,7 +242,7 @@ export const isRequestedResolution = (request, response) => {
240
242
  if (isInputConstraintSet(request)) {
241
243
  const [width] = extractConstrainNumber('width')(request);
242
244
  const [height] = extractConstrainNumber('height')(request);
243
- const { width: responseWidth = 0, height: responseHeight = 0 } = response.settings ?? {};
245
+ const { width: responseWidth = 0, height: responseHeight = 0 } = response ?? {};
244
246
  const portrait = responseHeight >= responseWidth;
245
247
  const resultWidth = portrait ? responseHeight : responseWidth;
246
248
  const resultHeight = portrait ? responseWidth : responseHeight;
@@ -275,10 +277,12 @@ export const isRequestedInputDevice = (request, response) => {
275
277
  .some(id => id === deviceId);
276
278
  }
277
279
  }
278
- const facingMode = response.settings?.facingMode;
279
- if (request.facingMode && facingMode) {
280
- const [facingModes] = extractConstrainString('facingMode')(request);
281
- return facingModes?.some(fm => fm === facingMode) ?? true;
280
+ if (isMediaDeviceTrackInfo(response)) {
281
+ const facingMode = response.settings.facingMode;
282
+ if (request.facingMode && facingMode) {
283
+ const [facingModes] = extractConstrainString('facingMode')(request);
284
+ return facingModes?.some(fm => fm === facingMode) ?? true;
285
+ }
282
286
  }
283
287
  return true;
284
288
  }
@@ -304,6 +308,13 @@ export const isRequestedInputTrack = (request, current) => {
304
308
  return true;
305
309
  };
306
310
  const extractDevice = extractConstraintsWithKeys(['device', 'deviceId']);
311
+ /**
312
+ * Check if there is a requesting device with respecting to the current devices
313
+ *
314
+ * @param request - Requesting input device constraint
315
+ * @param kind - The kind of the device
316
+ * @param currentDevices - The current indexed devices
317
+ */
307
318
  export const hasRequestingDevice = (request, kind, currentDevices) => {
308
319
  if (!request) {
309
320
  return false;
@@ -312,44 +323,52 @@ export const hasRequestingDevice = (request, kind, currentDevices) => {
312
323
  // No device specified
313
324
  if ((!devices || devices.length === 0) &&
314
325
  (!deviceIds || deviceIds.length === 0)) {
315
- return currentDevices.some(device => device.kind === kind);
326
+ return Boolean(currentDevices.size(kind));
316
327
  }
317
328
  // Has device specified
318
- const find = (device) => findDevice(device)(currentDevices);
319
- return (devices?.some(device => find(device)) ??
320
- deviceIds?.some(deviceId => find({ kind, deviceId, label: '', groupId: '' })) ??
329
+ return (devices?.some(device => currentDevices.get(device).length) ??
330
+ deviceIds?.some(deviceId => currentDevices.get({ kind, deviceId }).length) ??
321
331
  false);
322
332
  };
323
333
  /**
324
- * Decide if we should send a new gUM request based on the inputs
334
+ * Check if the track is muted or 'ended'
335
+ * @param track - The media stream tracks
336
+ */
337
+ export const isTrackMutedOrEnded = (track) => track.readyState === 'ended' || track.muted;
338
+ /**
339
+ * Decide if we should send a new gUM request
325
340
  *
326
- * @param request - Requesting input device constraint
327
- * @param tracks - The current media stream tracks
328
- * @param currentDevices - The current list of device of the same kind of the
329
- * request
341
+ * @param deviceRequestParams - @see DeviceRequestParams
330
342
  *
331
343
  * @returns `true` means the request should be conducted, otherwise `false`.
332
344
  */
333
- export const shouldRequestDevice = (request, tracks, currentDevices) => {
334
- if (!request || !currentDevices.some(device => device.label)) {
335
- return false;
336
- }
337
- const { kind } = currentDevices[0] ?? {};
338
- if (!kind || currentDevices.some(device => device.kind !== kind)) {
339
- throw new Error('Expect a single kind of device');
340
- }
341
- const [track] = tracks;
342
- if (tracks.length === 0 ||
343
- tracks.some(track => track.readyState === 'ended' || track.muted)) {
344
- return true;
345
- }
346
- if (isRequestedInputTrack(request, track)) {
345
+ export const shouldRequestDevice = ({ kind, permission, request, tracks, currentDevices, }) => {
346
+ if (!request) {
347
347
  return false;
348
348
  }
349
- if (hasRequestingDevice(request, kind, currentDevices)) {
350
- return true;
349
+ switch (permission) {
350
+ case 'prompt':
351
+ return true;
352
+ case 'granted': {
353
+ if (currentDevices.size(kind) === 0) {
354
+ return false;
355
+ }
356
+ if (tracks.length === 0 || tracks.some(isTrackMutedOrEnded)) {
357
+ return true;
358
+ }
359
+ const [track] = tracks;
360
+ assert(track);
361
+ if (isRequestedInputTrack(request, track)) {
362
+ return false;
363
+ }
364
+ if (hasRequestingDevice(request, kind, currentDevices)) {
365
+ return true;
366
+ }
367
+ return false;
368
+ }
369
+ default:
370
+ return false;
351
371
  }
352
- return false;
353
372
  };
354
373
  export const resolveInputDevice = (tracksOrDevice, findInput) => {
355
374
  if (isMediaDeviceInfo(tracksOrDevice)) {
@@ -363,14 +382,20 @@ export const resolveInputDevice = (tracksOrDevice, findInput) => {
363
382
  }
364
383
  return undefined;
365
384
  };
366
- export const isStreamingRequestedDevicesBase = (request, tracksOrDevices, devices) => {
367
- const audioRequest = relaxInputConstraint(request.audio, devices);
368
- const videoRequest = relaxInputConstraint(request.video, devices);
385
+ export const isStreamingRequestingDevice = (request, trackOrDevice, devices) => {
386
+ if (request === undefined) {
387
+ return true;
388
+ }
369
389
  const findInput = findMediaInputFromMediaStreamTrack(devices);
370
- const audioInput = resolveInputDevice(tracksOrDevices.audio, findInput);
371
- const videoInput = resolveInputDevice(tracksOrDevices.video, findInput);
372
- const audio = isRequestedInputTrack(audioRequest, audioInput);
373
- const video = isRequestedInputTrack(videoRequest, videoInput);
390
+ const input = resolveInputDevice(trackOrDevice, findInput);
391
+ if (!input) {
392
+ return Boolean(request) === Boolean(input);
393
+ }
394
+ return isRequestedInputTrack(request, input);
395
+ };
396
+ export const isStreamingRequestedDevicesBase = (request, tracksOrDevices, devices) => {
397
+ const audio = isStreamingRequestingDevice(request.audio, tracksOrDevices.audio, devices);
398
+ const video = isStreamingRequestingDevice(request.video, tracksOrDevices.video, devices);
374
399
  return { audio, video };
375
400
  };
376
401
  /**
@@ -407,41 +432,51 @@ export const findPermissionGrantedDevices = (devices) => devices.filter(isDevice
407
432
  * stream and there is no device with label, 'prompt' will be returned
408
433
  * otherwise, 'denied'.
409
434
  *
410
- * @param devices - The current devices
411
- * @param anyActiveStream - Has ever got an active stream to help the fallback to
435
+ * @param anyGrantedDevice - Any permission granted device
436
+ * @param anyLiveTrack - Has ever got a live track
412
437
  * guess the state more accurately
413
438
  */
414
- export const toPermissionState = (devices, anyActiveStream = false) => {
415
- const granted = devices.some(device => device.label);
416
- if (anyActiveStream) {
417
- return granted ? 'granted' : 'denied';
439
+ export const toPermissionState = (anyGrantedDevice, anyLiveTrack = false) => {
440
+ if (anyLiveTrack) {
441
+ return anyGrantedDevice ? 'granted' : 'denied';
418
442
  }
419
- return granted ? 'granted' : 'prompt';
443
+ return anyGrantedDevice ? 'granted' : 'prompt';
420
444
  };
421
- /**
422
- * A wrapper for `navigator.permissions.query` with fallback to use
423
- * `navigator.mediaDevices.enumerateDevices` to guess the `PermissionState`
424
- *
425
- * @param anyActiveStream - Has ever got an active stream to help the fallback to
426
- * guess the state more accurately
427
- */
428
- export const getInputDevicePermissionState = async (anyActiveStream = false) => {
445
+ export const queryPermissionFallback = async (name, everActiveTrack = false, getDevices = () => navigator.mediaDevices.enumerateDevices()) => {
446
+ const devices = await getDevices();
447
+ const kind = name === 'camera' ? 'videoinput' : 'audioinput';
448
+ return toPermissionState(devices.some(device => device.kind === kind && isDeviceGranted(device)), everActiveTrack);
449
+ };
450
+ export const queryPermission = async (name, everActiveTrack = false, getDevices = () => navigator.mediaDevices.enumerateDevices()) => {
429
451
  try {
430
452
  // @ts-expect-error -- Typings/dom.d.ts change blocks upgrade to typescript 4.4
431
- const video = await navigator.permissions.query({ name: 'camera' });
432
- // @ts-expect-error -- Typings/dom.d.ts change blocks upgrade to typescript 4.4
433
- const audio = await navigator.permissions.query({ name: 'microphone' });
434
- return { video: video.state, audio: audio.state };
453
+ const { state } = await navigator.permissions.query({ name });
454
+ // Firefox v133 added permission API but always returns granted but
455
+ // based on the device label, it is actually not granted, so we need to
456
+ // fallback to use `enumerateDevices` to guess the state
457
+ if (state === 'granted') {
458
+ return await queryPermissionFallback(name, everActiveTrack, getDevices);
459
+ }
460
+ return state;
435
461
  }
436
462
  catch {
437
- const devices = await getDevices();
438
- return ['videoinput', 'audioinput'].reduce((permission, kind) => {
439
- const state = toPermissionState(devices.filter(device => device.kind === kind), anyActiveStream);
440
- permission[kind === 'audioinput' ? 'audio' : 'video'] = state;
441
- return permission;
442
- }, { video: 'prompt', audio: 'prompt' });
463
+ return await queryPermissionFallback(name, everActiveTrack, getDevices);
443
464
  }
444
465
  };
466
+ /**
467
+ * A wrapper for `navigator.permissions.query` with fallback to use
468
+ * `navigator.mediaDevices.enumerateDevices` to guess the `PermissionState`
469
+ *
470
+ * @param anyActiveAudioTrack - Has ever got an active audio to help the fallback to
471
+ * guess the state more accurately
472
+ * @param anyActiveVideoTrack - Has ever got an active video to help the fallback to
473
+ * guess the state more accurately
474
+ * @param getDevices - A function to get the list of devices, e.g. navigator.mediaDevices.enumerateDevices
475
+ */
476
+ export const getInputDevicePermissionState = async (anyActiveAudioTrack = false, anyActiveVideoTrack = false, getDevices = () => navigator.mediaDevices.enumerateDevices()) => ({
477
+ audio: await queryPermission('microphone', anyActiveAudioTrack, getDevices),
478
+ video: await queryPermission('camera', anyActiveVideoTrack, getDevices),
479
+ });
445
480
  /**
446
481
  * Find current audio output id to be used to set as sinkId
447
482
  *
@@ -1,6 +1,5 @@
1
- import { MediaDeviceFailure } from './types';
2
1
  import { getDevices } from './devices';
3
- import { getMediaConstraints, relaxInputConstraint } from './constraints';
2
+ import { getMediaConstraints } from './constraints';
4
3
  import { normalizeGetUserMediaError } from './errors';
5
4
  import { logger } from './logger';
6
5
  /**
@@ -11,25 +10,16 @@ import { logger } from './logger';
11
10
  * @beta
12
11
  */
13
12
  export const getMediaStream = async (request) => {
14
- if (!request.audio && !request.video) {
15
- throw new Error(MediaDeviceFailure.MissingConstraintsError);
16
- }
17
- const devices = await getDevices();
18
- const audio = relaxInputConstraint(request.audio, devices);
19
- const video = relaxInputConstraint(request.video, devices);
20
13
  const constraints = getMediaConstraints({
21
- audio,
22
- video,
14
+ audio: request.audio,
15
+ video: request.video,
23
16
  defaultConstraints: request.getDefaultConstraints(),
24
17
  });
25
- logger.debug({ request, constraints, devices }, 'Constraints used for getting media');
18
+ logger.debug({ request, constraints }, 'Constraints used for getting media');
26
19
  try {
27
20
  // Track device changes before and after the getUserMedia call to keep
28
21
  // track of authorized/unauthorized devices
29
- navigator.mediaDevices.dispatchEvent(new Event('devicechange'));
30
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
31
- navigator.mediaDevices.dispatchEvent(new Event('devicechange'));
32
- return stream;
22
+ return await navigator.mediaDevices.getUserMedia(constraints);
33
23
  }
34
24
  catch (error) {
35
25
  if (error instanceof Error) {
package/dist/index.d.ts CHANGED
@@ -7,13 +7,13 @@
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
- import { DeviceChangedChanges, areMultipleFacingModeSupported, areTracksEnabled, createTrackDevicesChanges, deviceChanged, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDevices, getInputDevicePermissionState, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, muteStreamTrack, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike } from './devices';
11
- import { compareDevices, findDevice, isAudioOutput, isVideoInput, isAudioInput, isDeviceGranted } from './utils';
12
- import { mergeConstraints, relaxInputConstraint, findDeviceFromConstraints, isExactDeviceConstraint, extractConstraintsWithKeys, applyConstraints, getValueFromConstrainNumber, getFacingModeFromConstraintString, extractConstrainDevice } from './constraints';
10
+ import { DeviceChangedChanges, areMultipleFacingModeSupported, areTracksEnabled, createTrackDevicesChanges, deviceChanged, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDevices, getInputDevicePermissionState, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, isStreamingRequestingDevice, isTrackMutedOrEnded, muteStreamTrack, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, toMediaDeviceInputKind } from './devices';
11
+ import { compareDevices, createIndexedDevices, findDevice, getDeviceId, isAudioInput, isAudioOutput, isDeviceGranted, isVideoInput, reverseDeviceId } from './utils';
12
+ import { applyConstraints, extractConstrainDevice, extractConstraintsWithKeys, findDeviceFromConstraints, getFacingModeFromConstraintString, getValueFromConstrainNumber, isExactDeviceConstraint, mergeConstraints, relaxInputConstraint, resolveMediaDeviceConstraints } from './constraints';
13
13
  import { MediaEvent, Events, MediaEventType } from './eventEmitter';
14
14
  import { createStreamTrackEventSubscriptions } from './streams';
15
- import { MediaDeviceRequest, MediaDeviceFailure, MediaDeviceKinds, MediaDeviceInfoLike, MediaStreamTrackLike, MediaInput, Unsubscribe, StreamTrackEventHandlers, InputConstraintSet, FacingMode, DisplayMediaOptions } from './types';
16
- import { isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, isFacingMode } from './typeGuards';
15
+ import { DisplayMediaOptions, FacingMode, IndexedDevices, InputConstraintSet, InputDeviceConstraint, MediaDeviceFailure, MediaDeviceInfoLike, MediaDeviceKinds, MediaDeviceRequest, MediaInput, MediaStreamTrackLike, StreamTrackEventHandlers, Unsubscribe } from './types';
16
+ import { isFacingMode, isIndexedDevices, isInputConstraintSet, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack } from './typeGuards';
17
17
  export { setLogger } from './logger';
18
18
  declare const
19
19
  /**
@@ -33,7 +33,7 @@ setDefaultConstraints: (newConstraints: MediaStreamConstraints) => void,
33
33
  */
34
34
  subscribe: (listener: (event: MediaEvent) => void) => () => void;
35
35
  export { getUserMedia, setDefaultConstraints, subscribe };
36
- export { DeviceChangedChanges, DisplayMediaOptions, Events, FacingMode, InputConstraintSet, MediaDeviceFailure, MediaDeviceInfoLike, MediaDeviceKinds, MediaDeviceRequest, MediaEvent, MediaEventType, MediaInput, MediaStreamTrackLike, StreamTrackEventHandlers, Unsubscribe, applyConstraints, areMultipleFacingModeSupported, areTracksEnabled, compareDevices, createStreamTrackEventSubscriptions, createTrackDevicesChanges, deviceChanged, extractConstraintsWithKeys, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDevice, findDeviceFromConstraints, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDevices, getFacingModeFromConstraintString, getInputDevicePermissionState, getValueFromConstrainNumber, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isAudioInput, isAudioOutput, isDeviceGranted, isExactDeviceConstraint, isFacingMode, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, isVideoInput, mergeConstraints, muteStreamTrack, relaxInputConstraint, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, extractConstrainDevice, };
36
+ export { DeviceChangedChanges, DisplayMediaOptions, Events, FacingMode, IndexedDevices, InputConstraintSet, InputDeviceConstraint, MediaDeviceFailure, MediaDeviceInfoLike, MediaDeviceKinds, MediaDeviceRequest, MediaEvent, MediaEventType, MediaInput, MediaStreamTrackLike, StreamTrackEventHandlers, Unsubscribe, applyConstraints, areMultipleFacingModeSupported, areTracksEnabled, compareDevices, createIndexedDevices, createStreamTrackEventSubscriptions, createTrackDevicesChanges, deviceChanged, extractConstrainDevice, extractConstraintsWithKeys, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDevice, findDeviceFromConstraints, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDeviceId, getDevices, getFacingModeFromConstraintString, getInputDevicePermissionState, getValueFromConstrainNumber, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isAudioInput, isAudioOutput, isDeviceGranted, isExactDeviceConstraint, isFacingMode, isIndexedDevices, isInputConstraintSet, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, isStreamingRequestingDevice, isTrackMutedOrEnded, isVideoInput, mergeConstraints, muteStreamTrack, relaxInputConstraint, resolveMediaDeviceConstraints, reverseDeviceId, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, toMediaDeviceInputKind, };
37
37
  export * from './constants';
38
38
  export * from './displayMedia';
39
39
  /**
package/dist/index.js CHANGED
@@ -7,13 +7,13 @@
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
- import { areMultipleFacingModeSupported, areTracksEnabled, createTrackDevicesChanges, deviceChanged, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDevices, getInputDevicePermissionState, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, muteStreamTrack, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, } from './devices';
11
- import { compareDevices, findDevice, isAudioOutput, isVideoInput, isAudioInput, isDeviceGranted, } from './utils';
12
- import { mergeConstraints, getConstraintsHandlers, relaxInputConstraint, findDeviceFromConstraints, isExactDeviceConstraint, extractConstraintsWithKeys, applyConstraints, getValueFromConstrainNumber, getFacingModeFromConstraintString, extractConstrainDevice, } from './constraints';
10
+ import { areMultipleFacingModeSupported, areTracksEnabled, createTrackDevicesChanges, deviceChanged, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDevices, getInputDevicePermissionState, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, isStreamingRequestingDevice, isTrackMutedOrEnded, muteStreamTrack, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, toMediaDeviceInputKind, } from './devices';
11
+ import { compareDevices, createIndexedDevices, findDevice, getDeviceId, isAudioInput, isAudioOutput, isDeviceGranted, isVideoInput, reverseDeviceId, } from './utils';
12
+ import { applyConstraints, extractConstrainDevice, extractConstraintsWithKeys, findDeviceFromConstraints, getConstraintsHandlers, getFacingModeFromConstraintString, getValueFromConstrainNumber, isExactDeviceConstraint, mergeConstraints, relaxInputConstraint, resolveMediaDeviceConstraints, } from './constraints';
13
13
  import { eventEmitter, MediaEventType } from './eventEmitter';
14
14
  import { handleMediaStream, createStreamTrackEventSubscriptions, } from './streams';
15
15
  import { MediaDeviceFailure, MediaDeviceKinds, } from './types';
16
- import { isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, isFacingMode, } from './typeGuards';
16
+ import { isFacingMode, isIndexedDevices, isInputConstraintSet, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, } from './typeGuards';
17
17
  import { createStreamTrackMap } from './streamTrackMap';
18
18
  export { setLogger } from './logger';
19
19
  const state = () => {
@@ -100,6 +100,6 @@ setDefaultConstraints,
100
100
  */
101
101
  subscribe, } = state();
102
102
  export { getUserMedia, setDefaultConstraints, subscribe };
103
- export { MediaDeviceFailure, MediaDeviceKinds, MediaEventType, applyConstraints, areMultipleFacingModeSupported, areTracksEnabled, compareDevices, createStreamTrackEventSubscriptions, createTrackDevicesChanges, deviceChanged, extractConstraintsWithKeys, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDevice, findDeviceFromConstraints, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDevices, getFacingModeFromConstraintString, getInputDevicePermissionState, getValueFromConstrainNumber, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isAudioInput, isAudioOutput, isDeviceGranted, isExactDeviceConstraint, isFacingMode, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, isVideoInput, mergeConstraints, muteStreamTrack, relaxInputConstraint, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, extractConstrainDevice, };
103
+ export { MediaDeviceFailure, MediaDeviceKinds, MediaEventType, applyConstraints, areMultipleFacingModeSupported, areTracksEnabled, compareDevices, createIndexedDevices, createStreamTrackEventSubscriptions, createTrackDevicesChanges, deviceChanged, extractConstrainDevice, extractConstraintsWithKeys, findAudioInputDevices, findAudioOutputDevices, findCurrentAudioOutputId, findCurrentVideoInputDeviceIdFromStream, findDevice, findDeviceFromConstraints, findDeviceWithDeviceId, findDevicesByKind, findMediaInputFromMediaStreamTrack, findMediaInputFromStream, findPermissionGrantedDevices, findVideoInputDevices, getDeviceId, getDevices, getFacingModeFromConstraintString, getInputDevicePermissionState, getValueFromConstrainNumber, hasAnyGrantedInput, hasAnyInputs, hasAudioInputs, hasAudioOrVideoInputs, hasChangedInput, hasRequestingDevice, hasVideoInputs, interpretCurrentFacingMode, isAudioInput, isAudioOutput, isDeviceGranted, isExactDeviceConstraint, isFacingMode, isIndexedDevices, isInputConstraintSet, isMediaDeviceInfo, isMediaDeviceInfoArray, isMediaStreamTrack, isRequestedInputDevice, isRequestedInputTrack, isRequestedResolution, isStreamingRequestedDevices, isStreamingRequestedDevicesBase, isStreamingRequestingDevice, isTrackMutedOrEnded, isVideoInput, mergeConstraints, muteStreamTrack, relaxInputConstraint, resolveMediaDeviceConstraints, reverseDeviceId, shouldRequestDevice, stopMediaStream, toKey, toMediaDeviceInfo, toMediaDeviceInfoLike, toMediaDeviceInputKind, };
104
104
  export * from './constants';
105
105
  export * from './displayMedia';
@@ -1,4 +1,4 @@
1
- import type { ConstraintDeviceParameters, DeviceConstraint, InputConstraintSet } from './types';
1
+ import type { ConstraintDeviceParameters, DeviceConstraint, IndexedDevices, InputConstraintSet, MediaDeviceTrackInfo } from './types';
2
2
  export declare const isBoolean: (t: unknown) => t is boolean;
3
3
  export declare const isUndefined: (t: unknown) => t is undefined;
4
4
  /**
@@ -86,4 +86,6 @@ export declare const isInputConstraintSet: (t: unknown) => t is InputConstraintS
86
86
  */
87
87
  export declare const isMediaStreamTrack: (m: unknown) => m is MediaStreamTrack;
88
88
  export declare const isFacingMode: (s: unknown) => s is "user" | "environment" | "left" | "right";
89
+ export declare const isIndexedDevices: (t: unknown) => t is IndexedDevices;
90
+ export declare const isMediaDeviceTrackInfo: (t: unknown) => t is MediaDeviceTrackInfo;
89
91
  export {};
@@ -146,6 +146,12 @@ export const isConstraintDeviceParameters = isConstrainDOMParameters(t => isMedi
146
146
  export const isConstraintSetDevice = (t) => isMediaDeviceInfo(t) ||
147
147
  isMediaDeviceInfoArray(t) ||
148
148
  isConstraintDeviceParameters(t);
149
+ const EXTENDED_CONSTRAIN_KEYS = [
150
+ ...EXTENDED_CONSTRAIN_DOUBLE_KEYS,
151
+ ...EXTENDED_CONSTRAIN_STRING_KEYS,
152
+ ...EXTENDED_CONSTRAIN_U_LONG_KEYS,
153
+ ...EXTENDED_CONSTRAIN_BOOLEAN_KEYS,
154
+ ];
149
155
  export const isExtendedConstraint = (t) => {
150
156
  if (typeof t !== 'object' || t === null) {
151
157
  return false;
@@ -154,12 +160,7 @@ export const isExtendedConstraint = (t) => {
154
160
  const { device } = t;
155
161
  return isConstraintSetDevice(device);
156
162
  }
157
- return [
158
- ...EXTENDED_CONSTRAIN_DOUBLE_KEYS,
159
- ...EXTENDED_CONSTRAIN_STRING_KEYS,
160
- ...EXTENDED_CONSTRAIN_U_LONG_KEYS,
161
- ...EXTENDED_CONSTRAIN_BOOLEAN_KEYS,
162
- ].some(key => hasOwn(t, key));
163
+ return EXTENDED_CONSTRAIN_KEYS.some(key => hasOwn(t, key));
163
164
  };
164
165
  export const isInputConstraintSet = (t) => {
165
166
  if (typeof t !== 'object' || t === null) {
@@ -184,3 +185,25 @@ export const isFacingMode = (s) => {
184
185
  }
185
186
  return false;
186
187
  };
188
+ export const isIndexedDevices = (t) => {
189
+ if (typeof t === 'object' && t !== null) {
190
+ if ('get' in t &&
191
+ 'size' in t &&
192
+ typeof t.get === 'function' &&
193
+ typeof t.size === 'function') {
194
+ return true;
195
+ }
196
+ }
197
+ return false;
198
+ };
199
+ export const isMediaDeviceTrackInfo = (t) => {
200
+ if (typeof t === 'object' && t !== null) {
201
+ return ('deviceId' in t &&
202
+ 'groupId' in t &&
203
+ 'kind' in t &&
204
+ 'label' in t &&
205
+ 'id' in t &&
206
+ 'settings' in t);
207
+ }
208
+ return false;
209
+ };
package/dist/types.d.ts CHANGED
@@ -4,9 +4,33 @@
4
4
  * @beta
5
5
  */
6
6
  export type MediaDeviceInfoLike = Omit<MediaDeviceInfo, 'toJSON'> & {
7
- settings?: MediaTrackSettings;
8
7
  toJSON?: MediaDeviceInfo['toJSON'];
9
8
  };
9
+ export type MediaDeviceTrackInfo = MediaDeviceInfoLike & {
10
+ id: string;
11
+ settings: MediaTrackSettings;
12
+ };
13
+ export type IndexedDeviceMap = {
14
+ [Key in MediaDeviceInfo['kind']]: {
15
+ deviceId: Map<MediaDeviceInfoLike['deviceId'], MediaDeviceInfoLike>;
16
+ label: Map<MediaDeviceInfoLike['label'], MediaDeviceInfoLike[]>;
17
+ size: number;
18
+ };
19
+ };
20
+ export interface IndexedDevices {
21
+ /**
22
+ * Get devices by kind and device id or label
23
+ * When both `deviceId` and `label` are provided, it will use `deviceId` to
24
+ * to find the device, and when it's not found, it will use `label` to find
25
+ *
26
+ * @param device - the device to find
27
+ * @returns found devices otherwise empty array
28
+ */
29
+ get(device?: Pick<Partial<MediaDeviceInfoLike>, 'deviceId' | 'groupId' | 'label' | 'kind'>): MediaDeviceInfoLike[];
30
+ size(kind?: MediaDeviceKind): number;
31
+ first(kind: MediaDeviceInfo['kind']): MediaDeviceInfoLike | undefined;
32
+ toJSON(): Record<MediaDeviceKind, MediaDeviceInfoLike[]>;
33
+ }
10
34
  export type DeviceConstraint = MediaDeviceInfoLike | MediaDeviceInfoLike[];
11
35
  export interface NormalizedConstraintDeviceParameters {
12
36
  exact?: MediaDeviceInfoLike[];
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MediaDeviceInfoLike } from './types';
1
+ import type { MediaDeviceInfoLike, IndexedDevices } from './types';
2
2
  import { MediaDeviceKinds } from './types';
3
3
  export declare const isSameDeviceKind: (kind: MediaDeviceKinds) => (device: MediaDeviceInfoLike) => boolean;
4
4
  export declare const isAudioInput: (device: MediaDeviceInfoLike) => boolean;
@@ -14,6 +14,31 @@ export declare const isAudioOutput: (device: MediaDeviceInfoLike) => boolean;
14
14
  **/
15
15
  export declare const isDeviceGranted: (device: MediaDeviceInfoLike) => boolean;
16
16
  export declare const not: <T = void>(fn: (arg: T) => boolean) => (arg: T) => boolean;
17
+ /**
18
+ * Create a device ID from provided `device`.
19
+ * The device ID here is in the format of `groupId:deviceId:kind`, be default.
20
+ *
21
+ * @param device - The device to create the ID from
22
+ * @param separator - The separator to use to join the device ID
23
+ */
24
+ export declare const getDeviceId: (device: Pick<MediaDeviceInfoLike, 'groupId' | 'deviceId' | 'kind'>, separator?: string) => string;
25
+ /**
26
+ * Reverse the device ID to its original form i.e. `MediaDeviceInfo`
27
+ * The device ID here is in the format of `groupId:deviceId:kind`, be default.
28
+ *
29
+ * @param id - The device ID to reverse
30
+ * @param separator - The separator used to split the device ID
31
+ */
32
+ export declare const reverseDeviceId: (id: string, separator?: string) => {
33
+ deviceId: string;
34
+ groupId: string;
35
+ kind: MediaDeviceKind;
36
+ };
37
+ /**
38
+ * Create an `IndexedDevices` construct from provided `devices` which index the
39
+ * device by device ID or kind.
40
+ */
41
+ export declare const createIndexedDevices: (devices: MediaDeviceInfoLike[]) => IndexedDevices;
17
42
  /**
18
43
  * Compare 2 devices to see if they are the same by `deviceId` or `label` as
19
44
  * a fallback
@@ -22,7 +47,7 @@ export declare const not: <T = void>(fn: (arg: T) => boolean) => (arg: T) => boo
22
47
  */
23
48
  export declare const compareDevices: (deviceInfo: MediaDeviceInfoLike, key?: Exclude<keyof MediaDeviceInfoLike, 'kind' | 'settings'>) => (anotherDeviceInfo: MediaDeviceInfoLike) => boolean;
24
49
  /**
25
- * Lookup the the `device` from `devices` list
50
+ * Lookup the `device` from `devices` list
26
51
  *
27
52
  * @remarks
28
53
  * When we cannot find the device by `deviceId`, we compare the label as last
package/dist/utils.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { assert } from '@pexip/utils';
1
2
  import { MediaDeviceKinds } from './types';
3
+ import { DEVICE_ID_SEPARATOR } from './constants';
2
4
  export const isSameDeviceKind = (kind) => (device) => device.kind === kind;
3
5
  export const isAudioInput = isSameDeviceKind(MediaDeviceKinds.AUDIOINPUT);
4
6
  export const isVideoInput = isSameDeviceKind(MediaDeviceKinds.VIDEOINPUT);
@@ -13,6 +15,126 @@ export const isAudioOutput = isSameDeviceKind(MediaDeviceKinds.AUDIOOUTPUT);
13
15
  **/
14
16
  export const isDeviceGranted = (device) => Boolean(device.label);
15
17
  export const not = (fn) => (arg) => !fn(arg);
18
+ /**
19
+ * Create a device ID from provided `device`.
20
+ * The device ID here is in the format of `groupId:deviceId:kind`, be default.
21
+ *
22
+ * @param device - The device to create the ID from
23
+ * @param separator - The separator to use to join the device ID
24
+ */
25
+ export const getDeviceId = (device, separator = DEVICE_ID_SEPARATOR) => [device.groupId, device.deviceId, device.kind].join(separator);
26
+ /**
27
+ * Reverse the device ID to its original form i.e. `MediaDeviceInfo`
28
+ * The device ID here is in the format of `groupId:deviceId:kind`, be default.
29
+ *
30
+ * @param id - The device ID to reverse
31
+ * @param separator - The separator used to split the device ID
32
+ */
33
+ export const reverseDeviceId = (id, separator = DEVICE_ID_SEPARATOR) => {
34
+ const [groupId, deviceId, kind] = id.split(separator);
35
+ assert(groupId !== undefined, `Invalid id[${id}] format: groupId`);
36
+ assert(deviceId !== undefined, `Invalid id[${id}] format: deviceId`);
37
+ assert(kind === 'audioinput' ||
38
+ kind === 'videoinput' ||
39
+ kind === 'audiooutput', `Invalid id[${id}] format: kind`);
40
+ return { deviceId, groupId, kind: kind };
41
+ };
42
+ /**
43
+ * Create an `IndexedDevices` construct from provided `devices` which index the
44
+ * device by device ID or kind.
45
+ */
46
+ export const createIndexedDevices = (devices) => {
47
+ const emptyIndex = {
48
+ audioinput: {
49
+ deviceId: new Map(),
50
+ label: new Map(),
51
+ get size() {
52
+ return this.deviceId.size;
53
+ },
54
+ },
55
+ audiooutput: {
56
+ deviceId: new Map(),
57
+ label: new Map(),
58
+ get size() {
59
+ return this.deviceId.size;
60
+ },
61
+ },
62
+ videoinput: {
63
+ deviceId: new Map(),
64
+ label: new Map(),
65
+ get size() {
66
+ return this.deviceId.size;
67
+ },
68
+ },
69
+ size(kind) {
70
+ switch (kind) {
71
+ case 'audioinput':
72
+ return this.audioinput.size;
73
+ case 'audiooutput':
74
+ return this.audiooutput.size;
75
+ case 'videoinput':
76
+ return this.videoinput.size;
77
+ default:
78
+ return (this.audioinput.size +
79
+ this.audiooutput.size +
80
+ this.videoinput.size);
81
+ }
82
+ },
83
+ get(deviceToFind) {
84
+ const { deviceId, kind, label } = deviceToFind ?? {};
85
+ if (!kind) {
86
+ return [
87
+ ...this.get({ ...deviceToFind, kind: 'audioinput' }),
88
+ ...this.get({ ...deviceToFind, kind: 'audiooutput' }),
89
+ ...this.get({ ...deviceToFind, kind: 'videoinput' }),
90
+ ];
91
+ }
92
+ const deviceIdMap = this[kind].deviceId;
93
+ const findSimilarDevices = (label) => {
94
+ const foundSimilar = this[kind].label.get(label);
95
+ return foundSimilar ? foundSimilar : [];
96
+ };
97
+ if (deviceId !== undefined) {
98
+ const foundExact = deviceIdMap.get(deviceId);
99
+ if (!foundExact && label) {
100
+ return findSimilarDevices(label);
101
+ }
102
+ return foundExact ? [foundExact] : [];
103
+ }
104
+ if (label) {
105
+ return findSimilarDevices(label);
106
+ }
107
+ return [...deviceIdMap.values()];
108
+ },
109
+ first(kind) {
110
+ if (!kind) {
111
+ return;
112
+ }
113
+ for (const device of this[kind].deviceId.values()) {
114
+ return device;
115
+ }
116
+ },
117
+ toJSON() {
118
+ return {
119
+ audioinput: this.get({ kind: 'audioinput' }),
120
+ videoinput: this.get({ kind: 'videoinput' }),
121
+ audiooutput: this.get({ kind: 'audiooutput' }),
122
+ };
123
+ },
124
+ };
125
+ return devices.reduce((acc, device) => {
126
+ // We don't want to index devices that are not granted
127
+ if (device.label && device.deviceId !== undefined) {
128
+ acc[device.kind].deviceId.set(device.deviceId, device);
129
+ }
130
+ if (device.label) {
131
+ const labelDevices = acc[device.kind].label.get(device.label) ?? [];
132
+ labelDevices.push(device);
133
+ acc[device.kind].label.set(device.label, labelDevices);
134
+ }
135
+ return acc;
136
+ }, emptyIndex);
137
+ };
16
138
  /**
17
139
  * Compare 2 devices to see if they are the same by `deviceId` or `label` as
18
140
  * a fallback
@@ -22,7 +144,7 @@ export const not = (fn) => (arg) => !fn(arg);
22
144
  export const compareDevices = (deviceInfo, key = 'deviceId') => (anotherDeviceInfo) => deviceInfo.kind === anotherDeviceInfo.kind &&
23
145
  deviceInfo[key] === anotherDeviceInfo[key];
24
146
  /**
25
- * Lookup the the `device` from `devices` list
147
+ * Lookup the `device` from `devices` list
26
148
  *
27
149
  * @remarks
28
150
  * When we cannot find the device by `deviceId`, we compare the label as last
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pexip/media-control",
3
- "version": "18.5.0",
3
+ "version": "19.0.0",
4
4
  "homepage": "https://gitlab.com/pexip/zoo",
5
5
  "bugs": "https://gitlab.com/pexip/zoo/issues",
6
6
  "repository": {
@@ -28,10 +28,11 @@
28
28
  "typecheck": "yarn tsc --noEmit -p tsconfig.build.json"
29
29
  },
30
30
  "dependencies": {
31
- "@pexip/utils": "16.12.1"
31
+ "@pexip/utils": "16.13.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@pexip/bundler": "17.0.0",
34
+ "@jest/globals": "^29.7.0",
35
+ "@pexip/bundler": "17.1.0",
35
36
  "@swc/core": "^1.4.6",
36
37
  "@swc/jest": "^0.2.36",
37
38
  "@types/uuid": "^8.3.0",
@@ -40,7 +41,7 @@
40
41
  "jest-junit": "^16.0.0",
41
42
  "prettier": "^3.2.5",
42
43
  "typescript": "~5.3.0",
43
- "uuid": "^9.0.0"
44
+ "uuid": "^11.0.0"
44
45
  },
45
46
  "publishConfig": {
46
47
  "access": "public",