ngx-scanner icon indicating copy to clipboard operation
ngx-scanner copied to clipboard

How to set a specific camera on startup?

Open joshua-classen opened this issue 4 months ago • 0 comments

In my code example I am unable to set the camera on Startup, for example i want to use the frontkamera.

I can only choose a different camera via the contextmenu and then onDeviceSelectChange gets called and this works.

import { Component, OnInit, ViewChild } from '@angular/core';
import { ZXingScannerComponent, ZXingScannerModule } from '@zxing/ngx-scanner';
import { BarcodeFormat } from '@zxing/library';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-scanner',
  standalone: true,
  imports: [ZXingScannerModule, FormsModule],
  template: `
    <div class="scanner-container">
      <h2>QR/Barcode Scanner</h2>

      <!-- Camera selection dropdown -->
      <div class="camera-selection">
        <label for="cameraSelect">Kamera auswählen:</label>
        <select
          id="cameraSelect"
          [(ngModel)]="selectedDevice"
          (change)="onDeviceSelectChange($event)"
        >
          <option [value]="null">Keine Kamera ausgewählt</option>
          @for (device of availableDevices; track device.deviceId) {
            <option [value]="device.deviceId">
              {{ device.label || 'Kamera ' + device.deviceId }}
            </option>
          }
        </select>
      </div>

      <!-- Scanner Component -->
      <zxing-scanner
        #scanner
        [device]="currentDevice"
        (camerasFound)="onCamerasFound($event)"
        (scanSuccess)="onCodeResult($event)"
        (permissionResponse)="onHasPermission($event)"
        [tryHarder]="true"
        [formats]="allowedFormats"
      >
      </zxing-scanner>

      <!-- Scan result -->
      @if (qrResultString) {
        <div class="result">
          <h3>Scan-Ergebnis:</h3>
          <p>{{ qrResultString }}</p>
        </div>
      }

      <!-- Debug information (optional) -->
      @if (availableDevices.length > 0) {
        <div class="debug-info">
          <h4>Verfügbare Kameras:</h4>
          <ul>
            @for (device of availableDevices; track device.deviceId) {
              <li>{{ device.label || 'Unbenannte Kamera' }} (ID: {{ device.deviceId }})</li>
            }
          </ul>
        </div>
      }
    </div>
  `,
  styles: [`
    .scanner-container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }

    .camera-selection {
      margin-bottom: 20px;
    }

    select {
      width: 100%;
      padding: 8px;
      margin-top: 5px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }

    zxing-scanner {
      display: block;
      width: 100%;
    }

    .result {
      margin-top: 20px;
      padding: 15px;
      background-color: #f0f0f0;
      border-radius: 4px;
    }

    .debug-info {
      margin-top: 20px;
      padding: 15px;
      background-color: #f5f5f5;
      border-radius: 4px;
      font-size: 0.9em;
    }

    .debug-info ul {
      margin: 10px 0;
      padding-left: 20px;
    }
  `]
})
export class ScannerComponent implements OnInit {
  @ViewChild('scanner', { static: false })
  scanner!: ZXingScannerComponent;

  availableDevices: MediaDeviceInfo[] = [];
  currentDevice: MediaDeviceInfo | undefined;
  selectedDevice: string | null = null;

  qrResultString: string = '';
  hasPermission: boolean = false;

  // Allowed barcode formats
  allowedFormats: BarcodeFormat[] = [
    BarcodeFormat.QR_CODE
  ];

  ngOnInit() {
    // Camera permission is automatically requested when the component loads
    console.log('Scanner component initialized');
  }

  onCamerasFound(devices: MediaDeviceInfo[]): void {
    this.availableDevices = devices;

    // Debug: Log all available cameras
    console.log('Available cameras:', devices);
    devices.forEach((device, index) => {
      console.log(`Camera ${index}: ${device.label || 'Unnamed'} - ID: ${device.deviceId}`);
    });

    // Automatically select the back camera (if available)
    if (devices && devices.length > 0) {
      let backCamera: MediaDeviceInfo | undefined;

      backCamera = devices.find(
        device => device.label.toLowerCase() === 'frontkamera'
      ); // TODO: it finds the camera but doesn't fully apply it. In any case, the camera with a strong zoom is still used.

      // 2nd priority: substring search if no exact match
      if (!backCamera) {
        backCamera = devices.find(
          device =>
            device.label.toLowerCase().includes('back') ||
            device.label.toLowerCase().includes('rear') ||
            device.label.toLowerCase().includes('rück') ||
            device.label.toLowerCase().includes('hinten')
        );
      }

      let deviceToSet: MediaDeviceInfo | undefined;

      if (backCamera) {
        console.log('Back camera found:', backCamera.label);
        this.selectedDevice = backCamera.deviceId;
        this.currentDevice = backCamera;
      } else if (devices.length > 1) {
        // If no back camera was found, use the second camera (often the back camera on iOS)
        console.log('No back camera found, using camera at index 1');
        this.selectedDevice = devices[1].deviceId;
        this.currentDevice = devices[1];
      } else {
        // Otherwise, use the first available camera
        console.log('Only one camera available, using it');
        this.selectedDevice = devices[0].deviceId;
        this.currentDevice = devices[0];
      }
    }
  }

  onDeviceSelectChange(event: Event): void {
    const selectElement = event.target as HTMLSelectElement;
    const deviceId = selectElement.value;

    if (deviceId && deviceId !== 'null') {
      this.currentDevice = this.availableDevices.find(device => device.deviceId === deviceId);
      console.log('Switched camera to:', this.currentDevice?.label);
    } else {
      this.currentDevice = undefined;
    }
  }

  onCodeResult(resultString: string): void {
    this.qrResultString = resultString;
    console.log('Scan successful:', resultString);

    // Optionally stop the scanner after a successful scan
    // this.scanner.reset();

    // Optional: vibration feedback (if available)
    if ('vibrate' in navigator) {
      navigator.vibrate(200);
    }
  }

  onHasPermission(has: boolean): void {
    this.hasPermission = has;
    console.log('Camera permission:', has ? 'Granted' : 'Denied');

    if (!has) {
      alert('Please allow camera access for this application.');
    }
  }

  // Additional helper methods

  toggleCamera(): void {
    // Toggle between front and back camera
    const currentIndex = this.availableDevices.findIndex(
      device => device.deviceId === this.currentDevice?.deviceId
    );

    if (currentIndex !== -1 && this.availableDevices.length > 1) {
      const nextIndex = (currentIndex + 1) % this.availableDevices.length;
      this.currentDevice = this.availableDevices[nextIndex];
      this.selectedDevice = this.currentDevice.deviceId;
      console.log('Switched camera to:', this.currentDevice.label);
    }
  }

  resetScanner(): void {
    this.qrResultString = '';
    this.scanner.reset();
    console.log('Scanner reset');
  }

  // Method to manually start the scanner
  startScanner(): void {
    if (this.scanner && this.currentDevice) {
      this.scanner.restart();
    }
  }

  // Method to stop the scanner
  stopScanner(): void {
    if (this.scanner) {
      this.scanner.reset();
    }
  }
}

Smartphone (please complete the following information):

  • Device: IPhone 16 Pro German Language

joshua-classen avatar Jun 18 '25 19:06 joshua-classen