|  | <!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`); | 
|  |  | 
|  | </script> |