233 lines
7.2 KiB
JavaScript
233 lines
7.2 KiB
JavaScript
// 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();
|