Reland "[webcodecs] Implement Audio/VideoDecoder error callback."

This reverts commit 51b274e89cbb45a7a1ce9b4f1ae78ed81639c7ba.

Bug: 1045247
Change-Id: I4f22c1e340d6c2224eaccb29e41b2605efe0d47a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368493
Commit-Queue: Dan Sanders <sandersd@chromium.org>
Commit-Queue: Chrome Cunningham <chcunningham@chromium.org>
Auto-Submit: Dan Sanders <sandersd@chromium.org>
Reviewed-by: Chrome Cunningham <chcunningham@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800406}
diff --git a/webcodecs/h264.mp4 b/webcodecs/h264.mp4
new file mode 100644
index 0000000..e21fcba
--- /dev/null
+++ b/webcodecs/h264.mp4
Binary files differ
diff --git a/webcodecs/video-decoder.html b/webcodecs/video-decoder.html
index 6c64d92..c70366d 100644
--- a/webcodecs/video-decoder.html
+++ b/webcodecs/video-decoder.html
@@ -4,6 +4,46 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
+'use strict';
+
+// TODO(sandersd): Move metadata into a helper library.
+// TODO(sandersd): Add H.264 idecode test once there is an API to query for
+// supported codecs.
+let h264 = {
+  buffer: fetch('h264.mp4').then(r => r.arrayBuffer()),
+  codec: "avc1.64000c",
+  description: {offset: 7229, size: 46},
+  frames: [{offset: 48, size: 4007},
+           {offset: 4055, size: 926},
+           {offset: 4981, size: 241},
+           {offset: 5222, size: 97},
+           {offset: 5319, size: 98},
+           {offset: 5417, size: 624},
+           {offset: 6041, size: 185},
+           {offset: 6226, size: 94},
+           {offset: 6320, size: 109},
+           {offset: 6429, size: 281}]
+};
+
+let vp9 = {
+  buffer: fetch('vp9.mp4').then(r => r.arrayBuffer()),
+  // TODO(sandersd): Verify that the file is actually level 1.
+  codec: "vp09.00.10.08",
+  frames: [{offset: 44, size: 3315},
+           {offset: 3359, size: 203},
+           {offset: 3562, size: 245},
+           {offset: 3807, size: 172},
+           {offset: 3979, size: 312},
+           {offset: 4291, size: 170},
+           {offset: 4461, size: 195},
+           {offset: 4656, size: 181},
+           {offset: 4837, size: 356},
+           {offset: 5193, size: 159}]
+};
+
+function view(buffer, {offset, size}) {
+  return new Uint8Array(buffer, offset, size);
+}
 
 // Calls done after giving async output/error callbacks a final chance to run.
 async function asyncDone(test) {
@@ -54,5 +94,133 @@
 
   asyncDone(t);
 }, 'Test VideoDecoder.configure() codec validity');
+
+promise_test(t => vp9.buffer.then(buffer => {
+  let numOutputs = 0;
+  let decoder = new VideoDecoder({
+    output(frame) {
+      t.step(() => {
+        assert_equals(++numOutputs, 1, "outputs");
+        assert_equals(frame.cropWidth, 320, "cropWidth");
+        assert_equals(frame.cropHeight, 240, "cropHeight");
+        assert_equals(frame.timestamp, 0, "timestamp");
+        frame.destroy();
+      });
+    },
+    error(e) {
+      t.step(() => {
+        // TODO(sandersd): Change to 'throw e' once e is defined.
+        throw "decode error";
+      });
+    }
+  });
+
+  decoder.configure({codec: vp9.codec});
+
+  decoder.decode(new EncodedVideoChunk('key', 0, view(buffer, vp9.frames[0])));
+
+  return decoder.flush().then(() => {
+    assert_equals(numOutputs, 1, "outputs");
+  });
+}), 'Decode VP9');
+
+promise_test(t => {
+  let decoder = new VideoDecoder({
+    output(frame) {
+      t.step(() => {
+        throw "unexpected output";
+      });
+    },
+    error(e) {
+      t.step(() => {
+        throw "unexpected error";
+      });
+    }
+  });
+
+  decoder.close();
+
+  let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of(0));
+  assert_throws_dom("InvalidStateError",
+                    () => decoder.configure({codec: vp9.codec}),
+                    "configure");
+  assert_throws_dom("InvalidStateError",
+                    () => decoder.decode(fakeChunk),
+                    "reset");
+  assert_throws_dom("InvalidStateError",
+                    () => decoder.reset(),
+                    "reset");
+  assert_throws_dom("InvalidStateError",
+                    () => decoder.close(),
+                    "close");
+  return promise_rejects_dom(t, 'InvalidStateError', decoder.flush(), 'flush');
+}, 'Closed decoder');
+
+promise_test(t => {
+  let numErrors = 0;
+  let decoder = new VideoDecoder({
+    output(frame) {
+      t.step(() => {
+        throw "unexpected output";
+      });
+    },
+    error(e) {
+      numErrors++;
+    }
+  });
+
+  let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of(0));
+  decoder.decode(fakeChunk);
+
+  return decoder.flush().then(
+      () => { throw "flush succeeded unexpectedly"; },
+      () => { assert_equals(numErrors, 1, "errors"); });
+}, 'Decode without configure');
+
+promise_test(t => {
+  let numErrors = 0;
+  let decoder = new VideoDecoder({
+    output(frame) {
+      t.step(() => {
+        throw "unexpected output";
+      });
+    },
+    error(e) {
+      numErrors++;
+    }
+  });
+
+  decoder.configure({codec: vp9.codec});
+
+  let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of(0));
+  decoder.decode(fakeChunk);
+
+  return decoder.flush().then(
+      () => { throw "flush succeeded unexpectedly"; },
+      () => { assert_equals(numErrors, 1, "errors"); });
+}, 'Decode corrupt VP9 frame');
+
+promise_test(t => {
+  let numErrors = 0;
+  let decoder = new VideoDecoder({
+    output(frame) {
+      t.step(() => {
+        throw "unexpected output";
+      });
+    },
+    error(e) {
+      numErrors++;
+    }
+  });
+
+  decoder.configure({codec: vp9.codec});
+
+  let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of());
+  decoder.decode(fakeChunk);
+
+  return decoder.flush().then(
+      () => { throw "flush succeeded unexpectedly"; },
+      () => { assert_equals(numErrors, 1, "errors"); });
+}, 'Decode empty VP9 frame');
 </script>
 </html>
diff --git a/webcodecs/vp9.mp4 b/webcodecs/vp9.mp4
new file mode 100644
index 0000000..7553e5c
--- /dev/null
+++ b/webcodecs/vp9.mp4
Binary files differ