Preview:
async function manipulate(videoTrack, fn) {
  let canvas = document.createElement("canvas");
  let running = true;
  const ctx = canvas.getContext("2d");
  let video = document.createElement("video");
  video.setAttribute("autoplay", true);
  video.setAttribute("muted", true);
  video.srcObject = createStream(videoTrack);
  videoTrack.addEventListener("ended", () => {
    running = false;
  });
  await new Promise((res) => video.addEventListener("play", res));
  function animate() {
    const { width, height } = videoTrack.getSettings();
    Object.assign(canvas, {
      width,
      height,
    });
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    // Recursively loop
    const frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const length = frame.data.length;
    let data = frame.data;
    for (let i = 0; i < length; i += 4) {
      let o = fn({
        red: data[i],
        g: data[i + 1],
        green: data[i + 1],
        blue: data[i + 2],
        alpha: data[i + 3],
      });
      data[i] = o.red;
      data[i + 1] = o.green;
      data[i + 2] = o.blue;
      data[i + 3] = o.alpha;
    }
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.putImageData(frame, 0, 0);
    if (running) {
      requestAnimationFrame(animate);
    }
  }
  requestAnimationFrame(animate);
  let track = get("video", canvas.captureStream(30));
  track.addEventListener("ended", () => {
    running = false;
  })
}
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter