Capacitor - Camera Preview

Présentation

Le but est de tester le plugin suivant : https://github.com/capacitor-community/camera-preview.

Pour commencer, l’idée est de suivre un tuto que j’ai trouvé mais qui semble dater un peu : https://www.youtube.com/watch?v=JA8k738i9jQ.

C’est de l’angular … Ca fait TRES longtemps donc je croise les doigts pour qu’il n’y ait pas trop de différence entre le tuto et la version d’Angular actuell

Tuto Ionic

Init

Alors comme d’hab, la première commande ne fonctionne pas :
npx ionic start cameraPreview blank --type=angular --capacitor

J’obtiens une erreur en rapport avec un paramètre qui ne serait plus supporter. Bon, j’ai donc autre chose à faire donc j’essaye de faire autrement :

# Init du projet
npx ionic start cameraPreview blank 
# 
cd cameraPreview/
# Ajout de capacitor
npm i @capacitor/core
npm i -D @capacitor/cli
# Init
npm cap init
# Ajout du plugin
npm install @capacitor-community/camera-preview
# Ajout d'android
npm install @capacitor/android
npx cap add android
# Sync & build
npx cap sync
npx ionic build

Le tuto indique une mise à jour dans le MainActivity.java présent dans la doc à ce moment là mais qui ne semble plus présent actuellement. La seule note sur Android est l’ajout des permissions :

<uses-permission android:name="android.permission.CAMERA" />

Un changement au niveau des plugins dans Capacitor ? Il semble possible de récupérer le plugin directement

import { CameraPreview, CameraOpacityOptions, CameraPreviewPictureOptions } from '@capacitor-community/camera-preview';

Mise en place

Il faut mettre en place un bouton qui appelle fonction suivante :

  openCamera() {
    // Création de l'option
    const cameraPreviewOptions : CameraPreviewOptions = {
      position: 'rear', // On s'embête pas, on prend la camera de derrière. Uniquement pour le web
      parent: 'cameraPreview', // Non documenté dans la liste mais présent dans la doc. Correspond au div
      className: 'cameraPreview' // Uniquement Web aussi
    }
    // Démarrage de la camera
    CameraPreview.start(cameraPreviewOptions);
    // Passe le marqueur à vrai
    this.cameraActive = true;
  }

Il ne faut pas oublier d’ajouter un div dont l’id est cameraPreview sinon une belle erreur (mais propre : parent is null).

Et là : incroyable mais ca marche :). En mode web, j’ai bien ma tête qui apparait dans l’écran.

Mode Android

Après plusieurs recherche, (pas tant que cela), j’ai ajouté cela dans le fichier global CSS :

body {
  background-color: transparent !important;
}

Sans Ionic

Forcément, svelte

Après Ionic, j’ai voulu essayé autre chose et même de me passer du plugin en me disant qu’il est peut-être possible de passer par le stream ?

Après l’init premier problème

Mes recherches ont confirmés que cela semblait possible. Pour cela, il faut faire un truc du style : mapper le stream récupérer depuis le navigateur vers l’objet video puis prendre une image.

Premier souci : il ne semble pas possible de binder directement sur la propriété srcObject qui est celle sur laquelle il faudrait mapper le flux (enfin je crois :).

J’ai fait simple :

// -- Démarrage de la caméra
const startCamera = async () => {
    if (videoPlayer) {
        try {
            // Device selected ?
            let c : MediaStreamConstraints = selectedDeviceId ?  { video: { deviceId : selectedDeviceId }} : { video: true }
            stream = await navigator.mediaDevices.getUserMedia(c);
            videoPlayer.srcObject = stream;
            // Affichage
            showVideo = true;
        } catch (error: any) {
            manageError(error.message);
        }
    }
};

Après des recherches, j’ai vu que d’autres utilisaient des actions. J’ai pas ce réflexe.

Ensuite l’astuce (fourni par MDN :)) est de remplir un canvas avec le flux vidéo puis de récupérer la photo :

// -- Prise d'une photo
const handleTakePicture = async () => {
    // Test
    if (!videoPlayer || !canvas) {
        manageError("Ni vidéo ni zone pour la capture ...");
        return;
    }
    // Mise à jour du canvas (qui n'est pas visible)
    // -- Taille
    canvas.width = videoPlayer.videoWidth;
    canvas.height = videoPlayer.videoHeight;
    // -- Context
    const ctx = canvas.getContext("2d");
    if (!ctx) {
        manageError("Impossible de récupérer le canvas");
        return;
    }
    // -- Mise à jour
    ctx.drawImage(videoPlayer, 0, 0, canvas.width, canvas.height);

    // Enregistrement de la photo
    // -- Récupération du flux
    let imageData = canvas.toDataURL();
    console.log(imageData);
    // -- Ajout dans la liste
    imagesList.push({
        dataUrl: imageData,
        date: new Date(),
    });
};

Et les photos apparaissent bien dans la liste.

Un point cependant : la taille de photo est basée sur la taille de la vidéo. Ce qui est quand même limitée. J’ai essayé de jouer avec les résolutions en jouant sur les contraintes :

 let c: MediaStreamConstraints = { 
    audio: false,
    video: {
        width: selectedResolution.width,
        height: selectedResolution.height,
        deviceId : selectedDeviceId ? selectedDeviceId : undefined
    }
}

mais sans vraiment réussir à obtenir ce que je voulais …

ImageCapture

En passant d’une recherche à l’autre, une page parlait ImageCapture

Cela fonctionne que sur Chrome (au moment de l’écriture de cette page :) :

// -- Récupération de la track
  const track = stream!.getTracks()[0];
  // -- Création de l'objet Image
  const imageCapture = new ImageCapture(track);
  const photosCaps = await imageCapture.getPhotoCapabilities();
  console.log(photosCaps);
  // -- Prise de la photo
  const blob : Blob = await imageCapture.takePhoto();
  console.log(blob);
  // --> Ajout dans la liste
  imagesList.push({
      dataUrl: URL.createObjectURL(blob),
      date: new Date(),
  label: "from ImageCapture"
  });

J’ai vu que les photos étaient limitées à 640 / 480 … change pas grand chose.

Installation de Capacitor

npm i @capacitor/core
npm i -D @capacitor/cli
npm i @capacitor/android
npx cap add android
npx cap sync
npx cap open android

Et la pour une fois … ca marche du premier coup :)

Maintenant pour faire ce que je veux ce ne serait que du CSS et franchement le CSS …

Svelte - CameraPreview

Incroyable mais …

Allez, on essaye quand même avec CameraPreview. Et encore une fois : CA MARCHE !!!.

Les étapes :

  • instal : RAS,
  • Mise en place : RAS,
  • Reprise du code du proto : RAS.

Bref, une fois n’est pas coutume … Pas de soucis particulier.

Quelques adaptations

Juste pour le plaisir, j’ai fait quelques adaptations qui au final sont nécessaires pour certaines.

La première : couper la caméra quand on sort de l’écran. C’est bête mais sinon l’application crash.

Autre, essayer de faire des boutons jolies. Exemple le bouton qui permet de prendre les photos :

.photo-button-container {
    position: relative;
    width: 80px; /* Assurez-vous que la largeur et la hauteur sont les mêmes */
    height: 80px; /* Assurez-vous que la largeur et la hauteur sont les mêmes */
    margin: 0 auto; /* Centre le bouton horizontalement */
}

.photo-button {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: #007bff;
    border: none;
    color: white;
    font-size: 14px;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    z-index: 1;
    padding: 0; /* Supprime le padding par défaut de Bootstrap */
}

.photo-button::before {
    content: '';
    position: absolute;
    top: -10px;
    left: -10px;
    right: -10px;
    bottom: -10px;
    border: 2px solid #007bff;
    border-radius: 50%;
    z-index: -1;
}