Javascript DOMException: The associated Track is in an invalid state
Asked Answered
B

5

5

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.

Beyond answered 15/3, 2020 at 13:40 Comment(3)
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.Flowerlike
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 knowBeyond
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? ThanksRattlebox
T
6

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);
  }
Tract answered 16/4, 2020 at 9:34 Comment(1)
You saved my day. Checking for readyState was is mandatory on my application before calling grabFrame().Marylynnmarylynne
F
2

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.

Flowerlike answered 27/3, 2020 at 15:49 Comment(0)
S
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/[email protected]/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

Satem answered 5/8, 2020 at 20:44 Comment(0)
A
0

I want to grabFrame() in a loop (reused without stop()), thanks to fomasha, my code works as follow `

async function aSleep(ms) {
    return new Promise(R => setTimeout(R, ms));
}
var prevP=null;
function getPrevImagePromise() {return prevP;}
function setPrevImagePromise(p) {prevP=p;}
function deletePrevImagePromise() {prevP=null;}
async function aRun() {
     imageCapture = new ImageCapture(gImageTrack);
  while(true) {
    const promise = getPrevImagePromise();
    if (!promise && !(imageCapture.track.readyState != 'live' || !imageCapture.track.enabled || imageCapture.track.muted)) {
    const imagePromise = imageCapture.grabFrame()
      .then((image) => {
        context.drawImage(image, 0, 0, 640, 480);
      })
      .then(() => {
        deletePrevImagePromise()
      })
      .catch((error) => {
        // 
      });
      setPrevImagePromise(imagePromise);
    }
    await aSleep(500);
  }
}

` refactoring

`

function zCamera() {
    var T=this;
    T.aInit=async function() {
        T.mStm=await navigator.mediaDevices.getUserMedia({ video: { width: 4096, height: 3200, facingMode: 'environment' } });
        T.mTrk=T.mStm.getTracks()[0];
        T.mCap=new ImageCapture(T.mTrk);
    }
    T.getSetting=function() {
        return T.mTrk.getSettings();
    }
    T.aGrabFrame=async function() {
      var P=new Promise((R)=>{
        T.mCap.grabFrame().then((vImg) => {
          R(vImg);
        }).catch((error) => {
          R(null);
        });
      });
      return P;    
    }
}
var gContext;
var oCamera;
async function aInitCamera()
{
    var canvas=document.getElementById('canvas');
    gContext=canvas.getContext('2d');
    oCamera=new zCamera();
    await oCamera.aInit();
}
async function aRun() {
    while(true) {
        var wImg=await oCamera.aGrabFrame();
        if (wImg!=null)
            gContext.drawImage(wImg, 0, 0, 640, 480);
        await aSleep(100);
    }
}
async function jMain() {
    await aInitCamera();
    aRun();
}

`

Aegis answered 19/1, 2022 at 10:2 Comment(0)
V
0

The error has gone when I clone the track

function(){
        navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
            const track = stream.getVideoTracks()[0]

            const imageCapture = new ImageCapture(track.clone())

            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('bitmaprenderer')

            setInterval(() => {
                if (track.readyState === 'live')
                    imageCapture.grabFrame().then((bmp: ImageBitmap) => {

                        canvas.height = bmp.height
                        canvas.width = bmp.width
                        ctx.transferFromImageBitmap(bmp)

                        new Promise(res => canvas.toBlob(res)).then((blob) =>
                            imageRef.current.src = URL.createObjectURL(blob as MediaSource))

                    }).catch(err => { console.log(err) })

            }, 1000)

}
Vaporing answered 24/2, 2023 at 6:24 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Cornemuse

© 2022 - 2024 — McMap. All rights reserved.