Skip to content

How to set a specific camera on startup? #590

@joshua-classen

Description

@joshua-classen

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions