georapbox / barcode-scanner

A Progressive Web Application (PWA) that scans barcodes of various formats, using the Barcode Detection API.
https://georapbox.github.io/barcode-scanner/
MIT License
60 stars 28 forks source link

Implementation with Angular App #11

Closed foch01 closed 1 month ago

foch01 commented 1 month ago

Hello,

Thank you for this good work

I have tried many open source libraries for codebar scanning and I find that this application is the one that works the best.

I want to embed it in an Angular application.

Do you have any documentation or example implementation on this ? I will be very interested.

georapbox commented 1 month ago

Hi,

Thanks for the kind words! I'm glad the barcode scanner is working well for you.

Right now, the project is built as a standalone application, so it's not set up to be embedded directly into something like an Angular app.

That said, I'm currently working on refactoring and cleaning up the code. There's a chance that something reusable could come out of this process, which might make integration easier in the future.

For now, you can check out issue #10 where I’m breaking down some pieces of the application responsible for the camera scanner side of the app.

Thanks for your interest and stay tuned for updates!

foch01 commented 1 month ago

Ok thank you very much for your response.

I was able to take inspiration from your answer and make it work in my Angular app.

However I encounter a problem on the Google Chrome mobile iOS browser only.

It works on Safari iOS and Mac and on Google Chrome Mac but not on iOS. However, on the link in your demonstration it works well on all browsers.

I would be grateful if you see the problem somewhere

Here is the case of reproduction of the bug:

When I first access the website on Google Chrome Mobile iOS it asks me for the necessary authorizations to have access to the camera. I give permissions. The video stream is available on the web page. I refresh the page, I no longer have the request for access to the camera and the video stream is no longer broadcast.

To obtain the video stream and the permission requests I have to uninstall the Chrome iOS mobile app and reinstall it.

On your demo application the problem is not reproduced. The app asks me for approval of camera access permissions each time the page is refreshed and the video stream is loaded on Chrome Mobile iOS.

Here is my complete implementation to reproduce the issue:

export class AppComponent implements OnInit {
  title = 'white-app';

  ngOnInit() {
    CapturePhoto.defineCustomElement();

    const capturePhotoEl: any = document.querySelector('capture-photo');
    const scanBtn = document.getElementById('scanBtn');

    capturePhotoEl!.addEventListener(
        'capture-photo:video-play',
        (evt: any) => {
          this.scan(capturePhotoEl, scanBtn);

          // Perform any other side effects here, when the video starts playing...
          const trackSettings = evt.target.getTrackSettings();
          const trackCapabilities = evt.target.getTrackCapabilities();
          const zoomLevelEl = document.getElementById('zoomLevel');

          if (trackSettings?.zoom && trackCapabilities?.zoom) {
            const zoomControls = document.getElementById('zoomControls');
            const minZoom = trackCapabilities?.zoom?.min || 0;
            const maxZoom = trackCapabilities?.zoom?.max || 10;
            let currentZoom = trackSettings?.zoom || 1;

            zoomControls!.hidden = false;
            zoomLevelEl!.textContent = currentZoom;

            zoomControls!.addEventListener('click', (evt: any) => {
              const zoomInBtn = evt.target.closest('[data-action="zoom-in"]');
              const zoomOutBtn = evt.target.closest('[data-action="zoom-out"]');

              if (zoomInBtn && currentZoom < maxZoom) {
                currentZoom += 0.5;
              }

              if (zoomOutBtn && currentZoom > minZoom) {
                currentZoom -= 0.5;
              }

              zoomLevelEl!.textContent = currentZoom;

              capturePhotoEl.zoom = currentZoom;
            });
          }
        },
        {
          once: true,
        },
    );

    scanBtn!.addEventListener('click', async () => {
      scanBtn!.hidden = true;
      await this.scan(capturePhotoEl, scanBtn);
    });
  }

  async scan(capturePhotoEl: any, scanBtn: any) {
    console.log('Scanning...');
    let rafId;
    try {
      const capturePhotoVideoEl =
          capturePhotoEl!.shadowRoot!.querySelector('video');
      let barcode: unknown = {};
      barcode = await this.detectBarcode(capturePhotoVideoEl);

      if (rafId) {
        window.cancelAnimationFrame(rafId);
      }
      // Display the scan button
      scanBtn!.hidden = false;

      // Perform any other side effects here, after the barcode is detected.
      // For example, you can show the barcode data in the UI.

      return;
    } catch (err) {
      // As the scanner keeps scanning if no barcode is detected, it will throw an error.
      // We don't want to show this error messages to the user because the scanner will keep scanning,
      // therefore we let it fail silently.
    }

    // We use requestAnimationFrame to keep scanning for barcodes in a loop.
    rafId = window.requestAnimationFrame(() =>
        this.scan(capturePhotoEl, scanBtn),
    );
  }

  detectBarcode(source: any) {
    const barcodeDetector = new BarcodeDetector();
    return new Promise((resolve, reject) => {
      barcodeDetector
          .detect(source)
          .then((results) => {
            if (Array.isArray(results) && results.length > 0) {
              resolve(results[0]);
            } else {
              reject({
                message: 'Could not detect barcode from provided source.',
              });
            }
          })
          .catch((err) => {
            reject(err);
          });
    });
  }
}
foch01 commented 1 month ago

I just added a setTmeout() and it works. This is probably related to the lifecycle of the Angular app. I haven't found a better solution to get around this yet.

georapbox commented 1 month ago

I'm afraid I'm not familiar to Angular's internals but I'm curious where you used setTimeout for this to work?