Article MSTP + VTG spec

by jib1

HTML

<button id="start">Start</button> works in Safari 18 Tech Preview<br><br>
<video id="video1" width="320" height="240" autoplay muted></video>
<video id="video2" width="320" height="240" autoplay muted></video><br>

JavaScript

start.onclick = async () => {
  video1.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
  const track = video1.srcObject.getVideoTracks()[0].clone();
  const worker = new Worker(`data:text/javascript,(${work.toString()})()`);
  worker.postMessage({track}, [track]);
  const {data} = await new Promise(r => worker.onmessage = r);
  video2.srcObject = new MediaStream([data.track]);
};

function work() {
  onmessage = async ({data: {track}}) => {
    const vtg = new VideoTrackGenerator();
    self.postMessage({track: vtg.track}, [vtg.track]);
    const mstp = new MediaStreamTrackProcessor({track});
    await mstp.readable.pipeThrough(new TransformStream({transform})).pipeTo(vtg.writable);
  };

  const canvas = new OffscreenCanvas(640, 480);
  const ctx = canvas.getContext('2d', {desynchronized: true});
  ctx.translate(canvas.width, 0);
  ctx.scale(-1, 1);

  function transform(frame, controller) {
    ctx.drawImage(frame, 0, 0);
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const {data} = imageData;
    for (let i = 0; i < data.length; i += 4) {
      const r = data[i], g = data[i + 1], b = data[i + 2];
      data[i]     = 0.393 * r + 0.769 * g + 0.189 * b;
      data[i + 1] = 0.349 * r + 0.686 * g + 0.168 * b;
      data[i + 2] = 0.272 * r + 0.534 * g + 0.131 * b;
    }
    ctx.putImageData(imageData, 0, 0);
    controller.enqueue(new VideoFrame(canvas, {timestamp: frame.timestamp}));
    frame.close();
  }
}