<!DOCTYPE html>
<meta charset="utf-8">
<title>RTCRtpEncodingParameters codec property</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../webrtc/RTCPeerConnection-helper.js"></script>
<script src="../webrtc/third_party/sdp/sdp.js"></script>
<script src="../webrtc/simulcast/simulcast.js"></script>
<script>
  'use strict';

  function findFirstCodec(name) {
    return RTCRtpSender.getCapabilities(name.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(name, undefined, { sensitivity: 'base' }) === 0)[0];
  }

  function codecsNotMatching(mimeType) {
    return RTCRtpSender.getCapabilities(mimeType.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(mimeType, undefined, {sensitivity: 'base'}) !== 0);
  }

  function assertCodecEquals(a, b) {
    assert_equals(a.mimeType, b.mimeType);
    assert_equals(a.clockRate, b.clockRate);
    assert_equals(a.channels, b.channels);
    assert_equals(a.sdpFmtpLine, b.sdpFmtpLine);
  }

  async function codecsForSender(sender) {
    const rids = sender.getParameters().encodings.map(e => e.rid);
    const stats = await sender.getStats();
    const codecs = [...stats]
      .filter(([k, v]) => v.type === 'outbound-rtp')
      .sort(([k, v], [k2, v2]) => rids.indexOf(v.rid) - rids.indexOf(v2.rid))
      .map(([k, v]) => stats.get(v.codecId).mimeType);
    return codecs;
  }

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const { sender } = pc.addTransceiver('audio');

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    assert_equals(encoding.codec, undefined);
  }, `Codec should be undefined by default on audio encodings`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const { sender } = pc.addTransceiver('video');

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    assert_equals(encoding.codec, undefined);
  }, `Codec should be undefined by default on video encodings`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const opus = findFirstCodec('audio/opus');

    const { sender } = pc.addTransceiver('audio', {
      sendEncodings: [{codec: opus}],
    });

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    assertCodecEquals(opus, encoding.codec);
  }, `Creating an audio sender with addTransceiver and codec should work`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const vp8 = findFirstCodec('video/VP8');

    const { sender } = pc.addTransceiver('video', {
      sendEncodings: [{codec: vp8}],
    });

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    assertCodecEquals(vp8, encoding.codec);
  }, `Creating a video sender with addTransceiver and codec should work`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const opus = findFirstCodec('audio/opus');

    const { sender } = pc.addTransceiver('audio');

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = opus;
    await sender.setParameters(param);
    param = sender.getParameters();
    encoding = param.encodings[0];

    assertCodecEquals(opus, encoding.codec);

    delete encoding.codec;
    await sender.setParameters(param);
    param = sender.getParameters();
    encoding = param.encodings[0];

    assert_equals(encoding.codec, undefined);
  }, `Setting codec on an audio sender with setParameters should work`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const vp8 = findFirstCodec('video/VP8');

    const { sender } = pc.addTransceiver('video');

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = vp8;
    await sender.setParameters(param);
    param = sender.getParameters();
    encoding = param.encodings[0];

    assertCodecEquals(vp8, encoding.codec);

    delete encoding.codec;
    await sender.setParameters(param);
    param = sender.getParameters();
    encoding = param.encodings[0];

    assert_equals(encoding.codec, undefined);
  }, `Setting codec on a video sender with setParameters should work`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const newCodec = {
      mimeType: "audio/newCodec",
      clockRate: 90000,
      channel: 2,
    };

    assert_throws_dom('OperationError', () => pc.addTransceiver('video', {
      sendEncodings: [{codec: newCodec}],
    }));
  }, `Creating an audio sender with addTransceiver and non-existing codec should throw OperationError`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const newCodec = {
      mimeType: "video/newCodec",
      clockRate: 90000,
    };

    assert_throws_dom('OperationError', () => pc.addTransceiver('video', {
      sendEncodings: [{codec: newCodec}],
    }));
  }, `Creating a video sender with addTransceiver and non-existing codec should throw OperationError`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const newCodec = {
      mimeType: "audio/newCodec",
      clockRate: 90000,
      channel: 2,
    };

    const { sender } = pc.addTransceiver('audio');

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = newCodec;
    await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
  }, `Setting a non-existing codec on an audio sender with setParameters should throw InvalidModificationError`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const newCodec = {
      mimeType: "video/newCodec",
      clockRate: 90000,
    };

    const { sender } = pc.addTransceiver('video');

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = newCodec;
    await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
  }, `Setting a non-existing codec on a video sender with setParameters should throw InvalidModificationError`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const opus = findFirstCodec('audio/opus');
    const nonOpus = codecsNotMatching(opus.mimeType);

    const transceiver = pc.addTransceiver('audio');
    const sender = transceiver.sender;

    transceiver.setCodecPreferences(nonOpus);

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = opus;
    await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
  }, `Setting a non-preferred codec on an audio sender with setParameters should throw InvalidModificationError`);

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    t.add_cleanup(() => pc.close());

    const vp8 = findFirstCodec('video/VP8');
    const nonVP8 = codecsNotMatching(vp8.mimeType);

    const transceiver = pc.addTransceiver('video');
    const sender = transceiver.sender;

    transceiver.setCodecPreferences(nonVP8);

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = vp8;
    await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
  }, `Setting a non-preferred codec on a video sender with setParameters should throw InvalidModificationError`);

  promise_test(async (t) => {
    const pc1 = new RTCPeerConnection();
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    t.add_cleanup(() => pc2.close());

    const opus = findFirstCodec('audio/opus');
    const nonOpus = codecsNotMatching(opus.mimeType);

    const transceiver = pc1.addTransceiver('audio');
    const sender = transceiver.sender;

    transceiver.setCodecPreferences(nonOpus);

    exchangeIceCandidates(pc1, pc2);
    await exchangeOfferAnswer(pc1, pc2);

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = opus;
    await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
  }, `Setting a non-negotiated codec on an audio sender with setParameters should throw InvalidModificationError`);

  promise_test(async (t) => {
    const pc1 = new RTCPeerConnection();
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    t.add_cleanup(() => pc2.close());

    const vp8 = findFirstCodec('video/VP8');
    const nonVP8 = codecsNotMatching(vp8.mimeType);

    const transceiver = pc1.addTransceiver('video');
    const sender = transceiver.sender;

    transceiver.setCodecPreferences(nonVP8);

    exchangeIceCandidates(pc1, pc2);
    await exchangeOfferAnswer(pc1, pc2);

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    encoding.codec = vp8;
    await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
  }, `Setting a non-negotiated codec on a video sender with setParameters should throw InvalidModificationError`);

  promise_test(async (t) => {
    const pc1 = new RTCPeerConnection();
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    t.add_cleanup(() => pc2.close());

    const opus = findFirstCodec('audio/opus');
    const nonOpus = codecsNotMatching(opus.mimeType);

    const transceiver = pc1.addTransceiver('audio', {
      sendEncodings: [{codec: opus}],
    });
    const sender = transceiver.sender;

    exchangeIceCandidates(pc1, pc2);
    await exchangeOfferAnswer(pc1, pc2);

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    assertCodecEquals(opus, encoding.codec);

    transceiver.setCodecPreferences(nonOpus);
    await exchangeOfferAnswer(pc1, pc2);

    param = sender.getParameters();
    encoding = param.encodings[0];

    assert_equals(encoding.codec, undefined);
  }, `Codec should be undefined after negotiating away the currently set codec on an audio sender`);

  promise_test(async (t) => {
    const pc1 = new RTCPeerConnection();
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    t.add_cleanup(() => pc2.close());

    const vp8 = findFirstCodec('video/VP8');
    const nonVP8 = codecsNotMatching(vp8.mimeType);

    const transceiver = pc1.addTransceiver('video', {
      sendEncodings: [{codec: vp8}],
    });
    const sender = transceiver.sender;

    exchangeIceCandidates(pc1, pc2);
    await exchangeOfferAnswer(pc1, pc2);

    let param = sender.getParameters();
    let encoding = param.encodings[0];

    assertCodecEquals(vp8, encoding.codec);

    transceiver.setCodecPreferences(nonVP8);
    await exchangeOfferAnswer(pc1, pc2);

    param = sender.getParameters();
    encoding = param.encodings[0];

    assert_equals(encoding.codec, undefined);
  }, `Codec should be undefined after negotiating away the currently set codec on a video sender`);

  promise_test(async (t) => {
    const pc1 = new RTCPeerConnection();
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    t.add_cleanup(() => pc2.close());
    const stream = await getNoiseStream({audio:true});
    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));

    const opus = findFirstCodec('audio/opus');
    const nonOpus = codecsNotMatching(opus.mimeType);

    const transceiver = pc1.addTransceiver(stream.getTracks()[0]);
    const sender = transceiver.sender;

    transceiver.setCodecPreferences(nonOpus.concat([opus]));

    exchangeIceCandidates(pc1, pc2);
    await exchangeOfferAnswer(pc1, pc2);

    let codecs = await codecsForSender(sender);
    assert_not_equals(codecs[0], opus.mimeType);

    let param = sender.getParameters();
    let encoding = param.encodings[0];
    encoding.codec = opus;

    await sender.setParameters(param);

    codecs = await codecsForSender(sender);
    assert_array_equals(codecs, [opus.mimeType]);
  }, `Stats output-rtp should match the selected codec in non-simulcast usecase on an audio sender`);

  promise_test(async (t) => {
    const pc1 = new RTCPeerConnection();
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    t.add_cleanup(() => pc2.close());
    const stream = await getNoiseStream({video:true});
    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));

    const vp8 = findFirstCodec('video/VP8');
    const nonVP8 = codecsNotMatching(vp8.mimeType);

    const transceiver = pc1.addTransceiver(stream.getTracks()[0]);
    const sender = transceiver.sender;

    transceiver.setCodecPreferences(nonVP8.concat([vp8]));

    exchangeIceCandidates(pc1, pc2);
    await exchangeOfferAnswer(pc1, pc2);

    let codecs = await codecsForSender(sender);
    assert_not_equals(codecs[0], vp8.mimeType);

    let param = sender.getParameters();
    let encoding = param.encodings[0];
    encoding.codec = vp8;

    await sender.setParameters(param);

    codecs = await codecsForSender(sender);
    assert_array_equals(codecs, [vp8.mimeType]);
  }, `Stats output-rtp should match the selected codec in non-simulcast usecase on a video sender`);
</script>
