QRScanner.js

7.60 KB
23/06/2025 12:21
JS
QRScanner.js
/**
 * 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 ในรูปภาพ'
      };
    }
  }
}