Reputation: 528
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.
Upvotes: 5
Views: 5469
Reputation: 1
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)
}
Upvotes: 0
Reputation: 311
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();
}
`
Upvotes: 0
Reputation: 121
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
Upvotes: 0
Reputation: 139
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);
}
Upvotes: 6
Reputation: 13799
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.
Upvotes: 2