<!doctype html>
<meta charset=utf-8>
<title>RTCRtpTransceiver.prototype.setCodecPreferences</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./third_party/sdp/sdp.js"></script>
<script>
'use strict';
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
transceiver.setCodecPreferences(capabilities.codecs);
}, `setCodecPreferences() on audio transceiver with codecs returned from RTCRtpReceiver.getCapabilities('audio') should succeed`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video');
const capabilities = RTCRtpReceiver.getCapabilities('video');
transceiver.setCodecPreferences(capabilities.codecs);
}, `setCodecPreferences() on video transceiver with codecs returned from RTCRtpReceiver.getCapabilities('video') should succeed`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
transceiver.setCodecPreferences([]);
}, `setCodecPreferences([]) should succeed`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
const { codecs } = capabilities;
if(codecs.length >= 2) {
const tmp = codecs[0];
codecs[0] = codecs[1];
codecs[1] = tmp;
}
transceiver.setCodecPreferences(codecs);
}, `setCodecPreferences() with reordered codecs should succeed`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video');
const capabilities = RTCRtpReceiver.getCapabilities('video');
const { codecs } = capabilities;
// This test verifies that the mandatory VP8 codec is present
// and can be preferred.
const codec = codecs.find(c => c.mimeType === 'video/VP8');
assert_true(!!codec, 'VP8 video codec was found');
transceiver.setCodecPreferences([codec]);
}, `setCodecPreferences() with only VP8 should succeed`);
test(() => {
const pc = new RTCPeerConnection();
const transceiver = pc.addTransceiver('video');
const capabilities = RTCRtpReceiver.getCapabilities('video');
const { codecs } = capabilities;
// This test verifies that the mandatory H264 codec is present
// and can be preferred.
const codec = codecs.find(c => c.mimeType === 'video/H264');
assert_true(!!codec, 'H264 video codec was found');
transceiver.setCodecPreferences([codec]);
}, `setCodecPreferences() with only H264 should succeed`);
async function getRTPMapLinesWithCodecAsFirst(firstCodec)
{
const codecs = RTCRtpReceiver.getCapabilities('video').codecs;
codecs.forEach((codec, idx) => {
if (codec.mimeType === firstCodec) {
codecs.splice(idx, 1);
codecs.unshift(codec);
}
});
const pc = new RTCPeerConnection();
const transceiver = pc.addTransceiver('video');
transceiver.setCodecPreferences(codecs);
const offer = await pc.createOffer();
return offer.sdp.split('\r\n').filter(line => line.startsWith('a=rtpmap:'));
}
promise_test(async () => {
const lines = await getRTPMapLinesWithCodecAsFirst('video/H264');
assert_greater_than(lines.length, 1);
assert_true(lines[0].indexOf('H264') !== -1, 'H264 should be the first codec');
}, `setCodecPreferences() should allow setting H264 as first codec`);
promise_test(async () => {
const lines = await getRTPMapLinesWithCodecAsFirst('video/VP8');
assert_greater_than(lines.length, 1);
assert_true(lines[0].indexOf('VP8') !== -1, 'VP8 should be the first codec');
}, `setCodecPreferences() should allow setting VP8 as first codec`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('video');
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(capabilities.codecs));
}, `setCodecPreferences() on audio transceiver with codecs returned from getCapabilities('video') should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const codecs = [{
mimeType: 'data',
clockRate: 2000,
channels: 2,
sdpFmtpLine: '0-15'
}];
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with user defined codec with invalid mimeType should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const codecs = [{
mimeType: 'audio/piepiper',
clockRate: 2000,
channels: 2,
sdpFmtpLine: '0-15'
}];
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with user defined codec should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
const codecs = [
...capabilities.codecs,
{
mimeType: 'audio/piepiper',
clockRate: 2000,
channels: 2,
sdpFmtpLine: '0-15'
}];
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with user defined codec together with codecs returned from getCapabilities() should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
const codecs = [capabilities.codecs[0]];
codecs[0].clockRate = codecs[0].clockRate / 2;
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with modified codec clock rate should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
const codecs = [capabilities.codecs[0]];
codecs[0].channels = codecs[0].channels + 11;
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with modified codec channel count should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
const codecs = [capabilities.codecs[0]];
codecs[0].sdpFmtpLine = "modifiedparameter=1";
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with modified codec parameters should throw InvalidModificationError`);
test((t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const capabilities = RTCRtpReceiver.getCapabilities('audio');
const { codecs } = capabilities;
assert_greater_than(codecs.length, 0,
'Expect at least one codec available');
const [ codec ] = codecs;
const { channels=2 } = codec;
codec.channels = channels+1;
assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs));
}, `setCodecPreferences() with modified codecs returned from getCapabilities() should throw InvalidModificationError`);
promise_test(async (t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
const {codecs} = RTCRtpReceiver.getCapabilities('audio');
// Reorder codecs, put PCMU/PCMA first.
let firstCodec;
let i;
for (i = 0; i < codecs.length; i++) {
const codec = codecs[i];
if (codec.mimeType === 'audio/PCMU' || codec.mimeType === 'audio/PCMA') {
codecs.splice(i, 1);
codecs.unshift(codec);
firstCodec = codec.mimeType.substr(6);
break;
}
}
assert_not_equals(firstCodec, undefined, 'PCMU or PCMA codec not found');
transceiver.setCodecPreferences(codecs);
const offer = await pc.createOffer();
const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0];
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
assert_equals(rtpParameters.codecs[0].name, firstCodec);
}, `setCodecPreferences() modifies the order of audio codecs in createOffer`);
promise_test(async (t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video');
const {codecs} = RTCRtpReceiver.getCapabilities('video');
// Reorder codecs, swap H264 and VP8.
let vp8 = -1;
let h264 = -1;
let firstCodec;
let i;
for (i = 0; i < codecs.length; i++) {
const codec = codecs[i];
if (codec.mimeType === 'video/VP8' && vp8 === -1) {
vp8 = i;
if (h264 !== -1) {
codecs[vp8] = codecs[h264];
codecs[h264] = codec;
firstCodec = 'VP8';
break;
}
}
if (codec.mimeType === 'video/H264' && h264 === -1) {
h264 = i;
if (vp8 !== -1) {
codecs[h264] = codecs[vp8];
codecs[vp8] = codec;
firstCodec = 'H264';
break;
}
}
}
assert_not_equals(firstCodec, undefined, 'VP8 and H264 codecs not found');
transceiver.setCodecPreferences(codecs);
const offer = await pc.createOffer();
const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0];
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
assert_equals(rtpParameters.codecs[0].name, firstCodec);
}, `setCodecPreferences() modifies the order of video codecs in createOffer`);
// Tests the note removed as result of discussion in
// https://github.com/w3c/webrtc-pc/issues/2933
promise_test(async (t) => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const transceiver = pc1.addTransceiver('video');
const {codecs} = RTCRtpReceiver.getCapabilities('video');
const vp8 = codecs.find(codec => codec.mimeType === 'video/VP8');
const h264 = codecs.find(codec => codec.mimeType === 'video/H264');
const thirdCodec = codecs.find(codec => ['video/VP9', 'video/AV1'].includes(codec.mimeType));
assert_true(!!vp8);
assert_true(!!h264);
assert_true(!!thirdCodec);
transceiver.setCodecPreferences([vp8, thirdCodec]);
await pc1.setLocalDescription();
await pc2.setRemoteDescription(pc1.localDescription);
const transceiver2 = pc2.getTransceivers()[0];
transceiver2.setCodecPreferences([h264, thirdCodec, vp8]);
await pc2.setLocalDescription();
await pc1.setRemoteDescription(pc2.localDescription);
const mediaSection = SDPUtils.getMediaSections(pc2.localDescription.sdp)[0];
const rtpParameters = SDPUtils.parseRtpParameters(mediaSection);
// Order is determined by pc2 but H264 is not present.
assert_equals(rtpParameters.codecs.length, 2);
assert_equals(rtpParameters.codecs[0].name, thirdCodec.mimeType.substring(6));
assert_equals(rtpParameters.codecs[1].name, 'VP8');
}, `setCodecPreferences() filters on receiver and prefers receiver order`);
["audio", "video"].forEach(kind => promise_test(async (t) => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const [codec] = RTCRtpReceiver.getCapabilities(kind).codecs;
codec.mimeType = codec.mimeType.toUpperCase();
const transceiver = pc.addTransceiver(kind);
transceiver.setCodecPreferences([codec]);
codec.mimeType = codec.mimeType.toLowerCase();
transceiver.setCodecPreferences([codec]);
}, `setCodecPreferences should accept ${kind} codecs regardless of mimeType case`));
</script>