Skip to content

Android: barcode scanner has no camera selection - always opens the default rear lens, which can be a fixed-focus one #71

Closed

Description

@AFT-syd

Summary

On a phone with several rear cameras the scanner opens a camera that cannot focus, and there is no way to switch to a different one. The barcode never becomes sharp, so scanning is impossible.

Environment

  • LibrisLog ghcr.io/codebude/librislog/librislog:latest, image built 2026-06-22 (API image same build date), self-hosted behind nginx with a trusted internal CA — HTTPS/secure context is fine, permission is granted and the camera starts
  • Huawei P30, Android
  • Vivaldi (Chromium) — also reproduced in Brave (Chromium)

The device is not the cause

Verified on the same phone, in the same browser:

Test Result
webrtc samples → devices/input-output switching between all cameras works; cameras 0, 2 and 3 (facing back) focus correctly
webrtc samples → pan-tilt-zoom zoom is exposed and works

Camera 4 (facing back) is the only one that cannot focus — evidently a fixed-focus auxiliary lens. So the browser exposes both camera selection and zoom on this device; the app just doesn't use them.

Root cause

The scanner requests the camera like this (/_app/immutable/chunks/BTxGinf0.js in the current image):

await navigator.mediaDevices.getUserMedia({
  audio: false,
  video: {
    facingMode: { ideal: "environment" },
    width:  { min: 640 },
    height: { min: 480 }
  }
})

Only facingMode is used, so the browser picks whichever rear camera it considers default and the user cannot influence it. On multi-camera Android phones that default is frequently an ultra-wide or a macro/depth sensor — and several of those are fixed-focus.

The bundled ZXing library does support deviceId: { exact: … } (it is right there in the same chunk), but this code path doesn't use it. No zoom or focusMode constraints are applied either.

Suggested fix

  1. enumerateDevices() → list the videoinput devices and let the user choose, persisting the choice (a phone's "best" lens is not guessable from the label alone).
  2. Request with deviceId: { exact: … }, and stop the previous track before switching — otherwise Android tends to hand back the old stream.
  3. Where track.getCapabilities() reports them, apply focusMode: "continuous" and expose zoom through applyConstraints().

A "switch camera" button that simply cycles through the available videoinput devices would already make the scanner usable on this class of phone.

Happy to test a build.

Activity

  1. codebude commented on Sep 1, 2026

    @codebude
    Owner

    That's a great issue description. Thanks! I'll put it on my to-do list for the next release and keep you updated.

  2. codebude commented on Sep 2, 2026

    @codebude
    Owner

    Hi @AFT-syd ,

    I implemented a camera selector in #73 . The code is merged to develop branch now. To test it before the next release, either checkout the develop branch and run from code or use the docker image with the develop tag.

  3. self-assigned this
    on Sep 2, 2026
  4. AFT-syd commented on Sep 2, 2026

    @AFT-syd
    Author

    Tested the develop image on the same setup as the report — it works. Zoom (the 1x–10x buttons and the slider), camera switching and actual scanning all function on the Huawei P30 in Vivaldi. Thanks for the quick turnaround, and for implementing focus and zoom as well, not just the selector.

    Two observations from using it, in case they're useful:

    1. Cycling includes the front cameras

    "Switch camera" steps through every videoinput, so on this phone that's 4 rear + 1 front. Finding the one usable rear lens means tapping through several, and two of the steps are front-facing views that are never useful for scanning a book.

    A dropdown listing the cameras would remove the guesswork — label is populated once permission has been granted, so entries like camera2 0, facing back can be shown directly. Failing that, filtering the cycle to rear-facing devices (with a separate front toggle) would still be a clear improvement.

    2. Remembering the last choice would fix most of the friction

    deviceId is persisted per origin across sessions, so storing the selected id in localStorage and restoring it when the scanner opens would mean picking the right lens once rather than every time.

    Worth guarding: the id is regenerated if the user clears site data, and differs in private windows — so fall back to the default when a stored id is no longer present in enumerateDevices(), instead of failing to open the camera.

    On picking a better default

    enumerateDevices() lists default capture devices first, so defaulting to the first videoinput is already a reasonable improvement over an unconstrained facingMode request — on this phone the first rear camera does autofocus, while the one the old code ended up on does not.

    Note that the number in the label (camera2 0) is Android's own camera id, not the index in the list, so "always use id 0" isn't portable.

    If you ever want a fully robust default, track.getCapabilities() distinguishes them directly: the fixed-focus auxiliary lenses report no usable focusMode/focusDistance, while the main camera does. Probing is slow enough that it'd only be worth doing once and caching — which is the same mechanism as remembering the user's choice.

    Happy to test any of this on the same device.

  5. codebude commented on Sep 2, 2026

    @codebude
    Owner

    A dropdown listing the cameras would remove the guesswork

    I don't plan to implement a dropdown. The technical names returned by enumerateDevices() will confuse most of the users. Cycling once through the available cameras until you find your preferred one, shouldn't be a big hurdle.

    1. Remembering the last choice would fix most of the friction

    That's what actually already is implemented. See write- and readCameraPref functions over here: b94ebeb#diff-a1239feb7a93870c9fd4f14992dcf1a474f074a4795d912fac36ad9f654a07e6R68

    Isn't it working for you? (Otherwise I didn't get your point. In that case please try to explain it again to me.)

    Worth guarding: the id is regenerated if the user clears site data, and differs in private windows — so fall back to the default when a stored id is no longer present in enumerateDevices(), instead of failing to open the camera.

    That's also already implemented:

    const preferred = readCameraPref();
    		let index = cameras.findIndex((c) => c.deviceId === preferred);
    		if (index === -1) {
    			index = cameras.findIndex((c) => /back|environment|rear/i.test(c.label));
    		}
    		if (index === -1) index = 0;

    (Source: b94ebeb#diff-a1239feb7a93870c9fd4f14992dcf1a474f074a4795d912fac36ad9f654a07e6R175 )

  6. AFT-syd commented on Sep 5, 2026

    @AFT-syd
    Author

    My bad — I fired off an idea before properly reading your commit. Sorry about that.

    It works for me: camera switching, zoom and scanning all fine, and it remembers my choice.

    Two small notes, take them or leave them:

    • The switch also cycles through the front cameras. Not sure if that's intended.
    • I have three rear cameras that look identical in quality and resolution, so if I didn't already know there were several, I'd probably wonder whether the button did anything. Instead of a dropdown, just a small label showing the currently selected camera would be enough to guide.

    But it works, so I'm happy. Thanks for the quick turnaround.

  7. codebude commented on Sep 8, 2026

    @codebude
    Owner

    @AFT-syd I added a small camera name badge besides the scanner in #98 (You can try out via develop image or wait for the next release.)

    Image
  8. AFT-syd commented on Sep 8, 2026

    @AFT-syd
    Author

    Great. Thanks for the fixes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions