livekit-client
Version:
JavaScript/TypeScript client SDK for LiveKit
104 lines (94 loc) • 3.9 kB
text/typescript
import log from '../../logger';
import LocalTrack from './LocalTrack';
import type { VideoCaptureOptions } from './options';
type FacingMode = NonNullable<VideoCaptureOptions['facingMode']>;
type FacingModeFromLocalTrackOptions = {
/**
* If no facing mode can be determined, this value will be used.
* @defaultValue 'user'
*/
defaultFacingMode?: FacingMode;
};
type FacingModeFromLocalTrackReturnValue = {
/**
* The (probable) facingMode of the track.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode}
*/
facingMode: FacingMode;
/**
* The confidence that the returned facingMode is correct.
*/
confidence: 'high' | 'medium' | 'low';
};
/**
* Try to analyze the local track to determine the facing mode of a track.
*
* @remarks
* There is no property supported by all browsers to detect whether a video track originated from a user- or environment-facing camera device.
* 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.
* If both methods fail, the default facing mode will be used.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode | MDN docs on facingMode}
* @experimental
*/
export function facingModeFromLocalTrack(
localTrack: LocalTrack | MediaStreamTrack,
options: FacingModeFromLocalTrackOptions = {},
): FacingModeFromLocalTrackReturnValue {
const track = localTrack instanceof LocalTrack ? localTrack.mediaStreamTrack : localTrack;
const trackSettings = track.getSettings();
let result: FacingModeFromLocalTrackReturnValue = {
facingMode: options.defaultFacingMode ?? 'user',
confidence: 'low',
};
// 1. Try to get facingMode from track settings.
if ('facingMode' in trackSettings) {
const rawFacingMode = trackSettings.facingMode;
log.trace('rawFacingMode', { rawFacingMode });
if (rawFacingMode && typeof rawFacingMode === 'string' && isFacingModeValue(rawFacingMode)) {
result = { facingMode: rawFacingMode, confidence: 'high' };
}
}
// 2. If we don't have a high confidence we try to get the facing mode from the device label.
if (['low', 'medium'].includes(result.confidence)) {
log.trace(`Try to get facing mode from device label: (${track.label})`);
const labelAnalysisResult = facingModeFromDeviceLabel(track.label);
if (labelAnalysisResult !== undefined) {
result = labelAnalysisResult;
}
}
return result;
}
const knownDeviceLabels = new Map<string, FacingModeFromLocalTrackReturnValue>([
['obs virtual camera', { facingMode: 'environment', confidence: 'medium' }],
]);
const knownDeviceLabelSections = new Map<string, FacingModeFromLocalTrackReturnValue>([
['iphone', { facingMode: 'environment', confidence: 'medium' }],
['ipad', { facingMode: 'environment', confidence: 'medium' }],
]);
/**
* Attempt to analyze the device label to determine the facing mode.
*
* @experimental
*/
export function facingModeFromDeviceLabel(
deviceLabel: string,
): FacingModeFromLocalTrackReturnValue | undefined {
const label = deviceLabel.trim().toLowerCase();
// Empty string is a valid device label but we can't infer anything from it.
if (label === '') {
return undefined;
}
// Can we match against widely known device labels.
if (knownDeviceLabels.has(label)) {
return knownDeviceLabels.get(label);
}
// Can we match against sections of the device label.
return Array.from(knownDeviceLabelSections.entries()).find(([section]) =>
label.includes(section),
)?.[1];
}
function isFacingModeValue(item: string): item is FacingMode {
const allowedValues: FacingMode[] = ['user', 'environment', 'left', 'right'];
return item === undefined || allowedValues.includes(item as FacingMode);
}