Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!doctype html>
<html>
<meta name="timeout" content="long">
<head>
<title>MediaRecorder canvas media source</title>
<meta name=variant content="?mimeType=''">
<meta name=variant content="?mimeType=video/webm;codecs=vp8,opus">
<meta name=variant content="?mimeType=video/webm;codecs=vp9,opus">
<meta name=variant content="?mimeType=video/webm;codecs=av1,opus">
<meta name=variant content="?mimeType=video/mp4;codecs=avc1,mp4a.40.2">
<link rel="help"
<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;
let large_size_data_available = false;
// 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, mimeType, frameSizeCallback) {
const mediaRecorder = new MediaRecorder(
stream, { mimeType }
);
mediaRecorder.ondataavailable = event => {
const {size} = event.data;
frameSizeCallback(size);
if (mediaRecorder.state !== "inactive") {
mediaRecorder.stop();
}
};
mediaRecorder.onstop = event => {
assert_equals(large_size_data_available, true),
"onstop is called after valid data is available";
};
mediaRecorder.start(1000);
}
const params = new URLSearchParams(window.location.search);
const mimeType = params.get('mimeType');
if (mimeType) {
assert_implements_optional(MediaRecorder.isTypeSupported(mimeType),
`"${mimeType}" for MediaRecorder is not supported`);
}
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, mimeType, (size => {
if (size > THRESHOLD_FOR_EMPTY_FRAMES) {
large_size_data_available = true;
stopCamera();
test.done();
}
}));
});
}, "MediaRecorder returns frames containing video content");
</script>
</body>
</html>