<!DOCTYPE html>
<html>
  <title>Test MediaSource addSourceBuffer overloads for WebCodecs Audio/VideoDecoderConfigs</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>

setup(() => {
  assert_implements(
      SourceBuffer.prototype.hasOwnProperty('appendEncodedChunks'),
      'SourceBuffer prototype hasOwnProperty "appendEncodedChunks", used ' +
          'here to feature detect MSE-for-WebCodecs implementation.');
});

testInvalidArguments();
testValidArguments();

function getValidAudioConfig() {
  // TODO(crbug.com/1144908): Consider confirming with WebCodecs'
  // isConfigSupported() once that API is available.
  return {
    codec: 'opus',
    sampleRate: 48000,
    numberOfChannels: 2
  };
}

function getValidVideoConfig() {
  // TODO(crbug.com/1144908): Consider confirming with WebCodecs'
  // isConfigSupported() once that API is available.
  return { codec: 'vp09.00.10.08' };
}

function testInvalidArguments() {
  const INVALID_CASES = [
    { arg: null,
      name: 'null' },
    { arg: undefined,
      name: 'undefined' },
    { arg: { },
      name: '{ empty dictionary }' },
    {
      arg: {
        audioConfig: getValidAudioConfig(),
        videoConfig: getValidVideoConfig()
      },
      name: '{ valid audioConfig and videoConfig }',
    },
    {
      arg: {
        audioConfig: {
          codec: 'bogus',
          sampleRate: 48000,
          numberOfChannels: 2
        }
      },
      name: 'bad audio config codec',
    },
    { arg: { videoConfig: { codec: 'bogus' } },
      name: 'bad video config codec' },
    { arg: { audioConfig: { sampleRate: 48000, numberOfChannels: 2 } },
      name: 'audio config missing required member "codec"' },
    { arg: { videoConfig: { } },
      name: 'video config missing required member "codec"' },
  ];

  [ 'closed', 'open', 'ended' ].forEach(readyStateScenario => {
    INVALID_CASES.forEach(invalidCase => {
      runAddSourceBufferTest(invalidCase['arg'] /* argument */,
          false /* isValidArgument */,
          invalidCase['name'] /* argumentDescription */,
          readyStateScenario);
    });
  });
}

function testValidArguments() {
  const VALID_CASES = [
    {
      arg: {
        audioConfig: getValidAudioConfig()
      },
      name: 'valid audioConfig'
    },
    {
      arg: {
        videoConfig: getValidVideoConfig()
      },
      name: 'valid videoConfig'
    },
  ];

  [ 'closed', 'open', 'ended' ].forEach(readyStateScenario => {
    VALID_CASES.forEach(validCase => {
      runAddSourceBufferTest(
          validCase['arg'] /* argument */,
          true /* isValidArgument */,
          validCase['name'] /* argumentDescription */,
          readyStateScenario);
    });
  });
}

async function getClosedMediaSource(test) {
  let mediaSource = new MediaSource();
  assert_equals(mediaSource.readyState, 'closed');
  return mediaSource;
}

async function getOpenMediaSource(test) {
  return new Promise(async resolve => {
    const v = document.createElement('video');
    const mediaSource = new MediaSource();
    const url = URL.createObjectURL(mediaSource);
    mediaSource.addEventListener('sourceopen', test.step_func(() => {
      URL.revokeObjectURL(url);
      assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
      resolve(mediaSource);
    }), { once: true });
    v.src = url;
  });
}

async function getEndedMediaSource(test) {
  let mediaSource = await getOpenMediaSource(test);
  assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
  mediaSource.endOfStream();
  assert_equals(mediaSource.readyState, 'ended', 'MediaSource is ended');
  return mediaSource;
}

function runAddSourceBufferTest(argument, isValidArgument, argumentDescription, readyStateScenario) {
  const testDescription = 'addSourceBuffer call with ' +
      (isValidArgument ? 'valid' : 'invalid') +
      ' argument ' + argumentDescription + ' while MediaSource readyState is ' +
      readyStateScenario;

  switch(readyStateScenario) {
    case 'closed':
      promise_test(async t => {
        let mediaSource = await getClosedMediaSource(t);
        assert_equals(mediaSource.readyState, 'closed');
        let sourceBuffer;
        if (isValidArgument) {
          assert_throws_dom('InvalidStateError',
              () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
              'addSourceBuffer(valid config) throws InvalidStateError if MediaSource is "closed"');
          assert_equals(sourceBuffer, undefined,
              'addSourceBuffer result for valid config while "closed" should be exception');
        } else {
          assert_throws_js(TypeError,
              () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
              'addSourceBuffer(invalid config) throws TypeError if MediaSource is "closed"');
          assert_equals(sourceBuffer, undefined,
              'addSourceBuffer result for invalid config while "closed" should be exception');
        }
      }, testDescription);
      break;
    case 'open':
      promise_test(async t => {
        let mediaSource = await getOpenMediaSource(t);
        assert_equals(mediaSource.readyState, 'open', 'MediaSource is open');
        let sourceBuffer;
        if (isValidArgument) {
          sourceBuffer = mediaSource.addSourceBuffer(argument);
          assert_true(sourceBuffer instanceof SourceBuffer,
              'addSourceBuffer result for valid config while "open" should be a SourceBuffer instance');
        } else {
          assert_throws_js(TypeError,
              () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
              'addSourceBuffer(invalid config) throws TypeError if MediaSource is "open"');
          assert_equals(sourceBuffer, undefined,
              'addSourceBufferResult for invalid config while "open" should be exception');
        }
      }, testDescription);
      break;
    case 'ended':
      promise_test(async t => {
        let mediaSource = await getEndedMediaSource(t);
        let sourceBuffer;
        if (isValidArgument) {
          assert_throws_dom('InvalidStateError',
              () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
              'addSourceBuffer(valid config) throws InvalidStateError if MediaSource is "ended"');
          assert_equals(sourceBuffer, undefined,
              'addSourceBuffer result for valid config while "ended" should be exception');
        } else {
          assert_throws_js(TypeError,
              () => { sourceBuffer = mediaSource.addSourceBuffer(argument); },
              'addSourceBuffer(invalid config) throws TypeError if MediaSource is "ended"');
          assert_equals(sourceBuffer, undefined,
              'addSourceBuffer result for invalid config while "ended" should be exception');
        }
      }, testDescription);
      break;
    default:
      assert_unreached('Invalid readyStateScenario ' + readyStateScenario);
      break;
  }
}

</script>
</html>
