4

I'm trying to create a photo capture web app on a nodeJS server, and i'm using the javascript code below.

const btn = document.querySelector('#btn');

btn.addEventListener('click', (event) => {
 navigator.mediaDevices.getUserMedia({video: true})
  .then(gotMedia)
  .catch(error => console.error('getUserMedia() error:', error));

  event.preventDefault()
})

And the gotMedia function is this:

function gotMedia(mediaStream) {
    const mediaStreamTrack = mediaStream.getVideoTracks()[0];
    const imageCapture = new ImageCapture(mediaStreamTrack);
    console.log(imageCapture);

    const canvas = document.querySelector('canvas');
    // ...
    imageCapture.grabFrame()
    .then(imageBitmap => {
        canvas.width = imageBitmap.width;
        canvas.height = imageBitmap.height;
        canvas.getContext('2d').drawImage(imageBitmap, 0, 0);
    })
    .catch(error => console.error('grabFrame() error:', error));
}

Everything works fine, the image capturing is ok, but an error occurs, when i snap photos rapidly one after another, that says:

grabFrame() error: DOMException: The associated Track is in an invalid state.

This usually happens when i capture too many photos (like clicking rapidly for about more than 20 seconds), but it also has happened on the first five snapshots. Does anyone know what's going on and what should i change, in order to fix this? Thank you for your time.

we_mor
  • 329
  • 2
  • 12
  • Did you ever figure this out? I'm running into the exact same thing. I wouldn't mind the exception if it didn't appear to kill the video stream permanently as well, even if caught. – hunterloftis Mar 26 '20 at 19:58
  • Unfortunately i haven't found any solution yet, but i'm still trying to figure this out and if i make it, i'll let u know – we_mor Mar 27 '20 at 13:50
  • hi, I am also running into the exact same thing. The loop starts to take photos every 20 seconds using the same function, after a series of photos taken, it starts to throw this (The associated Track is in an invalid state) error. How should we fix the invalid state? Any one of you succeeded to come up with a solution for this? Thanks – Mustafa Ekim Feb 22 '21 at 07:15

3 Answers3

4

According to the spec such error could be result of not acceptable state. In chromium sources I have found this method.

I've overcame error using code like this:

  const promise = getPrevImagePromise();

  if (!promise && !(imageCapture.track.readyState != 'live' || !imageCapture.track.enabled || imageCapture.track.muted)) {
    const imagePromise = imageCapture.grabFrame()
      .then((image) => {
        // do work with image
      })
      .then(() => {
        deletePrevImagePromise()
      })
      .catch((error) => {
        // 
      });

    setPrevImagePromise(imagePromise);
  }
fomasha
  • 101
  • 4
  • You saved my day. Checking for `readyState` was is mandatory on my application before calling `grabFrame()`. – art May 13 '20 at 11:10
3

I ran into the same issue and was unable to get the bleeding-edge grabFrame function to work reliably.

Instead, you can draw from an intermediate video source. In your function, that would look something like this (untested):

function gotMedia(mediaStream) {
    const mediaStreamTrack = mediaStream.getVideoTracks()[0];
    const canvas = document.querySelector('canvas');
    const video = document.createElement('video');

    video.autoplay = true;
    video.srcObject = mediaStream;
    video.onplay = () => {
         canvas.width = video.videoWidth;
         canvas.height = video.videoHeight;
         canvas.getContext('2d').drawImage(video, 0, 0);
    };
}

Of course, if you're going to be capturing a lot of frames, it'd be best to create a single video alongside the stream and to capture from that each time.

hunterloftis
  • 11,934
  • 5
  • 40
  • 45
0

My two cents:

let preview = document.getElementById('preview')
let recording = document.getElementById('recording')
let photo = document.getElementById('photo')
let photoButton = document.getElementById('photoButton')
let imageHolder = document.getElementById('image-holder')

async function startWebcam() {
  return navigator.mediaDevices.getUserMedia({
    video: true
  })
};

photoButton.addEventListener('click', async() => {
  let webcamStream = await startWebcam()
  preview.srcObject = webcamStream
  const track = webcamStream.getVideoTracks()[0]
  const imageCapture = new ImageCapture(track)
  const bitmap = await imageCapture.grabFrame()
  const canvas = document.getElementById('canvas')
  canvas.width = bitmap.width
  canvas.height = bitmap.height
  const context = canvas.getContext('2d')
  context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height)
  let image = canvas.toDataURL()
  imageHolder.innerHTML += `<img src="${image}"></img>`
  track.stop()

})
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Desktoprecord</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">
  <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
  <script src="node_modules/axios/dist/axios.min.js"></script>
  <script src="app4.js" defer></script>
</head>

<div class="container">
  <video id="preview" width="25%" height="auto" autoplay muted poster="https://placekitten.com/g/150"></video>
  <canvas id="canvas"></canvas>
  <img src="http://placekitten.com/g/320/261" id="photo" alt="photo">
  <div id="image-holder">
    To be replaced with pretty pictures
  </div>
  <video src="" id="preview"></video>
  </jdiv>
  <div class="buttons are-large">
    <button id="photoButton" class="button is-success"><i class="fas fa-camera"></i></button>
  </div>
</div>

Each time you are going to take a photo you start the camera and after you got the pic you have to stop the track

Marco Santana
  • 69
  • 1
  • 12