fix(machine): decode QR at intrinsic frame + tuned capture resolution
The camera pairing source decoded frames at the <video> element's CSS box size (qr's readFrame default) rather than the intrinsic frame, and let the stream stay at the panel-bound ~720p that frontalCamera negotiates from the screen size. On the 1280x800 kiosk with a fixed-focus 5MP scan camera that left far too few pixels-per-module for a dense spire-seed QR, so a centered, in-square code never decoded. Decode the intrinsic frame (readFrame fullSize=true) and pin a deliberate 1280x960 capture via applyConstraints. lamassu-machine caps QR scanning at 640x480 for decode speed (megapixels only slow the per-frame decode); our seed QR is denser than a lightning invoice, so 1280x960 balances pixels-per-module against latency and keeps auto-exposure from blowing out a frame-filling phone screen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d32ddc806a
commit
fd4f69826d
1 changed files with 30 additions and 1 deletions
|
|
@ -32,6 +32,31 @@ export class QrPairingSource implements PairingSource {
|
|||
if (!video) throw new Error('QrPairingSource requires a <video> element')
|
||||
|
||||
const camera = await frontalCamera(video)
|
||||
|
||||
// `frontalCamera` requests `ideal: screen.{width,height}`, so on the kiosk's
|
||||
// 1280x800 panel it otherwise streams at ~720p — and `decodeQR` then
|
||||
// center-crops to a square, leaving too few pixels-per-module for a dense
|
||||
// spire-seed QR on this fixed-focus lens. Pin a deliberate 1280x960 capture
|
||||
// instead: lamassu-machine caps QR scanning at 640x480 for decode speed
|
||||
// (megapixels just slow the per-frame decode), but our seed QR is denser
|
||||
// than a lightning invoice, so 1280x960 is the balance — ~14-18px/module at
|
||||
// frame-fill, still fast, and it meters exposure better than maxing the
|
||||
// sensor (a frame-filling QR keeps auto-exposure from blowing out on a
|
||||
// bright phone screen). The stream lives on the <video>'s srcObject
|
||||
// (QRCamera.stream is private); soft `ideal` so a camera that can't honor
|
||||
// it degrades to its closest mode instead of throwing.
|
||||
try {
|
||||
const stream = video.srcObject
|
||||
if (stream instanceof MediaStream) {
|
||||
await stream.getVideoTracks()[0]?.applyConstraints({
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 960 },
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
onError?.(e)
|
||||
}
|
||||
|
||||
const canvas = new QRCanvas() // decode-only; no overlay canvases needed
|
||||
|
||||
let stopped = false
|
||||
|
|
@ -46,7 +71,11 @@ export class QrPairingSource implements PairingSource {
|
|||
cancel = frameLoop(() => {
|
||||
if (stopped) return
|
||||
try {
|
||||
const result = camera.readFrame(canvas)
|
||||
// `fullSize: true` decodes the camera's intrinsic frame (videoWidth ×
|
||||
// videoHeight) rather than the <video> element's CSS box — the default
|
||||
// (`false`) was decoding the few-hundred-px on-screen preview, which
|
||||
// (compounded by `object-cover` cropping) starved the decoder.
|
||||
const result = camera.readFrame(canvas, true)
|
||||
if (result) {
|
||||
stop()
|
||||
onScan(result)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue