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>
|