A basic manual test harness — not part of the library, not published. Uses the negotiated API
(preferredBackend), so it exercises qrLtBackend,
qrBinLtBackend, and the header-frame auto-detection between them.
Speed tuning (applies to every section below — see notes):
Sends a file to a hidden canvas and reads it straight back via a captured canvas stream —
same trick as loopback-demo.html. Runs the real encodeToFrames →
DisplayDriver → NegotiatingReceiverSession pipeline end to end, in
this browser, without needing a second device or a real camera. This is the fastest way to
check whether the selected backend actually works here at all.
Sender canvas
"Camera" (mirrors sender)
idle
Requires a real camera on the receiving end (section 3, on this device or another one). If
the receiver is a different device on your network, plain
http://<lan-ip> won't get camera access — browsers require a secure
context (https://, or localhost on the same device) for
getUserMedia. See the README's Browser support section.
The canvas below displays at its native resolution (drag its bottom-right corner to resize further). Avoid shrinking it — a downscaled code is harder for the receiving camera to resolve; hold the camera closer instead if you want the code to fill more of its view.
idle
Point this device's camera at another device running section 2. Drag the video's bottom-right corner to resize the preview — a bigger preview makes it much easier to judge whether the code fills the frame cleanly (see the tuning notes below).
idle
qr-lt's throughput is roughly fps × fragmentSize bytes/second, bounded by how
fast the receiver can actually scan and decode. Two knobs, both above:
DisplayDriver's display rate. Higher sends more
frames/second, but the receiver's camera and decode loop need to keep up, or frames just
get skipped without being scanned (no benefit, same failure mode as raising it too far).
qr:capacity in
package.json if you want to compute a different ceiling).
scanHz past that one
decoder's own throughput bought nothing. Raising this past 1 lets that many frames decode
concurrently — worthwhile on capable hardware, especially alongside a higher
scanHz, since decode latency was the de facto ceiling on how much of it was
actually usable. Each worker costs its own ~1MB zxing-wasm instance plus a startup delay,
and on a low-end phone several concurrent decodes can thrash the CPU rather than help —
there's no universally safe value above 1, so this is left as a manual, per-device choice
rather than an automatic default. The goodput reading next to the progress bar above is
the way to tell whether a given value actually helped on your device.
Beyond these knobs: QR ECC is already at the library's minimum overhead level (L), so there's no further redundancy to trade away. On the physical/environmental side — screen brightness/contrast, camera autofocus lock, holding distance/angle steady, and reducing motion blur — all matter more than they might seem, and are usually a bigger lever on real-world reliability (which is what lets you safely raise fps/fragmentSize in the first place) than any of the code-level knobs above.