peerconnection.js (5190B)
1 /** 2 * @fileoverview Utility functions for tests utilizing PeerConnections 3 */ 4 5 /** 6 * Exchanges offers and answers between two peer connections. 7 * 8 * pc1's offer is set as local description in pc1 and 9 * remote description in pc2. After that, pc2's answer 10 * is set as it's local description and remote description in pc1. 11 * 12 * @param {!RTCPeerConnection} pc1 The first peer connection. 13 * @param {!RTCPeerConnection} pc2 The second peer connection. 14 */ 15 async function exchangeOfferAnswer(pc1, pc2) { 16 await pc1.setLocalDescription(); 17 await pc2.setRemoteDescription(pc1.localDescription); 18 await pc2.setLocalDescription(); 19 await pc1.setRemoteDescription(pc2.localDescription); 20 } 21 22 /** 23 * Sets the specified codec preference if it's included in the transceiver's 24 * list of supported codecs. 25 * @param {!RTCRtpTransceiver} transceiver The RTP transceiver. 26 * @param {string} codecPreference The codec preference. 27 */ 28 function setTransceiverCodecPreference(transceiver, codecPreference) { 29 for (const codec of RTCRtpSender.getCapabilities('video').codecs) { 30 if (codec.mimeType.includes(codecPreference)) { 31 transceiver.setCodecPreferences([codec]); 32 return; 33 } 34 } 35 } 36 37 /** 38 * Starts a connection between two peer connections, using a audio and/or video 39 * stream. 40 * @param {*} t Test instance. 41 * @param {boolean} audio True if audio should be used. 42 * @param {boolean} video True if video should be used. 43 * @param {string} [videoCodecPreference] String containing the codec preference. 44 * @returns an array with the two connected peer connections, the remote stream, 45 * and an object containing transceivers by kind. 46 */ 47 async function startConnection(t, audio, video, videoCodecPreference) { 48 const scope = []; 49 if (audio) scope.push("microphone"); 50 if (video) scope.push("camera"); 51 await setMediaPermission("granted", scope); 52 const stream = await navigator.mediaDevices.getUserMedia({audio, video}); 53 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 54 const pc1 = new RTCPeerConnection(); 55 t.add_cleanup(() => pc1.close()); 56 const pc2 = new RTCPeerConnection(); 57 t.add_cleanup(() => pc2.close()); 58 const transceivers = {}; 59 for (const track of stream.getTracks()) { 60 const transceiver = pc1.addTransceiver(track, {streams: [stream]}); 61 transceivers[track.kind] = transceiver; 62 if (videoCodecPreference && track.kind == 'video') { 63 setTransceiverCodecPreference(transceiver, videoCodecPreference); 64 } 65 } 66 for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) { 67 local.addEventListener('icecandidate', ({candidate}) => { 68 if (!candidate || remote.signalingState == 'closed') return; 69 remote.addIceCandidate(candidate); 70 }); 71 } 72 const haveTrackEvent = new Promise(r => pc2.ontrack = r); 73 await exchangeOfferAnswer(pc1, pc2); 74 const {streams} = await haveTrackEvent; 75 return [pc1, pc2, streams[0], transceivers]; 76 } 77 78 /** 79 * Given a peer connection, return after at least numFramesOrPackets 80 * frames (video) or packets (audio) have been received. 81 * @param {*} t Test instance. 82 * @param {!RTCPeerConnection} pc The peer connection. 83 * @param {boolean} lookForAudio True if audio packets should be waited for. 84 * @param {boolean} lookForVideo True if video packets should be waited for. 85 * @param {int} numFramesOrPackets Number of frames (video) and packets (audio) 86 * to wait for. 87 */ 88 async function waitForReceivedFramesOrPackets( 89 t, pc, lookForAudio, lookForVideo, numFramesOrPackets) { 90 let initialAudioPackets = 0; 91 let initialVideoFrames = 0; 92 while (lookForAudio || lookForVideo) { 93 const report = await pc.getStats(); 94 for (const stats of report.values()) { 95 if (stats.type == 'inbound-rtp') { 96 if (lookForAudio && stats.kind == 'audio') { 97 if (!initialAudioPackets) { 98 initialAudioPackets = stats.packetsReceived; 99 } else if (stats.packetsReceived > initialAudioPackets + 100 numFramesOrPackets) { 101 lookForAudio = false; 102 } 103 } 104 if (lookForVideo && stats.kind == 'video') { 105 if (!initialVideoFrames) { 106 initialVideoFrames = stats.framesDecoded; 107 } else if (stats.framesDecoded > initialVideoFrames + 108 numFramesOrPackets) { 109 lookForVideo = false; 110 } 111 } 112 } 113 } 114 await new Promise(r => t.step_timeout(r, 100)); 115 } 116 } 117 118 /** 119 * Given a peer connection, return after one of its inbound RTP connections 120 * includes use of the specified codec. 121 * @param {*} t Test instance. 122 * @param {!RTCPeerConnection} pc The peer connection. 123 * @param {string} codecToLookFor The waited-for codec. 124 */ 125 async function waitForReceivedCodec(t, pc, codecToLookFor) { 126 let currentCodecId; 127 for (;;) { 128 const report = await pc.getStats(); 129 for (const stats of report.values()) { 130 if (stats.type == 'inbound-rtp' && stats.kind == 'video') { 131 if (stats.codecId) { 132 if (report.get(stats.codecId).mimeType.toLowerCase() 133 .includes(codecToLookFor.toLowerCase())) { 134 return; 135 } 136 } 137 } 138 } 139 await new Promise(r => t.step_timeout(r, 100)); 140 } 141 }