-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* expose facingMode functions * add changeset
- Loading branch information
Showing
6 changed files
with
140 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"livekit-client": patch | ||
--- | ||
|
||
expose facingMode functions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { facingModeFromDeviceLabel } from './facingMode'; | ||
|
||
describe('Test facingMode detection', () => { | ||
test('OBS virtual camera should be detected.', () => { | ||
const result = facingModeFromDeviceLabel('OBS Virtual Camera'); | ||
expect(result?.facingMode).toEqual('environment'); | ||
expect(result?.confidence).toEqual('medium'); | ||
}); | ||
|
||
test.each([ | ||
['Peter’s iPhone Camera', { facingMode: 'environment', confidence: 'medium' }], | ||
['iPhone de Théo Camera', { facingMode: 'environment', confidence: 'medium' }], | ||
])( | ||
'Device labels that contain "iphone" should return facingMode "environment".', | ||
(label, expected) => { | ||
const result = facingModeFromDeviceLabel(label); | ||
expect(result?.facingMode).toEqual(expected.facingMode); | ||
expect(result?.confidence).toEqual(expected.confidence); | ||
}, | ||
); | ||
|
||
test.each([ | ||
['Peter’s iPad Camera', { facingMode: 'environment', confidence: 'medium' }], | ||
['iPad de Théo Camera', { facingMode: 'environment', confidence: 'medium' }], | ||
])('Device label that contain "ipad" should detect.', (label, expected) => { | ||
const result = facingModeFromDeviceLabel(label); | ||
expect(result?.facingMode).toEqual(expected.facingMode); | ||
expect(result?.confidence).toEqual(expected.confidence); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import log from 'loglevel'; | ||
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.debug('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.debug(`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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters