Camera Preview Shows White Screen in APK - Overlay Visible but Camera Not Displayed

I’ve been working on an Ionic project that involves using the Capacitor Camera Preview plugin to take selfies. Everything works fine when I run the app in the browser or on an emulator. However, when I build the APK and run it on a physical device, the camera preview shows a white screen. The overlay elements are visible, but the camera feed is not displayed.

I’ve ensured that the CameraPreviewOptions are correctly set, and I’ve tried adjusting the z-index and background styles as suggested in various forums, but the issue persists. Here are the details of my setup:

Relevant Code Snippets

TypeScript (bp-tirar-selfie.component.ts):

import { Component, OnInit } from '@angular/core';
import { CameraPreview, CameraPreviewOptions } from '@capacitor-community/camera-preview';
import { SharedModule } from '../../shared.module';
import { InstrucoesSelfieComponent } from './modal-instrucoes-selfie/instrucoes-selfie.component';
import { ModalController, NavController } from '@ionic/angular';
import { VisualizarFotoComponent } from './modal-visualizar-foto/visualizar-foto.component';
import { OverlayService } from 'src/app/core/services/overlay/overlay.service';

const cameraPreviewOptions: CameraPreviewOptions = {
  position: 'front',
  className: 'cameraPreview',
  parent: 'cameraPreview', // Ensure this element exists in your template
  toBack: true, // Push the camera preview behind the web view content
};

@Component({
  selector: 'app-bp-tirar-selfie',
  templateUrl: './bp-tirar-selfie.component.html',
  styleUrls: ['./bp-tirar-selfie.component.scss'],
  standalone: true,
  imports: [SharedModule],
})
export class BpTirarSelfieComponent implements OnInit {
  public cameraActive = false;
  public image: string | null = null;
  public selfieUsuario = '';

  constructor(private modalCtrl: ModalController, private overlayService: OverlayService) { }

  ngOnInit() {
    this.mostrarInstrucoes();
  }

  public async mostrarInstrucoes() {
    const modal = await this.modalCtrl.create({
      component: InstrucoesSelfieComponent,
      backdropDismiss: false,
    });

    modal.onDidDismiss().then(() => {
      this.openCamera();
    });

    return await modal.present();
  }

  public openCamera() {
    CameraPreview.start(cameraPreviewOptions);
    this.cameraActive = true;
  }

  public async takePicture() {
    try {
      const result = await CameraPreview.capture();
      const base64PictureData = result.value;
      sessionStorage.setItem('fotoFace', base64PictureData);
      this.stopCamera();
      await this.verificarFoto(base64PictureData);
    } catch (error) {
      console.error(error);
      this.overlayService.toast({
        message: 'Erro ao tirar foto, por favor tente novamente.',
        duration: 2000,
        color: 'danger',
      });
    }
  }

  public async verificarFoto(foto: string) {
    const modal = await this.modalCtrl.create({
      component: VisualizarFotoComponent,
      componentProps: {
        foto: 'data:image/png;base64,' + foto,
      },
    });

    modal.onDidDismiss().then((ret) => {
      if (ret.data && ret.data === 'mudar') {
        sessionStorage.removeItem('fotoFace');
        this.openCamera();
      } else {
        this.returnPage();
      }
    });

    return await modal.present();
  }

  public async returnPage() {
    await this.stopCamera();
    this.modalCtrl.dismiss();
  }

  public async switchCamera() {
    CameraPreview.flip();
  }

  public async stopCamera() {
    CameraPreview.stop();
  }
}

HTML (bp-tirar-selfie.component.html):

<ion-content>
  <div id="cameraPreview" class="cameraPreview">
    <!-- Camera feed will be displayed here -->
  </div>
  <img class="molde-cabeca" src="/assets/images/perfil/face.png" *ngIf="cameraActive" />
  <p class="texto" *ngIf="cameraActive">
    Enquadre seu rosto na moldura e aperte o botão Amarelo para tirar uma nova
    foto. Tire máscaras, óculos e outros adereços.
  </p>
  <div *ngIf="cameraActive">
    <ion-fab vertical="bottom" horizontal="start">
      <ion-fab-button color="light" (click)="returnPage()" routerDirection="back">
        <ion-icon name="arrow-back"></ion-icon>
      </ion-fab-button>
    </ion-fab>
    <ion-fab vertical="bottom" horizontal="center">
      <ion-fab-button color="warning" (click)="takePicture()">
        <ion-icon name="camera"></ion-icon>
      </ion-fab-button>
    </ion-fab>
    <ion-fab vertical="bottom" horizontal="end">
      <ion-fab-button color="light" (click)="switchCamera()">
        <ion-icon name="camera-reverse-sharp"></ion-icon>
      </ion-fab-button>
    </ion-fab>
  </div>
</ion-content>

CSS (bp-tirar-selfie.component.scss):

.cameraPreview {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1; /* Ensure it's behind other elements */
  background: transparent;
}

ion-content {
  --background: transparent; /* Make sure the background is transparent */
  position: relative; /* Ensure the content positions correctly */
  z-index: 0; /* Default z-index for content */
}

.molde-cabeca {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1; /* Ensure the frame is above the camera preview */
}

Problem Description

  • When running the app on a device using the APK, the camera preview area shows a white screen.
  • The overlay elements (e.g., the frame image) are visible and interactable.
  • The camera feed is not displayed, though the camera appears to function as expected (e.g., taking pictures works).

Assuming you’ve debugged (looked for any errors) on your Android device looking at DevTools via chrome://inspect/#devices and LogCat in Android Studio?

Also, what version of Capacitor and the plugin are you using?

It’s not showing any error log in chrome://inspect/#devices. Clicking the yellow button only takes a snapshot.


Those are the versions that I am using.

 "@capacitor-community/camera-preview": "^6.0.0",
    "@capacitor/android": "5.6.0",
    "@capacitor/app": "5.0.7",
    "@capacitor/browser": "^5.2.0",
    "@capacitor/core": "^6.1.0",
    "@capacitor/haptics": "5.0.7",
    "@capacitor/ios": "5.6.0",
    "@capacitor/keyboard": "5.0.8",
    "@capacitor/share": "^6.0.1",
    "@capacitor/status-bar": "5.0.7",

First, you have mix-matched versions for Capacitor and its plugins. Everything should be on v5 or v6.

Depending on if you are using Capacitor 5 or 6, you also need to use the correct version for @capacitor-community/camera-preview.

Version 6 of this plugin requires Capacitor 6.

If you are using Capacitor 5, use version 5
(source)

`I’ve tried to use version 5 of the plugin, but the error persists. So, I updated my Capacitor version, but I’m still getting the error.

Updating to 6.0 | Capacitor Documentation (capacitorjs.com)

"@capacitor-community/camera-preview": "^6.0.0",
"@capacitor/android": "^6.0.0",
"@capacitor/app": "^6.0.0",
"@capacitor/browser": "^6.0.0",
"@capacitor/core": "^6.0.0",
"@capacitor/haptics": "^6.0.0",
"@capacitor/ios": "^6.0.0",
"@capacitor/keyboard": "^6.0.0",
"@capacitor/share": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",`
"@capacitor/cli": "^6.0.0",

Hi everyone,

I wanted to update you on the issue I was facing with the camera preview showing a white screen in the APK. After further investigation and some trial and error, I found the solution.

Solution:

The problem was caused by using the Camera Preview plugin within a modal. It appears that the plugin does not function correctly when initialized inside a modal. Instead, it needs to be used directly on a page.

If the camera preview shows a white screen in your APK but overlays are visible, it could be due to camera permissions or compatibility issues with the device’s camera hardware. Check permissions in your manifest file and ensure your camera initialization code handles different device configurations correctly. Updating device drivers or testing on different devices may also help identify the issue.

Hi @GustavoBarreto01 , I also ran into this issue while trying to implement the @capacitor-community/camera-preview. plugin in a modal, this is what I did below

in the app.component.ts file

 async openModal() {
    const modal = await this.modalController.create({
      component: CameraModalComponent,
      id: 'pc-modal',
    });

    await modal.present();
  }

in the app.component.html file

<ion-content>
  <ion-button (click)="openModal()"> Open Camera </ion-button>
</ion-content>

in the global.scss file

.bg-transparent {
--background: transparent !important;
}

in the camera-component.ts file

