|
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 | +} |
0 commit comments