/** * QRScanner.js * คลาสสำหรับจัดการการสแกน QR Code * รองรับการแสดงข้อมูลดิบและข้อมูลที่ถอดรหัสแล้ว */ class QRScanner { constructor(debug = false) { this.debug = debug; this.stream = null; this.scanInterval = null; this.isScanning = false; this.lastScanResult = null; this.scanAttempts = 0; this.maxScanAttempts = 100; // จำกัดการพยายาม // ตัวแปรสำหรับเก็บข้อมูล QR this.rawQRData = null; this.parsedQRData = null; } /** * เริ่มต้นการสแกน QR Code */ async startScanning(videoElement, canvasElement, onSuccess, onError) { try { // ขอสิทธิ์เข้าถึงกล้อง this.stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: 'environment' // ใช้กล้องหลัง } }); // ตั้งค่า video element videoElement.srcObject = this.stream; videoElement.setAttribute('playsinline', true); videoElement.play(); this.isScanning = true; this.scanAttempts = 0; // รอให้ video พร้อม videoElement.addEventListener('loadedmetadata', () => { this.startScanLoop(videoElement, canvasElement, onSuccess, onError); }); if (this.debug) { console.log('QR Scanner: กล้องเริ่มทำงานแล้ว'); } } catch (error) { console.error('QR Scanner Error:', error); if (onError) { onError(`ไม่สามารถเข้าถึงกล้องได้: ${error.message}`); } } } /** * เริ่ม loop การสแกน */ startScanLoop(videoElement, canvasElement, onSuccess, onError) { const scanFrame = () => { if (!this.isScanning) return; this.scanAttempts++; try { // ตรวจสอบว่า video พร้อมแล้ว if (videoElement.readyState === videoElement.HAVE_ENOUGH_DATA) { // ตั้งค่า canvas canvasElement.width = videoElement.videoWidth; canvasElement.height = videoElement.videoHeight; const context = canvasElement.getContext('2d'); context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); // อ่านข้อมูลภาพ const imageData = context.getImageData(0, 0, canvasElement.width, canvasElement.height); // ใช้ jsQR ในการอ่าน QR Code const qrCode = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert", }); if (qrCode) { this.handleQRDetected(qrCode, onSuccess); return; } } // Debug log ทุก 20 ครั้ง if (this.debug && this.scanAttempts % 20 === 0) { console.log(`QR Scanner: พยายามสแกนครั้งที่ ${this.scanAttempts}`); } // หยุดหากพยายามมากเกินไป if (this.scanAttempts > this.maxScanAttempts) { if (onError) { onError('ไม่พบ QR Code กรุณาลองใหม่'); } return; } // ทำซ้ำในรอบถัดไป if (this.isScanning) { this.scanInterval = requestAnimationFrame(scanFrame); } } catch (error) { console.error('QR Scan Error:', error); if (onError) { onError(`เกิดข้อผิดพลาดในการสแกน: ${error.message}`); } } }; // เริ่มการสแกน scanFrame(); } /** * จัดการเมื่อตรวจพบ QR Code */ handleQRDetected(qrCode, onSuccess) { this.stopScanning(); // เก็บข้อมูล QR ดิบ this.rawQRData = qrCode.data; if (this.debug) { console.log('=== QR Code Detected ==='); console.log('Raw Data:', qrCode.data); console.log('Location:', qrCode.location); console.log('Scan Attempts:', this.scanAttempts); console.log('========================'); } // ส่งข้อมูลกลับ (ไม่ parse ใน QRScanner) if (onSuccess) { onSuccess({ data: this.rawQRData, location: qrCode.location, scanAttempts: this.scanAttempts }); } } /** * หยุดการสแกน */ stopScanning() { this.isScanning = false; if (this.scanInterval) { cancelAnimationFrame(this.scanInterval); this.scanInterval = null; } if (this.stream) { this.stream.getTracks().forEach(track => track.stop()); this.stream = null; } if (this.debug) { console.log('QR Scanner: หยุดการสแกนแล้ว'); } } /** * ดึงข้อมูล QR ล่าสุด */ getLastScanResult() { return { rawData: this.rawQRData, scanAttempts: this.scanAttempts }; } /** * ล้างข้อมูล */ reset() { this.stopScanning(); this.rawQRData = null; this.scanAttempts = 0; this.lastScanResult = null; } /** * เปิด/ปิด debug mode */ setDebug(enabled) { this.debug = enabled; } /** * สแกน QR จากไฟล์ blob */ async scanImageBlob(blob) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { try { const result = this.scanImageElement(img); resolve(result); } catch (error) { reject(error); } }; img.onerror = () => reject(new Error('ไม่สามารถโหลดรูปภาพได้')); img.src = URL.createObjectURL(blob); }); } /** * สแกน QR จากไฟล์ */ async scanImageFile(file) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { try { const result = this.scanImageElement(img); resolve(result); } catch (error) { reject(error); } }; img.onerror = () => reject(new Error('ไม่สามารถโหลดรูปภาพได้')); img.src = URL.createObjectURL(file); }); } /** * สแกน QR จาก Image Element */ scanImageElement(imgElement) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = imgElement.naturalWidth || imgElement.width; canvas.height = imgElement.naturalHeight || imgElement.height; ctx.drawImage(imgElement, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { this.rawQRData = code.data; if (this.debug) { console.log('QR Code ถูกพบ:', code.data); } return { success: true, data: code.data, location: code.location }; } else { if (this.debug) { console.log('ไม่พบ QR Code ในรูปภาพ'); } return { success: false, error: 'ไม่พบ QR Code ในรูปภาพ' }; } } }