import { DOCUMENT } from '@angular/common';
import {
  Component,
  ElementRef,
  Inject,
  inject,
  OnChanges,
  OnInit,
  SimpleChanges,
  ViewChild,
} from '@angular/core';
import {
  CameraPreview,
  CameraPreviewOptions,
  CameraPreviewPictureOptions,
} from '@capacitor-community/camera-preview';
import {
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonButton,
  IonIcon,
  IonImg,
  ModalController,
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { cameraReverse, camera, closeCircle } from 'ionicons/icons';
import { Platform } from '@ionic/angular';

@Component({
  selector: 'app-camera-modal',
  templateUrl: './camera-modal.component.html',
  styleUrls: ['./camera-modal.component.scss'],
  standalone: true,
  imports: [
    IonImg,
    IonIcon,
    IonButton,
    IonHeader,
    IonToolbar,
    IonTitle,
    IonContent,
  ],
})
export class CameraModalComponent implements OnInit {
  image!: string | null;
  cameraInView = false;
  constructor(
    @Inject(DOCUMENT) private document: Document,
    public platform: Platform
  ) {
    addIcons({ cameraReverse, camera, closeCircle });
  }

  ngOnInit() {
    this.openCamera();
  }

  modal = inject(ModalController);

  openCamera() {
    this.image = null;
   this.updateDomClass("pc-modal" true, "bg-transparent");
this.updateDomClass("pc-modal-content", true, "bg-transparent")
    const cameraPreviewOptions: CameraPreviewOptions = {
      position: 'front',
      parent: 'content',
      toBack: true,
      lockAndroidOrientation: true,
      disableExifHeaderStripping: true,
      width: window.screen.width, //width of the camera display
      height: window.screen.height,
    };
    CameraPreview.start(cameraPreviewOptions);
    this.cameraInView = true;
  }

  stopCamera() {
    CameraPreview.stop();
    this.updateDomClass("pc-modal", false, "bg-transparent");
this.updateDomClass("pc-modal-content", false, "bg-transparent")
    this.cameraInView = false;
  }

  async captureImage() {
    const cameraPreviewPictureOptions: CameraPreviewPictureOptions = {
      quality: 100,
    };
    const result = await CameraPreview.capture(cameraPreviewPictureOptions);

    this.image = this.platform.is('android')
      ? await this.rotateImage(result.value, -90)
      : `data:image/jpeg;base64,${result.value}`;
    this.stopCamera();
  }

  async flipCamera() {
    await CameraPreview.flip();
  }

  closeCamera() {
    this.stopCamera();
    this.modal.dismiss();
  } 
  
   updateDomClass(id: string, hideTabs: boolean, className: string) {
    const body = this.document.getElementById(id) as HTMLElement;
    hideTabs ? body?.classList.add(className) : body?.classList.remove(className);
  }


  async rotateImage(base64String: string, degrees: number) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const image = new Image();

    image.src = `data:image/jpeg;base64,${base64String}`;
    await image.decode();

    canvas.width = degrees % 180 === 0 ? image.width : image.height;
    canvas.height = degrees % 180 === 0 ? image.height : image.width;

    ctx?.translate(canvas.width / 2, canvas.height / 2);
    ctx?.rotate((degrees * Math.PI) / 180);
    ctx?.drawImage(image, image.width / -2, image.height / -2);

    return canvas.toDataURL();
  }
}

Camera.component.html

<ion-content [fullscreen]="true" id="pc-modal-content">
  @if (image) {
  <ion-img [src]="image"></ion-img>
  } @if (cameraInView) {
  <div id="content" class="cameraPreview">
    <div class="overlay"></div>
    <div class="camera-buttons">
      <ion-button (click)="closeCamera()" id="close" expand="block">
        <ion-icon name="close-circle" slot="icon-only"></ion-icon>
      </ion-button>

      <ion-button (click)="captureImage()" expand="block" id="capture">
        <ion-icon name="camera" slot="icon-only"></ion-icon>
      </ion-button>

      <ion-button (click)="flipCamera()" expand="block" id="flip">
        <ion-icon name="camera-reverse" slot="icon-only"></ion-icon>
      </ion-button>
    </div>
  </div>

  } @if (!cameraInView && image) {
  <ion-button (click)="openCamera()"> Recapture </ion-button>
  }
</ion-content>
1 Like