Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to select the correct camera with autofocus? #529

Open
maerni opened this issue Jun 23, 2023 · 3 comments
Open

How to select the correct camera with autofocus? #529

maerni opened this issue Jun 23, 2023 · 3 comments
Labels

Comments

@maerni
Copy link

maerni commented Jun 23, 2023

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

@maerni maerni added the bug label Jun 23, 2023
@stefbeysdiekeure
Copy link

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.

@oobayly
Copy link

oobayly commented Sep 1, 2023

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:

  1. Ensure the enabled attribute is set to false on the ZXingScannerComponent
  2. Enumerate the devices.
  3. getUserMedia() for the device
  4. Enumerate the tracks
  5. getCapabilities() for each track
  6. Determine which device has the capability with the shortest focalDistance.min value
  7. Once we have the device, set it
  8. 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,
};

@csabe812
Copy link

csabe812 commented Oct 7, 2024

@oobayly your solution looks good. Thanks man. I just have one more question:
I am planning to put this code inside (camerasFound) attribute, so it would run after view was initialized.
Do I still need to put a delay? Btw where did you try to add?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants