How to select the correct camera with autofocus?
When we use ngx-scanner with the smartphone, we have several cameras, i.e.:
- camera2 1, facing front
- camera2 3, facing front
- camera2 2, facing back
- camera2 0, facing back
When we use "camera2 2, facing back", it does not work to scan small QR codes because the autofocus is not active.
But when we use "camera2 0, facing back", it works to scan small QR codes because the autofocus is active.
How we can find out which camera to take to have autofocus?
Do we have other possiblities to find out more informations about all available devices to select automatically the correct one?
Thanks very much.
Best regards Mike
I had to write a few workarounds to get it how I want, but i don't use the autostart anymore
either with: window.navigator.mediaDevices.getUserMedia({ video: true, facingMode: 'environment' })
or window.navigator.mediaDevices.enumerateDevices();
note that with the first one you have to cleanup your streams before changing to the device, or the camera won't work. with the second you'll have to loop over all of them to get the capabillities of a device with the getCapabilities function.
I had the same issue. My solution was to enumerate all the devices and find the one with the shortest focusDistance, as we're likely to be scanning codes close to the device (See #517). I also had to add delays because the scanner wasn't registering the device change (See #485).
The process involved:
- Ensure the
enabledattribute is set to false on theZXingScannerComponent - Enumerate the devices.
getUserMedia()for the device- Enumerate the tracks
getCapabilities()for each track- Determine which device has the capability with the shortest
focalDistance.minvalue - Once we have the device, set it
- After a delay (~ 2s), ~~start~~ enable the scanner
This is my code for querying the capabilities of the devices:
export type MediaTrackCapabilitiesMap = { [key: string]: MediaTrackCapabilities[] };
interface FocusDistance {
min: number,
max: number,
step: number
}
async function getCameraWithClosestFocus(): Promise<string | undefined> {
const capabilities = await getCapabilities();
let minFocusDistance = Number.MAX_SAFE_INTEGER;
let bestDeviceId: string | undefined;
Object.entries(capabilities).forEach(([id, values]) => {
const focusDistance = getFocusDistance(values);
if (focusDistance && focusDistance.min < minFocusDistance) {
minFocusDistance = focusDistance.min;
bestDeviceId = id;
}
});
return bestDeviceId;
}
async function getCameras(): Promise<MediaDeviceInfo[] | undefined> {
if (!navigator.mediaDevices.enumerateDevices) {
return undefined;
}
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter((x) => x.kind === "videoinput");
}
function getCapability<T>(value: MediaTrackCapabilities, key: string): T | undefined {
if (key in value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
return (value as unknown as any)[key] as T | undefined;
}
return undefined;
}
function getCapabilities(value: string): Promise<MediaTrackCapabilities[] | undefined>;
function getCapabilities(value: MediaDeviceInfo): Promise<MediaTrackCapabilities[]>;
function getCapabilities(): Promise<MediaTrackCapabilitiesMap>;
async function getCapabilities(value: MediaDeviceInfo | string | undefined = undefined): Promise<MediaTrackCapabilitiesMap | MediaTrackCapabilities[] | undefined> {
if (!value) {
const devices = await getCameras();
const resp: MediaTrackCapabilitiesMap = {};
if (devices?.length) {
for (const device of devices) {
const capabilities = await getCapabilities(device);
resp[device.deviceId] = capabilities;
}
}
return resp;
} else {
// Single device
const device = typeof value === "string" ? (await getCameras())?.find((x) => x.deviceId === value) : value;
if (!device) {
return undefined;
}
const media = await navigator.mediaDevices.getUserMedia({ video: device });
const tracks = media.getTracks();
const capabilities = tracks.map((track) => track.getCapabilities());
tracks.forEach((track) => track.stop());
return capabilities;
}
}
function getFocusDistance(value: MediaTrackCapabilities[]): FocusDistance | undefined {
return value
.map((x) => {
return getCapability<{ min: number, max: number, step: number }>(x, "focusDistance");
})
.find((x): x is FocusDistance => x !== undefined)
;
}
function getHasAutoFocus(value: MediaTrackCapabilities[]): boolean {
return value.some((x) => {
const focusMode = getCapability<string[]>(x, "focusMode");
return focusMode?.includes("auto") || false;
});
}
export {
getCameraWithClosestFocus,
getCameras,
getCapabilities,
};