Vanilagy/webm-muxer

Chrome throwing error and failing to export video

Closed this issue · 2 comments

I had code that was working, I don't think anything changed but I'm now getting this error.

A VideoFrame was garbage collected without being closed. Applications should call close() on frames when done with them to prevent stalls.

I'm not explicitly calling close() anywhere but neither are you in your example code.

I'm using the newest published version of this library and passing canvas frames into code like this.


export default class WebM {
  constructor(width, height, transparent = true, fps) {
    this.muxer = new Muxer({
      target: new ArrayBufferTarget(),
      video: {
        codec: 'V_VP9',
        width: width,
        height: height,
        frameRate: fps,
        alpha: transparent,
      },
      audio: undefined,
      firstTimestampBehavior: 'offset',
    });


    this.videoEncoder = new VideoEncoder({
      output: (chunk, meta) => this.muxer.addVideoChunk(chunk, meta),
      error: (error) => reject(error),
    });
    this.videoEncoder.configure({
      codec: 'vp09.00.10.08',
      width: width,
      height: height,
      bitrate: 1e6,
    });
  }

  addFrame(frame, time, frameIndex) {
    return new Promise((resolve) => {
      this.videoEncoder.encode(new VideoFrame(frame, { timestamp: time * 1000 }), {
        keyFrame: !(frameIndex % 50),
      });
      resolve();
    });
  }

  generate() {
    return new Promise((resolve, reject) => {
      this.videoEncoder
        .flush()
        .then(() => {
          this.muxer.finalize();
          resolve(new Blob([this.muxer.target.buffer], { type: 'video/webm' }));
        })
        .catch(reject);
    });
  }
}

I actually do call close in my demo code! Here:

// ...
const encodeVideoFrame = () => {
    let elapsedTime = document.timeline.currentTime - startTime;
    let frame = new VideoFrame(canvas, {
        timestamp: framesGenerated * 1e6 / 30
    });
    framesGenerated++;

    let needsKeyFrame = elapsedTime - lastKeyFrame >= 10000;
    if (needsKeyFrame) lastKeyFrame = elapsedTime;

    videoEncoder.encode(frame, { keyFrame: needsKeyFrame });
    frame.close(); // <=== THIS LINE HERE

    recordingStatus.textContent =
        `${elapsedTime % 1000 < 500 ? '🔴' : '⚫'} Recording - ${(elapsedTime / 1000).toFixed(1)} s`;
};
// ...

The warning you're talking about is typically benign and just wants to encourage the programmer to manually GC video frames for better memory management.

In your line:

this.videoEncoder.encode(new VideoFrame(frame, { timestamp: time * 1000 }), {
  keyFrame: !(frameIndex % 50),
});

you'll need to keep a reference to the frame to garbage collect it later on:

let frame = new VideoFrame(frame, { timestamp: time * 1000 });
this.videoEncoder.encode(frame, {
  keyFrame: !(frameIndex % 50),
});
frame.close();

You are totally correct, all of this is working as it should, thank you for the reply.