summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/mediacapture-record/MediaRecorder-canvas-media-source.https.html
blob: 187015f42e64b911ac32145324a48f8b9d6763e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!doctype html>
<html>
<meta name="timeout" content="long">

<head>
  <title>MediaRecorder canvas media source</title>
  <link rel="help"
        href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimeType">
  <script src="/resources/testharness.js"></script>
  <script src="/resources/testharnessreport.js"></script>
  <script src="/resources/testdriver.js"></script>
  <script src="/resources/testdriver-vendor.js"></script>
  <script src="../mediacapture-streams/permission-helper.js"></script>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script>

async_test(test => {
  const CANVAS_WIDTH = 256;
  const CANVAS_HEIGHT = 144;

  // Empty video frames from this resolution consistently have ~750 bytes in my
  // tests, while valid video frames usually contain 7-8KB. A threshold of
  // 1.5KB consistently fails when video frames are empty but passes when video
  // frames are non-empty.
  const THRESHOLD_FOR_EMPTY_FRAMES = 1500;

  const CAMERA_CONSTRAINTS = {
    video: {
      width: { ideal: CANVAS_WIDTH },
      height: { ideal: CANVAS_HEIGHT }
    }
  };

  function useUserMedia(constraints) {
    let activeStream = null;

    function startCamera() {
      return navigator.mediaDevices.getUserMedia(constraints).then(
        (stream) => {
          activeStream = stream;
          return stream;
        }
      );
    }

    function stopCamera() {
      activeStream?.getTracks().forEach((track) => track.stop());
    }

    return { startCamera, stopCamera };
  }

  function useMediaRecorder(stream, frameSizeCallback) {
    const mediaRecorder = new MediaRecorder(
      stream,
      {}
    );

    mediaRecorder.ondataavailable = event => {
      const {size} = event.data;
      frameSizeCallback(size);

      if (mediaRecorder.state !== "inactive") {
        mediaRecorder.stop();
      }
    };

    mediaRecorder.start(1000);
  }

  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d", {
    alpha: false,
  });

  canvas.width = CANVAS_WIDTH;
  canvas.height = CANVAS_HEIGHT;

  const {startCamera, stopCamera} = useUserMedia(CAMERA_CONSTRAINTS);
  startCamera().then(async stream => {
    const videoTrack = stream.getVideoTracks()[0];
    const { readable: readableStream } = new MediaStreamTrackProcessor({
      track: videoTrack
    });

    const composedTrackGenerator = new MediaStreamTrackGenerator({
      kind: "video"
    });
    const sink = composedTrackGenerator.writable;

    ctx.fillStyle = "#333";
    ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

    const transformer = new TransformStream({
      async transform(cameraFrame, controller) {
        if (cameraFrame && cameraFrame?.codedWidth > 0) {
          const leftPos = (CANVAS_WIDTH - cameraFrame.displayWidth) / 2;
          const topPos = (CANVAS_HEIGHT - cameraFrame.displayHeight) / 2;

          ctx.drawImage(cameraFrame, leftPos, topPos);

          const newFrame = new VideoFrame(canvas, {
            timestamp: cameraFrame.timestamp
          });
          cameraFrame.close();
          controller.enqueue(newFrame);
        }
      }
    });

    readableStream.pipeThrough(transformer).pipeTo(sink);

    const compositedMediaStream = new MediaStream([composedTrackGenerator]);

    useMediaRecorder(compositedMediaStream, test.step_func_done(size => {
      assert_greater_than(size, THRESHOLD_FOR_EMPTY_FRAMES);
      stopCamera();
    }));
  });
}, "MediaRecorder returns frames containing video content");

  </script>
</body>

</html>