async function setupCapture() {
  const video = document.querySelector('#videoInput');
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: false })
    .then((stream) => {
      video.srcObject = stream;
      video.play();
    })
    .catch((err) => console.error(err));
  return video;
}

function loadClassifiers(classifierFile, classifier) {
  let utils = new Utils('errorMessage');
  return new Promise((resolve, reject) => {
    utils.createFileFromUrl(classifierFile, classifierFile, () => {
      classifier.load(classifierFile);
      resolve();
    });
  });
}

function onOpenCvReady() {
  cv['onRuntimeInitialized'] = async () => {
    const video = await setupCapture();
    let destinationMat = new cv.Mat(video.height, video.width, cv.CV_8UC4);
    let grayMat = new cv.Mat();
    let cap = new cv.VideoCapture(video);
    let faces = new cv.RectVector();

    let classifier = new cv.CascadeClassifier();
    let faceCascadeFile = 'haarcascade_frontalface_default.xml';
    await loadClassifiers(faceCascadeFile, classifier);

    const FPS = 30;
    function processVideo() {
      let begin = Date.now();
      cap.read(destinationMat);
      cv.cvtColor(destinationMat, grayMat, cv.COLOR_RGBA2GRAY, 0);

      classifier.detectMultiScale(grayMat, faces, 1.1, 3, 0);

      for (let i = 0; i < faces.size(); ++i) {
        let face = faces.get(i);
        let point1 = new cv.Point(face.x, face.y);
        let point2 = new cv.Point(face.x + face.width, face.y + face.height);

        cv.rectangle(destinationMat, point1, point2, [255, 0, 0, 255]);
      }

      cv.imshow('canvasOutput', destinationMat);
      let delay = 1000 / FPS - (Date.now() - begin);
      setTimeout(processVideo, delay);
    }
    setTimeout(processVideo, 0);
  };
}
