Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 9 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /mediacapture-insertable-streams/VideoTrackGenerator.worker.html - WPT Dashboard Interop Dashboard
// META: title=VideoTrackGenerator tests.
importScripts("/resources/testharness.js");
function make_audio_data(timestamp, channels, sampleRate, frames) {
let data = new Float32Array(frames*channels);
// This generates samples in a planar format.
for (var channel = 0; channel < channels; channel++) {
let hz = 100 + channel * 50; // sound frequency
let base_index = channel * frames;
for (var i = 0; i < frames; i++) {
let t = (i / sampleRate) * hz * (Math.PI * 2);
data[base_index + i] = Math.sin(t);
}
}
return new AudioData({
timestamp: timestamp,
data: data,
numberOfChannels: channels,
numberOfFrames: frames,
sampleRate: sampleRate,
format: "f32-planar",
});
}
const pixelColour = [50, 100, 150, 255];
const height = 240;
const width = 320;
function makeVideoFrame(timestamp) {
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext('2d', {alpha: false});
ctx.fillStyle = `rgba(${pixelColour.join()})`;
ctx.fillRect(0, 0, width, height);
return new VideoFrame(canvas, {timestamp, alpha: 'discard'});
}
promise_test(async t => {
const videoFrame = makeVideoFrame(1);
const originalWidth = videoFrame.displayWidth;
const originalHeight = videoFrame.displayHeight;
const originalTimestamp = videoFrame.timestamp;
const generator = new VideoTrackGenerator();
t.add_cleanup(() => generator.track.stop());
// Use a MediaStreamTrackProcessor as a sink for |generator| to verify
// that |processor| actually forwards the frames written to its writable
// field.
const processor = new MediaStreamTrackProcessor(generator);
const reader = processor.readable.getReader();
const readerPromise = new Promise(async resolve => {
const result = await reader.read();
t.add_cleanup(() => result.value.close());
t.step_func(() => {
assert_equals(result.value.displayWidth, originalWidth);
assert_equals(result.value.displayHeight, originalHeight);
assert_equals(result.value.timestamp, originalTimestamp);
})();
resolve();
});
generator.writable.getWriter().write(videoFrame);
return readerPromise;
}, 'Tests that VideoTrackGenerator forwards frames to sink');
promise_test(async t => {
const generator = new VideoTrackGenerator();
t.add_cleanup(() => generator.track.stop());
const writer = generator.writable.getWriter();
const frame = makeVideoFrame(1);
await writer.write(frame);
assert_equals(generator.track.kind, "video");
assert_equals(generator.track.readyState, "live");
}, "Tests that creating a VideoTrackGenerator works as expected");
promise_test(async t => {
const generator = new VideoTrackGenerator();
t.add_cleanup(() => generator.track.stop());
const writer = generator.writable.getWriter();
const frame = makeVideoFrame(1);
await writer.write(frame);
assert_throws_dom("InvalidStateError", () => frame.clone(), "VideoFrame wasn't destroyed on write.");
}, "Tests that VideoFrames are destroyed on write");
promise_test(async t => {
const generator = new VideoTrackGenerator();
t.add_cleanup(() => generator.track.stop());
const writer = generator.writable.getWriter();
if (!self.AudioData)
return;
const defaultInit = {
timestamp: 1234,
channels: 2,
sampleRate: 8000,
frames: 100,
};
const audioData = make_audio_data(defaultInit.timestamp, defaultInit.channels, defaultInit.sampleRate,
defaultInit.frames);
await promise_rejects_js(t, TypeError, writer.write("test"));
}, "Generator writer rejects on mismatched media input");
promise_test(async t => {
const generator = new VideoTrackGenerator();
t.add_cleanup(() => generator.track.stop());
const writer = generator.writable.getWriter();
await promise_rejects_js(t, TypeError, writer.write("potatoe"));
}, "Generator writer rejects on non media input");
promise_test(async t => {
const generator = new VideoTrackGenerator();
const writer = generator.writable.getWriter();
const frame1 = makeVideoFrame(1);
t.add_cleanup(() => frame1.close());
await writer.write(frame1);
assert_equals(frame1.codedWidth, 0);
generator.track.stop();
await writer.closed;
const frame2 = makeVideoFrame(1);
t.add_cleanup(() => frame2.close());
await promise_rejects_js(t, TypeError, writer.write(frame2));
assert_equals(frame2.codedWidth, 320);
}, "A writer rejects when generator's track is stopped");
promise_test(async t => {
const generator = new VideoTrackGenerator();
generator.muted = true;
const writer = generator.writable.getWriter();
const frame1 = makeVideoFrame(1);
t.add_cleanup(() => frame1.close());
await writer.write(frame1);
assert_equals(frame1.codedWidth, 0);
generator.track.stop();
await writer.closed;
const frame2 = makeVideoFrame(1);
t.add_cleanup(() => frame2.close());
await promise_rejects_js(t, TypeError, writer.write(frame2));
assert_equals(frame2.codedWidth, 320);
}, "A muted writer rejects when generator's track is stopped");
promise_test(async t => {
const generator = new VideoTrackGenerator();
const writer = generator.writable.getWriter();
const frame1 = makeVideoFrame(1);
t.add_cleanup(() => frame1.close());
await writer.write(frame1);
assert_equals(frame1.codedWidth, 0);
const clonedTrack = generator.track.clone();
generator.track.stop();
await new Promise(resolve => t.step_timeout(resolve, 100));
const frame2 = makeVideoFrame(1);
t.add_cleanup(() => frame2.close());
await writer.write(frame2);
assert_equals(frame2.codedWidth, 0);
clonedTrack.stop();
await writer.closed;
const frame3 = makeVideoFrame(1);
t.add_cleanup(() => frame3.close());
await promise_rejects_js(t, TypeError, writer.write(frame3));
assert_equals(frame3.codedWidth, 320);
}, "A writer rejects when generator's track and clones are stopped");
promise_test(async t => {
const generator = new VideoTrackGenerator();
t.add_cleanup(() => generator.track.stop());
// Use a MediaStreamTrackProcessor as a sink for |generator| to verify
// that |processor| actually forwards the frames written to its writable
// field.
const processor = new MediaStreamTrackProcessor(generator);
const reader = processor.readable.getReader();
const videoFrame = makeVideoFrame(1);
const writer = generator.writable.getWriter();
const videoFrame1 = makeVideoFrame(1);
writer.write(videoFrame1);
const result1 = await reader.read();
t.add_cleanup(() => result1.value.close());
assert_equals(result1.value.timestamp, 1);
generator.muted = true;
// This frame is expected to be discarded.
const videoFrame2 = makeVideoFrame(2);
writer.write(videoFrame2);
generator.muted = false;
const videoFrame3 = makeVideoFrame(3);
writer.write(videoFrame3);
const result3 = await reader.read();
t.add_cleanup(() => result3.value.close());
assert_equals(result3.value.timestamp, 3);
// Set up a read ahead of time, then mute, enqueue and unmute.
const promise5 = reader.read();
generator.muted = true;
writer.write(makeVideoFrame(4)); // Expected to be discarded.
generator.muted = false;
writer.write(makeVideoFrame(5));
const result5 = await promise5;
t.add_cleanup(() => result5.value.close());
assert_equals(result5.value.timestamp, 5);
}, 'Tests that VideoTrackGenerator forwards frames only when unmuted');
done();