Fix UX-015: ZXing async callback silently swallows camera errors - add handleCameraError(), 5s cameraFailTimer, error branch in decodeFromVideoDevice callback
This commit is contained in:
+31
-2
@@ -2893,20 +2893,48 @@
|
|||||||
|
|
||||||
setScanStatus('Point camera at a barcode', 'success');
|
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
|
// decodeFromVideoDevice handles camera + scanning in one call
|
||||||
codeReader.decodeFromVideoDevice(deviceId, video, (result, err) => {
|
codeReader.decodeFromVideoDevice(deviceId, video, (result, err) => {
|
||||||
|
// Clear the timeout once we get any callback (stream started or error)
|
||||||
|
clearTimeout(cameraFailTimer);
|
||||||
if (result && scanningActive) {
|
if (result && scanningActive) {
|
||||||
const val = result.getText();
|
const val = result.getText();
|
||||||
if (val && val !== currentScannedMachineId) {
|
if (val && val !== currentScannedMachineId) {
|
||||||
handleBarcode(val.trim());
|
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) {
|
} catch (e) {
|
||||||
console.error('Camera error:', e);
|
console.error('Camera error:', e);
|
||||||
|
handleCameraError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCameraError(e) {
|
||||||
setScanStatus('Camera failed: ' + e.message, 'error');
|
setScanStatus('Camera failed: ' + e.message, 'error');
|
||||||
const ph = document.getElementById('cameraPlaceholder');
|
const ph = document.getElementById('cameraPlaceholder');
|
||||||
if (ph) ph.innerHTML = `<span class="cam-icon">${gi("📷")}</span><div class="cam-hint" style="margin-top:4px;">Camera: ${e.message}</div>`;
|
if (ph) {
|
||||||
|
ph.style.display = 'block';
|
||||||
|
ph.innerHTML = `<span class="cam-icon">${gi("📷")}</span><div class="cam-hint" style="margin-top:4px;">Camera: ${e.message}</div>`;
|
||||||
|
}
|
||||||
// Show manual search fallback
|
// Show manual search fallback
|
||||||
const fallback = document.getElementById('manualSearchFallback');
|
const fallback = document.getElementById('manualSearchFallback');
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
@@ -2921,7 +2949,8 @@
|
|||||||
<button class="btn btn-primary btn-sm" onclick="handleManualBarcode()" style="margin-top:6px;width:100%;">Search</button>
|
<button class="btn btn-primary btn-sm" onclick="handleManualBarcode()" style="margin-top:6px;width:100%;">Search</button>
|
||||||
<button class="btn btn-outline btn-sm" onclick="startScanning()" style="margin-top:6px;width:100%;">Try camera again</button>
|
<button class="btn btn-outline btn-sm" onclick="startScanning()" style="margin-top:6px;width:100%;">Try camera again</button>
|
||||||
`;
|
`;
|
||||||
}
|
// Focus the manual input for convenience
|
||||||
|
setTimeout(() => document.getElementById('manualBarcodeInput')?.focus(), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user