Surya Adhikari
Surya Adhikari

Reputation: 33

maximum call stack size exceeded when rendering webcam video to canvas

I am developing an app where there will be two participant in a session and they can have video call. Basically, there will be 3 video stream. Two of the participants and one will be shared screen. This part is already working but when user clicks on record button, those 3 video should be fed in canvas and when paused then it should be paused. I am trying to render only one video in canvas for testing if it work "RangeError: Maximum call stack size exceeded on draw function ".

const draw = (canva, context) => {
    console.log('localVideoRef', localVideoRef)
    context.drawImage(localVideoRef.current, 0, 0, canva.clientWidth, canva.clientHeight);
    console.log('context draw', context)
    requestAnimationFrame(draw(canva, context))
  }
  const handleRecord = () => {
    const canva = document.createElement('canvas');
    canva.id = "canvas";
    const context = canva.getContext("2d");
    canva.width = canva.clientWidth;
    canva.width = canva.clientHeight;
    console.log('canva', context, canva);
    console.log('room', room);
    draw(canva, context);
    requestAnimationFrame(draw(canva, context)) # above error is shown here
    console.log('canva after drawing image', canva);
  }

The error is in this line requestAnimationFrame(draw(canva, context))

Update(still continuously runs even after stopping the record)

let canva, context = null;

function App(props) {
  const [record, setRecord] = React.useState('');
  React.useEffect(() => {
    canva = document.createElement('canvas');
    canva.id = "canvas";
    context = canva.getContext("2d");
    canva.width = canva.clientWidth;
    canva.width = canva.clientHeight;
  }, [])
  React.useEffect(() => {
    console.log('record', record);
    if (record === 'start' || record === 'resume') {
      requestAnimationFrame(() => draw(canva, context));
      console.log('canva after paint video', canva);
    } else {
      // when pausing or stoping recording, the painting should not be done. 
      console.log('this is the canva after paused or stopped', canva)
    }

  }, [record])
  const draw = (canva, context) => {
    context.drawImage(localMediaRef.current, 0, 0, canva.clientWidth, canva.clientHeight);
    console.log('painting');
    requestAnimationFrame(() => draw(canva, context));
  }
  const handleRecord = (type) => {
    setRecord(type)
  }
  render {
    return (

    )
  }
}

Upvotes: 0

Views: 1062

Answers (2)

Samleo
Samleo

Reputation: 605

Hi this is because you are already calling the requestAnimationFrame() in your draw() code. Hence the function is already recursive. By calling draw AND requestAnimationFrame(draw), you are calling 2 infinitely recursive functions at once. Simply remove the draw() and it will work:

const draw = (canva, context) => {
    console.log('localVideoRef', localVideoRef)
    context.drawImage(localVideoRef.current, 0, 0, canva.clientWidth, canva.clientHeight);
    console.log('context draw', context)
    requestAnimationFrame( (timestamp)=>draw(canva, context) ); // EDITED AS PER @Nicholas' answer.
}
const handleRecord = () => {
    const canva = document.createElement('canvas');
    canva.id = "canvas";
    const context = canva.getContext("2d");
    canva.width = canva.clientWidth;
    canva.width = canva.clientHeight;
    console.log('canva', context, canva);
    console.log('room', room);
    //draw(canva, context);        //just remove this line
    requestAnimationFrame( (timestamp)=>draw(canva, context) ) // EDITED AS PER @Nicholas' answer.
    console.log('canva after drawing image', canva);
}

Check out this documentation for more details on requestAnimationFrame. Notice that in their eg, requestAnimationFrame(step) is called, but step() is not called.

Update: As @Nicholas pointed out, you need to wrap your draw function in another function. This is shown above.

As for why it causes a maximum call size exceeded error, I'm actually a bit confused. When I tested the faulty code on Chrome console, I got this error instead:

Uncaught TypeError: Failed to execute 'requestAnimationFrame' on 'Window': The callback provided as parameter 1 is not a function.

This is actually correct in theory. Think of requestAnimationFrame as something similar to setTimeout. setTimeout uses a callback like setTimeout(callback, time);; so does requestAnimationFrame.

When you pass requestAnimationFrame( draw(canva, context) ), you are basically telling the requestAnimationFrame function to do something like draw(canva, context)() somewhere in its code, not draw(canva, context) as (I think) you are expecting. Hence it throws the callback provided is not a function error on my Chrome console.

But as for max call stack exceeded error, I think your JavaScript Engine/compiler's requestAnimationFrame catches the error, ignores it, and then proceeds to call the draw function as is. This thus translates basically to:

draw = (canva, context)=>{
    ...
    draw(canva, context) //oh noes
}

This is basically now an infinitely running recursive function. Hence, maximum call stack exceeded error.

Upvotes: 1

Nicholas Tower
Nicholas Tower

Reputation: 85122

requestAnimationFrame(draw(canva, context))

This code will immediately call draw, and then whatever the return value of draw is will be passed into requestAnimationFrame. Inside of draw, you repeat this process, immediately calling draw, which immediately calls draw, etc.

Instead, you want to pass a function into request animation frame, like this:

requestAnimationFrame(() => draw(canva, context));

Upvotes: 3

Related Questions