livekit-client 1.11.2 → 1.11.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,6 @@
1
1
  import { sleep } from '../utils';
2
+ import log from './../../logger';
3
+ import LocalTrack from './LocalTrack';
2
4
  import type { AudioCaptureOptions, CreateLocalTracksOptions, VideoCaptureOptions } from './options';
3
5
  import type { AudioTrack } from './types';
4
6
 
@@ -112,3 +114,103 @@ export function getNewAudioContext(): AudioContext | void {
112
114
  return new AudioContext({ latencyHint: 'interactive' });
113
115
  }
114
116
  }
117
+
118
+ type FacingMode = NonNullable<VideoCaptureOptions['facingMode']>;
119
+ type FacingModeFromLocalTrackOptions = {
120
+ /**
121
+ * If no facing mode can be determined, this value will be used.
122
+ * @defaultValue 'user'
123
+ */
124
+ defaultFacingMode?: FacingMode;
125
+ };
126
+ type FacingModeFromLocalTrackReturnValue = {
127
+ /**
128
+ * The (probable) facingMode of the track.
129
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode}
130
+ */
131
+ facingMode: FacingMode;
132
+ /**
133
+ * The confidence that the returned facingMode is correct.
134
+ */
135
+ confidence: 'high' | 'medium' | 'low';
136
+ };
137
+
138
+ /**
139
+ * Try to analyze the local track to determine the facing mode of a track.
140
+ *
141
+ * @remarks
142
+ * There is no property supported by all browsers to detect whether a video track originated from a user- or environment-facing camera device.
143
+ * For this reason, we use the `facingMode` property when available, but will fall back on a string-based analysis of the device label to determine the facing mode.
144
+ * If both methods fail, the default facing mode will be used.
145
+ *
146
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode}
147
+ * @experimental
148
+ */
149
+ export function facingModeFromLocalTrack(
150
+ localTrack: LocalTrack | MediaStreamTrack,
151
+ options: FacingModeFromLocalTrackOptions = {},
152
+ ): FacingModeFromLocalTrackReturnValue {
153
+ const track = localTrack instanceof LocalTrack ? localTrack.mediaStreamTrack : localTrack;
154
+ const trackSettings = track.getSettings();
155
+ let result: FacingModeFromLocalTrackReturnValue = {
156
+ facingMode: options.defaultFacingMode ?? 'user',
157
+ confidence: 'low',
158
+ };
159
+
160
+ // 1. Try to get facingMode from track settings.
161
+ if ('facingMode' in trackSettings) {
162
+ const rawFacingMode = trackSettings.facingMode;
163
+ log.debug('rawFacingMode', { rawFacingMode });
164
+ if (rawFacingMode && typeof rawFacingMode === 'string' && isFacingModeValue(rawFacingMode)) {
165
+ result = { facingMode: rawFacingMode, confidence: 'high' };
166
+ }
167
+ }
168
+
169
+ // 2. If we don't have a high confidence we try to get the facing mode from the device label.
170
+ if (['low', 'medium'].includes(result.confidence)) {
171
+ log.debug(`Try to get facing mode from device label: (${track.label})`);
172
+ const labelAnalysisResult = facingModeFromDeviceLabel(track.label);
173
+ if (labelAnalysisResult !== undefined) {
174
+ result = labelAnalysisResult;
175
+ }
176
+ }
177
+
178
+ return result;
179
+ }
180
+
181
+ const knownDeviceLabels = new Map<string, FacingModeFromLocalTrackReturnValue>([
182
+ ['obs virtual camera', { facingMode: 'environment', confidence: 'medium' }],
183
+ ]);
184
+ const knownDeviceLabelSections = new Map<string, FacingModeFromLocalTrackReturnValue>([
185
+ ['iphone', { facingMode: 'environment', confidence: 'medium' }],
186
+ ['ipad', { facingMode: 'environment', confidence: 'medium' }],
187
+ ]);
188
+ /**
189
+ * Attempt to analyze the device label to determine the facing mode.
190
+ *
191
+ * @experimental
192
+ */
193
+ export function facingModeFromDeviceLabel(
194
+ deviceLabel: string,
195
+ ): FacingModeFromLocalTrackReturnValue | undefined {
196
+ const label = deviceLabel.trim().toLowerCase();
197
+ // Empty string is a valid device label but we can't infer anything from it.
198
+ if (label === '') {
199
+ return undefined;
200
+ }
201
+
202
+ // Can we match against widely known device labels.
203
+ if (knownDeviceLabels.has(label)) {
204
+ return knownDeviceLabels.get(label);
205
+ }
206
+
207
+ // Can we match against sections of the device label.
208
+ return Array.from(knownDeviceLabelSections.entries()).find(([section]) =>
209
+ label.includes(section),
210
+ )?.[1];
211
+ }
212
+
213
+ function isFacingModeValue(item: string): item is FacingMode {
214
+ const allowedValues: FacingMode[] = ['user', 'environment', 'left', 'right'];
215
+ return item === undefined || allowedValues.includes(item as FacingMode);
216
+ }