diff --git a/static/index.html b/static/index.html index 4fb4db0..c4ed0eb 100644 --- a/static/index.html +++ b/static/index.html @@ -2893,35 +2893,64 @@ setScanStatus('Point camera at a barcode', 'success'); + // Camera readiness check: if the video never starts streaming + // (no media attached within 5s), treat as camera failure + const cameraFailTimer = setTimeout(() => { + if (video.readyState === 0 && scanningActive) { + console.warn('Camera stream never started — showing manual fallback'); + scanningActive = false; + video.style.display = 'none'; + if (codeReader) { + try { codeReader.reset(); } catch (e) { /* ignore */ } + } + handleCameraError(new Error('No camera stream — connect a camera or enter ID manually')); + } + }, 5000); + // decodeFromVideoDevice handles camera + scanning in one call codeReader.decodeFromVideoDevice(deviceId, video, (result, err) => { + // Clear the timeout once we get any callback (stream started or error) + clearTimeout(cameraFailTimer); if (result && scanningActive) { const val = result.getText(); if (val && val !== currentScannedMachineId) { handleBarcode(val.trim()); } + } else if (err && scanningActive) { + // ZXing reports camera errors through the callback (not as exceptions) + console.error('ZXing camera error:', err); + handleCameraError(new Error(err.message || err)); } }); } catch (e) { console.error('Camera error:', e); - setScanStatus('Camera failed: ' + e.message, 'error'); - const ph = document.getElementById('cameraPlaceholder'); - if (ph) ph.innerHTML = `