| <!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`); |
| |
| ['rtx', 'red', 'ulpfec'].forEach(resiliencyMechanism => { |
| promise_test(async (t) => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| const transceiver = pc.addTransceiver('video'); |
| const {codecs} = RTCRtpReceiver.getCapabilities('video'); |
| const filteredCodecs = codecs. |
| filter(codec => codec.mimeType !== 'video/' + resiliencyMechanism); |
| assert_not_equals(codecs.length, filteredCodecs.length); |
| transceiver.setCodecPreferences(filteredCodecs); |
| |
| const offer = await pc.createOffer(); |
| const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0]; |
| const rtpParameters = SDPUtils.parseRtpParameters(mediaSection); |
| assert_equals(rtpParameters.codecs.find(codec => codec.name === resiliencyMechanism), |
| undefined); |
| }, `setCodecPreferences() can remove ${resiliencyMechanism}`); |
| }); |
| |
| // 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> |