How-to: camera selection and torch

fromCamera returns a CameraController with everything you need to pick a lens and light the subject. The <ur-scanner> element wires a picker and torch button for you; drop to the controller when you want your own UI.

Start on the back camera

The default constraints prefer the environment-facing camera, which is what you want for scanning another screen:

import { fromCamera } from '@nkwib/ur-scanner';
const cam = await fromCamera({ video, constraints: { video: { facingMode: 'environment' } } });

facingMode is a preference, not a guarantee. On a multi-camera phone the OS may hand you a wide or macro lens that focuses poorly at scanning distance. If scanning is flaky, enumerate and pick explicitly.

List and switch cameras

Device labels are only populated after a camera permission has been granted, so call listVideoInputs() once scanning has started.

const inputs = await cam.listVideoInputs();          // MediaDeviceInfo[]
// e.g. render a <select>, then:
await cam.switchCamera(inputs[1].deviceId);

switchCamera stops the current stream and reopens on the chosen device, keeping the same receiver and progress.

Torch (flashlight)

Torch is supported on some Android/Chromium devices and essentially no iOS browser. Feature-detect before showing a button:

if (cam.hasTorch()) {
  torchButton.hidden = false;
  torchButton.onclick = () => cam.torch(!(torchButton.dataset.on === '1'));
}

torch(on) is a no-op where unsupported, so it is safe to call blind. The torch capability lives on the active MediaStreamTrack; switching cameras can change whether it exists, so re-check hasTorch() after switchCamera.

Detection throttle

The detect loop runs on requestAnimationFrame but only attempts a decode every scanIntervalMs (default 120ms, about 8/s). Decoding every animation frame wastes battery and can actually lower throughput on a busy main thread. Raise the interval on low-end devices; lower it if you have CPU to spare and want faster locks. See the beat-frequency note in tuning.

With the web component

<ur-scanner auto-start facing-mode="environment" scan-interval="150"></ur-scanner>

The element shows a camera <select> automatically when two or more inputs exist (::part(camera-select)) and a torch button when the active track supports it (::part(torch-button)).

Errors you will meet

fromCamera throws typed URScannerErrors: INSECURE_CONTEXT (serve over HTTPS/localhost), CAMERA_PERMISSION_DENIED, CAMERA_NOT_FOUND, CAMERA_UNSUPPORTED. Always offer the file-input fallback when the camera cannot start.

ur-scanner Browser receiver for animated BC-UR QR codes. Camera to bytes, no wallet required.