blob: 368789b9ad08fb74b364b6506818d0b37f789938 [file] [edit]
<!DOCTYPE html>
<html>
<title>Test the VideoTrackReader API.</title>
<body>
<img id='frame_image' src="pattern.png">
</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/media.js"></script>
<script>
const defaultConfig = {
codec: 'vp8',
framerate: 25,
width: 640,
height: 480
};
async function generateBitmap(width, height) {
return createImageBitmap(document.getElementById('frame_image'),
{ resizeWidth: width,
resizeHeight: height });
}
async function createVideoFrame(width, height, timestamp) {
let bitmap = await generateBitmap(width, height);
return new VideoFrame(bitmap, { timestamp: timestamp });
}
// Calls done after giving async output/error callbacks a final chance to run.
async function asyncDone(test) {
test.step_timeout(test.done.bind(test), 0);
}
async_test(async (t) => {
// VideoEncoderInit lacks required fields.
assert_throws_js(TypeError, () => { new VideoEncoder({}); });
// VideoEncoderInit has required fields.
let encoder = new VideoEncoder({
output(chunk) { t.unreached_func("Unexpected output").call(); },
error(error) { t.unreached_func("Unexpected error:" + error).call(); },
});
assert_equals(encoder.state, "unconfigured");
encoder.close();
asyncDone(t);
}, 'Test VideoEncoder construction');
async_test(async (t) => {
let encoder = new VideoEncoder({
output(chunk) { t.unreached_func("Unexpected output").call(); },
error(error) { t.unreached_func("Unexpected error:" + error).call(); },
});
const requiredConfigPairs = defaultConfig;
let incrementalConfig = {};
for (let key in requiredConfigPairs) {
// Configure should fail while required keys are missing.
assert_throws_js(TypeError, () => { encoder.configure(incrementalConfig); });
incrementalConfig[key] = requiredConfigPairs[key];
assert_equals(encoder.state, "unconfigured");
}
// Configure should pass once incrementalConfig meets all requirements.
encoder.configure(incrementalConfig);
assert_equals(encoder.state, "configured");
// We should be able to reconfigure the encoder.
encoder.configure(incrementalConfig);
assert_equals(encoder.state, "configured");
let config = incrementalConfig;
// Bogus codec rejected.
config.codec = 'bogus';
assert_throws_js(TypeError, () => { encoder.configure(config); });
// Audio codec rejected.
config.codec = 'vorbis';
assert_throws_js(TypeError, () => { encoder.configure(config); });
// Ambiguous codec rejected.
config.codec = 'vp9';
assert_throws_js(TypeError, () => { encoder.configure(config); });
// Codec with mime type rejected.
config.codec = 'video/webm; codecs="vp9"';
assert_throws_js(TypeError, () => { encoder.configure(config); });
// The failed configures should not affect the current config.
assert_equals(encoder.state, "configured");
// Test we can configure after a reset.
encoder.reset()
assert_equals(encoder.state, "unconfigured");
encoder.configure(defaultConfig);
assert_equals(encoder.state, "configured");
encoder.close();
asyncDone(t);
}, 'Test VideoEncoder.configure()');
async_test(async (t) => {
let encoder = new VideoEncoder({
output(chunk) { t.unreached_func("Unexpected output").call(); },
error(error) { t.unreached_func("Unexpected error:" + error).call(); },
});
let videoFrame = await createVideoFrame(640, 480, 0);
assert_throws_dom('InvalidStateError',
() => { encoder.encode(videoFrame); },
'first encode');
// Once more for good measure.
assert_throws_dom('InvalidStateError',
() => { encoder.encode(videoFrame); },
'second encode');
encoder.close();
asyncDone(t);
}, 'Test encode() before configure() throws InvalidStateError.');
async_test(async (t) => {
let output_chunks = [];
let encoder = new VideoEncoder({
output(chunk) { output_chunks.push(chunk); },
error(error) { t.unreached_func("Unexpected error:" + error).call(); },
});
// No encodes yet.
assert_equals(encoder.encodeQueueSize, 0);
encoder.configure(defaultConfig);
// Still no encodes.
assert_equals(encoder.encodeQueueSize, 0);
let frame1 = await createVideoFrame(640, 480, 0);
let frame2 = await createVideoFrame(640, 480, 33333);
encoder.encode(frame1.clone());
encoder.encode(frame2.clone());
// Could be 0, 1, or 2. We can't guarantee this check runs before the UA has
// processed the encodes.
assert_true(encoder.encodeQueueSize >= 0 && encoder.encodeQueueSize <= 2)
await encoder.flush();
// We can guarantee that all encodes are processed after a flush.
assert_equals(encoder.encodeQueueSize, 0);
assert_equals(output_chunks.length, 2);
assert_equals(output_chunks[0].timestamp, frame1.timestamp);
assert_equals(output_chunks[1].timestamp, frame2.timestamp);
encoder.close();
asyncDone(t);
}, 'Test successful configure(), encode(), and flush()');
async_test(async (t) => {
let output_chunks = [];
let encoder = new VideoEncoder({
output(chunk) { output_chunks.push(chunk); },
error(error) { t.unreached_func("Unexpected error:" + error).call(); },
});
// No encodes yet.
assert_equals(encoder.encodeQueueSize, 0);
let config = defaultConfig;
encoder.configure(config);
let frame1 = await createVideoFrame(640, 480, 0);
let frame2 = await createVideoFrame(640, 480, 33333);
encoder.encode(frame1.clone());
encoder.configure(config);
encoder.encode(frame2.clone());
await encoder.flush();
// We can guarantee that all encodes are processed after a flush.
assert_equals(encoder.encodeQueueSize, 0);
// The first frame may have been dropped when reconfiguring.
// This shouldn't happen, and should be fixed/called out in the spec, but
// this is preptively added to prevent flakiness.
// TODO: Remove these checks when implementations handle this correctly.
assert_true(output_chunks.length == 1 || output_chunks.length == 2);
if (output_chunks.length == 1) {
// If we only have one chunk frame, make sure we droped the frame that was
// in flight when we reconfigured.
assert_equals(output_chunks[0].timestamp, frame2.timestamp);
} else {
assert_equals(output_chunks[0].timestamp, frame1.timestamp);
assert_equals(output_chunks[1].timestamp, frame2.timestamp);
}
output_chunks = [];
let frame3 = await createVideoFrame(640, 480, 66666);
let frame4 = await createVideoFrame(640, 480, 100000);
encoder.encode(frame3.clone());
// Verify that a failed call to configure does not change the encoder's state.
config.codec = 'bogus';
assert_throws_js(TypeError, () => encoder.configure(config));
encoder.encode(frame4.clone());
await encoder.flush();
assert_equals(output_chunks[0].timestamp, frame3.timestamp);
assert_equals(output_chunks[1].timestamp, frame4.timestamp);
encoder.close();
asyncDone(t);
}, 'Test successful encode() after re-configure().');
async_test(async (t) => {
let output_chunks = [];
let encoder = new VideoEncoder({
output(chunk) { output_chunks.push(chunk); },
error(error) { t.unreached_func("Unexpected error:" + error).call(); },
});
let timestamp = 33333;
let frame = await createVideoFrame(640, 480, timestamp);
t.step(() => {
// No encodes yet.
assert_equals(encoder.encodeQueueSize, 0);
encoder.configure(defaultConfig);
encoder.encode(frame);
assert_not_equals(frame.timestamp, timestamp);
assert_throws_dom("InvalidStateError", () => frame.clone());
encoder.close();
});
asyncDone(t);
}, 'Test encoder consumes (destroys) frames.');
promise_test(async t => {
let encoder = new VideoEncoder({
output(frame) {
t.step(() => {
throw "unexpected output";
});
},
error(e) {
t.step(() => {
throw "unexpected error";
});
}
});
encoder.close();
assert_equals(encoder.state, "closed")
let frame = await createVideoFrame(640, 480, 0);
assert_throws_dom("InvalidStateError",
() => encoder.configure(defaultConfig),
"configure");
assert_throws_dom("InvalidStateError",
() => encoder.encode(frame),
"encode");
assert_throws_dom("InvalidStateError",
() => encoder.reset(),
"reset");
assert_throws_dom("InvalidStateError",
() => encoder.close(),
"close");
return promise_rejects_dom(t, 'InvalidStateError', encoder.flush(), 'flush');
}, 'Closed encoder');
promise_test(async t => {
let encoder = new VideoEncoder({
output(frame) {
t.step(() => {
throw "unexpected output";
});
},
error(e) {
t.step(() => {
throw "unexpected error";
});
}
});
assert_equals(encoder.state, "unconfigured");
let frame = await createVideoFrame(640, 480, 0);
// Resetting an unconfigured encoder is a no-op.
encoder.reset();
assert_equals(encoder.state, "unconfigured");
assert_throws_dom("InvalidStateError",
() => encoder.encode(frame),
"encode");
return promise_rejects_dom(t, 'InvalidStateError', encoder.flush(), 'flush');
}, 'Unconfigured encoder');
</script>
</html>