document.addEventListener('DOMContentLoaded', () => { // DOM elements const imageUpload = document.getElementById('imageUpload'); const preview = document.getElementById('preview'); const scanCanvas = document.getElementById('scanCanvas'); const status = document.getElementById('status'); const result = document.getElementById('result'); // Camera elements - will be created dynamically let videoElement; let cameraContainer; let cameraControls; let startCameraButton; let stopCameraButton; let switchCameraButton; let enhancementToggle; let debugModeToggle; let cameraStream = null; let activeCameraId = null; let availableCameras = []; let isScanning = false; let scanInterval = null; let useImageEnhancement = true; // Default to true let debugMode = false; // Default to false let focusModeActive = false; // Check if ZXing library is loaded if (typeof ZXing === 'undefined') { status.textContent = 'Error: ZXing library not loaded'; result.textContent = 'Please check your internet connection and reload the page.'; console.error('ZXing library not loaded'); return; } // Initialize ZXing code reader and hints const codeReader = new ZXing.BrowserMultiFormatReader(); const hints = new Map(); const formats = [ ZXing.BarcodeFormat.QR_CODE, ZXing.BarcodeFormat.DATA_MATRIX, ZXing.BarcodeFormat.AZTEC, ZXing.BarcodeFormat.PDF_417, ZXing.BarcodeFormat.EAN_13, ZXing.BarcodeFormat.EAN_8, ZXing.BarcodeFormat.UPC_A, ZXing.BarcodeFormat.UPC_E, ZXing.BarcodeFormat.CODE_128, ZXing.BarcodeFormat.CODE_39, ZXing.BarcodeFormat.CODE_93, ZXing.BarcodeFormat.ITF, ZXing.BarcodeFormat.CODABAR ]; hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats); hints.set(ZXing.DecodeHintType.TRY_HARDER, true); // Initialize the app function init() { status.textContent = 'Ready to scan barcodes. Upload an image or use camera.'; createCameraElements(); // Check if we're in a secure context (HTTPS or localhost) if (!isSecureContext()) { status.textContent = 'Camera access requires HTTPS. Current connection is not secure.'; const warningDiv = document.createElement('div'); warningDiv.className = 'security-warning'; warningDiv.innerHTML = 'Security Warning: Camera access requires a secure connection (HTTPS). ' + 'You are currently on an insecure connection, which may prevent camera access. ' + 'Please access this page via HTTPS or use localhost for testing.'; document.querySelector('.container').insertBefore(warningDiv, document.querySelector('.upload-section')); } checkCameraSupport(); } // Check if we're in a secure context (HTTPS or localhost) function isSecureContext() { // Check if the context is secure using the SecureContext API if (window.isSecureContext === true) { return true; } // Fallback check for older browsers return location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1'; } // Create camera UI elements function createCameraElements() { // Create camera container cameraContainer = document.createElement('div'); cameraContainer.id = 'camera-container'; cameraContainer.className = 'camera-container'; cameraContainer.style.display = 'none'; // Create video element videoElement = document.createElement('video'); videoElement.id = 'camera-feed'; videoElement.autoplay = true; videoElement.playsInline = true; // Create camera controls cameraControls = document.createElement('div'); cameraControls.className = 'camera-controls'; // Create camera buttons startCameraButton = document.createElement('button'); startCameraButton.id = 'start-camera'; startCameraButton.textContent = 'Start Camera'; startCameraButton.addEventListener('click', startCamera); stopCameraButton = document.createElement('button'); stopCameraButton.id = 'stop-camera'; stopCameraButton.textContent = 'Stop Camera'; stopCameraButton.style.display = 'none'; stopCameraButton.addEventListener('click', stopCamera); switchCameraButton = document.createElement('button'); switchCameraButton.id = 'switch-camera'; switchCameraButton.textContent = 'Switch Camera'; switchCameraButton.style.display = 'none'; switchCameraButton.addEventListener('click', switchCamera); // Create capture image button const captureImageButton = document.createElement('button'); captureImageButton.id = 'capture-image'; captureImageButton.textContent = 'Capture Image'; captureImageButton.style.display = 'none'; captureImageButton.addEventListener('click', captureImage); // Create focus mode button const focusModeButton = document.createElement('button'); focusModeButton.id = 'focus-mode'; focusModeButton.textContent = 'Focus Mode'; focusModeButton.style.display = 'none'; focusModeButton.addEventListener('click', toggleFocusMode); // Create retry button for permission issues const retryPermissionButton = document.createElement('button'); retryPermissionButton.id = 'retry-permission'; retryPermissionButton.textContent = 'Retry Camera Permission'; retryPermissionButton.style.display = 'none'; retryPermissionButton.addEventListener('click', () => { retryPermissionButton.style.display = 'none'; status.textContent = 'Requesting camera permission...'; checkCameraSupport(); }); // Create enhancement toggle const enhancementContainer = document.createElement('div'); enhancementContainer.className = 'enhancement-toggle-container'; enhancementToggle = document.createElement('input'); enhancementToggle.type = 'checkbox'; enhancementToggle.id = 'enhancement-toggle'; enhancementToggle.checked = useImageEnhancement; enhancementToggle.addEventListener('change', (e) => { useImageEnhancement = e.target.checked; status.textContent = useImageEnhancement ? 'Image enhancement enabled. This may help detect barcodes in difficult lighting.' : 'Image enhancement disabled. Use this if barcodes are not being detected correctly.'; }); const enhancementLabel = document.createElement('label'); enhancementLabel.htmlFor = 'enhancement-toggle'; enhancementLabel.textContent = 'Enable image enhancement'; enhancementContainer.appendChild(enhancementToggle); enhancementContainer.appendChild(enhancementLabel); // Create debug mode toggle const debugContainer = document.createElement('div'); debugContainer.className = 'enhancement-toggle-container debug-toggle-container'; debugModeToggle = document.createElement('input'); debugModeToggle.type = 'checkbox'; debugModeToggle.id = 'debug-toggle'; debugModeToggle.checked = debugMode; debugModeToggle.addEventListener('change', (e) => { debugMode = e.target.checked; status.textContent = debugMode ? 'Debug mode enabled. Processing details will be shown.' : 'Debug mode disabled.'; // Create or remove debug info element let debugInfo = document.getElementById('debug-info'); if (debugMode) { if (!debugInfo) { debugInfo = document.createElement('div'); debugInfo.id = 'debug-info'; debugInfo.className = 'debug-info'; result.parentNode.appendChild(debugInfo); } } else { if (debugInfo) { debugInfo.remove(); } } }); const debugLabel = document.createElement('label'); debugLabel.htmlFor = 'debug-toggle'; debugLabel.textContent = 'Debug mode'; debugContainer.appendChild(debugModeToggle); debugContainer.appendChild(debugLabel); // Append elements cameraControls.appendChild(startCameraButton); cameraControls.appendChild(stopCameraButton); cameraControls.appendChild(switchCameraButton); cameraControls.appendChild(captureImageButton); cameraControls.appendChild(focusModeButton); cameraControls.appendChild(retryPermissionButton); cameraControls.appendChild(enhancementContainer); cameraControls.appendChild(debugContainer); cameraContainer.appendChild(videoElement); // Insert camera elements into the DOM const uploadSection = document.querySelector('.upload-section'); uploadSection.parentNode.insertBefore(cameraContainer, uploadSection.nextSibling); uploadSection.parentNode.insertBefore(cameraControls, uploadSection.nextSibling); } // Check if camera is supported function checkCameraSupport() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { console.warn('Camera access not supported in this browser'); startCameraButton.disabled = true; startCameraButton.title = 'Camera not supported in this browser'; status.textContent = 'Camera not supported in this browser. Try using a modern browser like Chrome, Firefox, or Edge.'; return false; } // First try to access the camera to trigger permission prompt navigator.mediaDevices.getUserMedia({ video: true }) .then(stream => { // Stop the stream immediately after getting permission stream.getTracks().forEach(track => track.stop()); // Now enumerate devices after permission is granted return navigator.mediaDevices.enumerateDevices(); }) .then(devices => { availableCameras = devices.filter(device => device.kind === 'videoinput'); console.log('Available cameras:', availableCameras); if (availableCameras.length === 0) { startCameraButton.disabled = true; startCameraButton.title = 'No cameras detected'; status.textContent = 'No cameras detected on your device'; return; } // Enable camera button startCameraButton.disabled = false; startCameraButton.title = 'Start camera for barcode scanning'; status.textContent = 'Camera permission granted. Click "Start Camera" to begin scanning.'; // Show switch camera button if multiple cameras available if (availableCameras.length > 1) { switchCameraButton.style.display = 'inline-block'; } }) .catch(error => { console.error('Error accessing camera:', error); if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') { status.textContent = 'Camera permission denied. Please click "Retry Camera Permission" and allow camera access when prompted.'; startCameraButton.disabled = true; startCameraButton.title = 'Camera permission denied'; // Show retry permission button const retryButton = document.getElementById('retry-permission'); if (retryButton) { retryButton.style.display = 'inline-block'; } } else { // Try to enumerate devices anyway, in case permissions were granted before navigator.mediaDevices.enumerateDevices() .then(devices => { availableCameras = devices.filter(device => device.kind === 'videoinput'); console.log('Available cameras (without permission):', availableCameras); if (availableCameras.length === 0) { startCameraButton.disabled = true; startCameraButton.title = 'No cameras detected'; status.textContent = 'No cameras detected on your device'; } else { startCameraButton.disabled = false; startCameraButton.title = 'Start camera for barcode scanning'; if (availableCameras.length > 1) { switchCameraButton.style.display = 'inline-block'; } } }) .catch(enumError => { console.error('Error enumerating devices:', enumError); startCameraButton.disabled = true; startCameraButton.title = 'Error accessing camera information'; status.textContent = 'Error accessing camera. Please check if your camera is connected and working properly.'; }); } }); return true; } // Start camera function startCamera() { // Hide image preview and show camera preview.style.display = 'none'; cameraContainer.style.display = 'block'; // Update buttons startCameraButton.style.display = 'none'; stopCameraButton.style.display = 'inline-block'; // Show focus mode and capture image buttons const focusModeButton = document.getElementById('focus-mode'); const captureImageButton = document.getElementById('capture-image'); if (focusModeButton) { focusModeButton.style.display = 'inline-block'; } if (captureImageButton) { captureImageButton.style.display = 'inline-block'; } // Clear previous results status.textContent = 'Starting camera...'; result.textContent = ''; // Try to get cameras again if none were detected initially if (availableCameras.length === 0) { navigator.mediaDevices.enumerateDevices() .then(devices => { availableCameras = devices.filter(device => device.kind === 'videoinput'); console.log('Re-checking available cameras:', availableCameras); continueStartCamera(); }) .catch(error => { console.error('Error re-enumerating devices:', error); continueStartCamera(); }); } else { continueStartCamera(); } } // Continue camera startup after checking for cameras function continueStartCamera() { // Select camera (use first camera by default or previously selected) const cameraId = activeCameraId || (availableCameras.length > 0 ? availableCameras[0].deviceId : null); if (!cameraId) { // Try a more generic approach if no specific camera ID is available const constraints = { video: { facingMode: 'environment' // Prefer back camera } }; startCameraWithConstraints(constraints); } else { // Use specific camera ID const constraints = { video: { deviceId: { exact: cameraId }, width: { ideal: 1280 }, height: { ideal: 720 } } }; startCameraWithConstraints(constraints); } } // Start camera with specific constraints function startCameraWithConstraints(constraints) { navigator.mediaDevices.getUserMedia(constraints) .then(stream => { cameraStream = stream; videoElement.srcObject = stream; // Get the actual device ID from the stream const videoTrack = stream.getVideoTracks()[0]; if (videoTrack) { const settings = videoTrack.getSettings(); activeCameraId = settings.deviceId; console.log('Active camera ID:', activeCameraId); console.log('Camera settings:', settings); } // Wait for video to be ready videoElement.onloadedmetadata = () => { status.textContent = 'Camera active. Point at a barcode to scan.'; startBarcodeScanning(); }; }) .catch(error => { console.error('Error starting camera with constraints:', error, constraints); // If failed with specific constraints, try generic constraints if (constraints.video.deviceId) { console.log('Trying generic camera constraints...'); startCameraWithConstraints({ video: { facingMode: 'environment' } }); } else { status.textContent = 'Error starting camera: ' + (error.message || 'Unknown error'); stopCamera(); } }); } // Stop camera function stopCamera() { // Stop scanning stopBarcodeScanning(); // Stop camera stream if (cameraStream) { cameraStream.getTracks().forEach(track => track.stop()); cameraStream = null; } // Update UI videoElement.srcObject = null; cameraContainer.style.display = 'none'; startCameraButton.style.display = 'inline-block'; stopCameraButton.style.display = 'none'; // Hide focus mode and capture image buttons const focusModeButton = document.getElementById('focus-mode'); const captureImageButton = document.getElementById('capture-image'); if (focusModeButton) { focusModeButton.style.display = 'none'; } if (captureImageButton) { captureImageButton.style.display = 'none'; } // Remove focus mode if active document.body.classList.remove('focus-mode-active'); status.textContent = 'Camera stopped. Upload an image or restart camera.'; } // Switch between available cameras function switchCamera() { if (availableCameras.length <= 1) return; // Find next camera in the list const currentIndex = availableCameras.findIndex(camera => camera.deviceId === activeCameraId); const nextIndex = (currentIndex + 1) % availableCameras.length; activeCameraId = availableCameras[nextIndex].deviceId; // Restart camera with new device if (cameraStream) { stopCamera(); startCamera(); } } // Start continuous barcode scanning function startBarcodeScanning() { if (isScanning) return; isScanning = true; // Add scanning indicator const scanIndicator = document.createElement('div'); scanIndicator.id = 'scan-indicator'; scanIndicator.className = 'scan-indicator'; cameraContainer.appendChild(scanIndicator); // Process frames at regular intervals scanInterval = setInterval(() => { if (!videoElement || !cameraStream) return; // Capture current frame const width = videoElement.videoWidth; const height = videoElement.videoHeight; if (width === 0 || height === 0) return; // Skip if video dimensions aren't available yet // Set canvas dimensions to match video scanCanvas.width = width; scanCanvas.height = height; // Draw video frame on canvas const ctx = scanCanvas.getContext('2d'); ctx.drawImage(videoElement, 0, 0, width, height); // Add scanning guide overlay drawScanningGuide(ctx, width, height); // Process the frame processVideoFrame(ctx, width, height); }, 100); // Scan more frequently (every 100ms instead of 200ms) } // Stop barcode scanning function stopBarcodeScanning() { isScanning = false; if (scanInterval) { clearInterval(scanInterval); scanInterval = null; } // Remove scanning indicator if it exists const scanIndicator = document.getElementById('scan-indicator'); if (scanIndicator) { scanIndicator.remove(); } } // Draw scanning guide overlay function drawScanningGuide(ctx, width, height) { // Draw a semi-transparent guide rectangle in the center const guideSize = Math.min(width, height) * (focusModeActive ? 0.5 : 0.7); // Smaller guide in focus mode const x = (width - guideSize) / 2; const y = (height - guideSize) / 2; // Draw outer darkened area ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; ctx.fillRect(0, 0, width, height); // Draw transparent center ctx.clearRect(x, y, guideSize, guideSize); // Draw guide border ctx.strokeStyle = focusModeActive ? 'rgba(231, 76, 60, 0.8)' : 'rgba(52, 152, 219, 0.8)'; ctx.lineWidth = focusModeActive ? 6 : 4; ctx.strokeRect(x, y, guideSize, guideSize); // Draw corner markers const markerLength = guideSize * 0.1; ctx.lineWidth = focusModeActive ? 8 : 6; // Top-left corner ctx.beginPath(); ctx.moveTo(x, y + markerLength); ctx.lineTo(x, y); ctx.lineTo(x + markerLength, y); ctx.stroke(); // Top-right corner ctx.beginPath(); ctx.moveTo(x + guideSize - markerLength, y); ctx.lineTo(x + guideSize, y); ctx.lineTo(x + guideSize, y + markerLength); ctx.stroke(); // Bottom-right corner ctx.beginPath(); ctx.moveTo(x + guideSize, y + guideSize - markerLength); ctx.lineTo(x + guideSize, y + guideSize); ctx.lineTo(x + guideSize - markerLength, y + guideSize); ctx.stroke(); // Bottom-left corner ctx.beginPath(); ctx.moveTo(x + markerLength, y + guideSize); ctx.lineTo(x, y + guideSize); ctx.lineTo(x, y + guideSize - markerLength); ctx.stroke(); // Add text instruction ctx.font = '16px Arial'; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; if (focusModeActive) { ctx.fillText('FOCUS MODE: Position barcode in the red box', width / 2, y + guideSize + 30); // Add crosshair in focus mode const centerX = x + guideSize / 2; const centerY = y + guideSize / 2; const crosshairSize = guideSize * 0.1; ctx.strokeStyle = 'rgba(231, 76, 60, 0.8)'; ctx.lineWidth = 2; // Horizontal line ctx.beginPath(); ctx.moveTo(centerX - crosshairSize, centerY); ctx.lineTo(centerX + crosshairSize, centerY); ctx.stroke(); // Vertical line ctx.beginPath(); ctx.moveTo(centerX, centerY - crosshairSize); ctx.lineTo(centerX, centerY + crosshairSize); ctx.stroke(); } else { ctx.fillText('Position barcode within the box', width / 2, y + guideSize + 30); } } // Process video frame for barcode detection function processVideoFrame(ctx, width, height) { try { // Pulse the scan indicator to show active scanning const scanIndicator = document.getElementById('scan-indicator'); if (scanIndicator) { scanIndicator.classList.add('pulse'); setTimeout(() => { scanIndicator.classList.remove('pulse'); }, 50); } // Update debug info if (debugMode) { updateDebugInfo('Processing frame', { width, height }); } // Store original image data for fallback const originalImageData = ctx.getImageData(0, 0, width, height); // Apply image enhancements to improve detection if enabled if (useImageEnhancement) { enhanceImage(ctx, width, height); if (debugMode) { updateDebugInfo('Image enhancement applied'); } } // Create a data URL from the canvas for the BrowserMultiFormatReader const dataUrl = scanCanvas.toDataURL('image/png'); // Try direct MultiFormatReader approach first (more reliable for video) try { // Create a ZXing HTMLCanvasElementLuminanceSource const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(scanCanvas); // Create a MultiFormatReader with hints const reader = new ZXing.MultiFormatReader(); // Set up hints with all supported formats and try harder flag const newHints = new Map(); newHints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats); newHints.set(ZXing.DecodeHintType.TRY_HARDER, true); // Add these additional hints for better video frame detection newHints.set(ZXing.DecodeHintType.PURE_BARCODE, false); newHints.set(ZXing.DecodeHintType.CHARACTER_SET, "UTF-8"); newHints.set(ZXing.DecodeHintType.ASSUME_GS1, false); reader.setHints(newHints); // Try different binarizers for better detection const binarizers = [ new ZXing.HybridBinarizer(luminanceSource), new ZXing.GlobalHistogramBinarizer(luminanceSource) ]; let decodedResult = null; let usedBinarizer = ''; let errorMessages = []; // Try each binarizer for (const binarizer of binarizers) { if (decodedResult) break; // Stop if we already found a result const binaryBitmap = new ZXing.BinaryBitmap(binarizer); usedBinarizer = binarizer instanceof ZXing.HybridBinarizer ? 'HybridBinarizer' : 'GlobalHistogramBinarizer'; if (debugMode) { updateDebugInfo(`Trying ${usedBinarizer}`); } try { // Try to decode the image using the binary bitmap const result = reader.decode(binaryBitmap); decodedResult = { text: result.getText(), format: result.getBarcodeFormat(), resultPoints: result.getResultPoints() }; if (debugMode) { updateDebugInfo(`Success with ${usedBinarizer}`, decodedResult); } } catch (error) { // Store error for debugging errorMessages.push(`${usedBinarizer}: ${error.message || 'Unknown error'}`); if (debugMode) { updateDebugInfo(`Failed with ${usedBinarizer}`, { error: error.message || 'Unknown error' }); } } } // If we got a result from the direct approach if (decodedResult) { // Barcode detected successfully status.textContent = 'Barcode recognized successfully!'; // Format the result const resultText = `Code: ${decodedResult.text}\nFormat: ${decodedResult.format}`; document.getElementById('result').textContent = resultText; // Draw the barcode location on the canvas if (decodedResult.resultPoints && decodedResult.resultPoints.length > 0) { drawBarcodeLocation(ctx, decodedResult.resultPoints); } // Play success sound playSuccessBeep(); // Pause scanning briefly after successful detection stopBarcodeScanning(); setTimeout(startBarcodeScanning, 2000); return; } // If direct approach failed, try with original image if we enhanced it if (useImageEnhancement) { // Restore original image ctx.putImageData(originalImageData, 0, 0); if (debugMode) { updateDebugInfo('Trying with original (non-enhanced) image'); } // Try again with original image for (const binarizer of binarizers) { if (decodedResult) break; // Stop if we already found a result const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(scanCanvas); const binaryBitmap = new ZXing.BinaryBitmap(binarizer); usedBinarizer = binarizer instanceof ZXing.HybridBinarizer ? 'HybridBinarizer (original)' : 'GlobalHistogramBinarizer (original)'; try { // Try to decode the image using the binary bitmap const result = reader.decode(binaryBitmap); decodedResult = { text: result.getText(), format: result.getBarcodeFormat(), resultPoints: result.getResultPoints() }; if (debugMode) { updateDebugInfo(`Success with ${usedBinarizer}`, decodedResult); } } catch (error) { // Store error for debugging errorMessages.push(`${usedBinarizer}: ${error.message || 'Unknown error'}`); if (debugMode) { updateDebugInfo(`Failed with ${usedBinarizer}`, { error: error.message || 'Unknown error' }); } } } // If we got a result from the original image if (decodedResult) { // Barcode detected successfully status.textContent = 'Barcode recognized successfully!'; // Format the result const resultText = `Code: ${decodedResult.text}\nFormat: ${decodedResult.format}`; document.getElementById('result').textContent = resultText; // Draw the barcode location on the canvas if (decodedResult.resultPoints && decodedResult.resultPoints.length > 0) { drawBarcodeLocation(ctx, decodedResult.resultPoints); } // Play success sound playSuccessBeep(); // Pause scanning briefly after successful detection stopBarcodeScanning(); setTimeout(startBarcodeScanning, 2000); return; } } // If all direct methods failed, fall back to BrowserMultiFormatReader if (debugMode) { updateDebugInfo('Trying BrowserMultiFormatReader fallback'); } // Use the BrowserMultiFormatReader to decode the image codeReader.decodeFromImageUrl(dataUrl) .then(result => { if (result) { // Barcode detected successfully status.textContent = 'Barcode recognized successfully!'; // Format the result const resultText = `Code: ${result.text}\nFormat: ${result.format}`; document.getElementById('result').textContent = resultText; if (debugMode) { updateDebugInfo('Success with BrowserMultiFormatReader', { text: result.text, format: result.format }); } // Draw the barcode location on the canvas if available if (result.resultPoints && result.resultPoints.length > 0) { drawBarcodeLocation(ctx, result.resultPoints); } // Play success sound playSuccessBeep(); // Pause scanning briefly after successful detection stopBarcodeScanning(); setTimeout(startBarcodeScanning, 2000); } }) .catch((error) => { // No barcode detected with any method if (debugMode) { updateDebugInfo('Failed with all methods', { error: error.message || 'Unknown error', allErrors: errorMessages.join(', ') }); } }); } catch (directError) { // If direct MultiFormatReader approach fails completely, fall back to BrowserMultiFormatReader if (debugMode) { updateDebugInfo('Direct MultiFormatReader failed, trying BrowserMultiFormatReader', { error: directError.message || 'Unknown error' }); } // Use the BrowserMultiFormatReader to decode the image codeReader.decodeFromImageUrl(dataUrl) .then(result => { if (result) { // Barcode detected successfully status.textContent = 'Barcode recognized successfully!'; // Format the result const resultText = `Code: ${result.text}\nFormat: ${result.format}`; document.getElementById('result').textContent = resultText; if (debugMode) { updateDebugInfo('Success with BrowserMultiFormatReader', { text: result.text, format: result.format }); } // Draw the barcode location on the canvas if available if (result.resultPoints && result.resultPoints.length > 0) { drawBarcodeLocation(ctx, result.resultPoints); } // Play success sound playSuccessBeep(); // Pause scanning briefly after successful detection stopBarcodeScanning(); setTimeout(startBarcodeScanning, 2000); } }) .catch((error) => { // No barcode detected with any method if (debugMode) { updateDebugInfo('Failed with all methods', { error: error.message || 'Unknown error' }); } }); } } catch (error) { console.error('Error processing video frame:', error); if (debugMode) { updateDebugInfo('Error processing frame', { error: error.message || 'Unknown error' }); } } } // Update debug information function updateDebugInfo(action, data = {}) { if (!debugMode) return; const debugInfo = document.getElementById('debug-info'); if (!debugInfo) return; const timestamp = new Date().toLocaleTimeString(); const entry = document.createElement('div'); entry.className = 'debug-entry'; let content = `${timestamp}: ${action}`; if (Object.keys(data).length > 0) { content += '