OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright 2017 The Chromium Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. |
| 5 */ |
| 6 |
| 7 'use strict'; |
| 8 |
| 9 var audio2 = document.querySelector('audio#audio2'); |
| 10 var callButton = document.querySelector('button#callButton'); |
| 11 var hangupButton = document.querySelector('button#hangupButton'); |
| 12 var codecSelector = document.querySelector('select#codec'); |
| 13 hangupButton.disabled = true; |
| 14 callButton.onclick = call; |
| 15 hangupButton.onclick = hangup; |
| 16 |
| 17 var pc1; |
| 18 var pc2; |
| 19 var localStream; |
| 20 |
| 21 var bitrateGraph; |
| 22 var bitrateSeries; |
| 23 |
| 24 var packetGraph; |
| 25 var packetSeries; |
| 26 |
| 27 var lastResult; |
| 28 |
| 29 var offerOptions = { |
| 30 offerToReceiveAudio: 1, |
| 31 offerToReceiveVideo: 0, |
| 32 voiceActivityDetection: false |
| 33 }; |
| 34 |
| 35 function gotStream(stream) { |
| 36 hangupButton.disabled = false; |
| 37 trace('Received local stream'); |
| 38 localStream = stream; |
| 39 var audioTracks = localStream.getAudioTracks(); |
| 40 if (audioTracks.length > 0) { |
| 41 trace('Using Audio device: ' + audioTracks[0].label); |
| 42 } |
| 43 pc1.addStream(localStream); |
| 44 trace('Adding Local Stream to peer connection'); |
| 45 |
| 46 pc1.createOffer( |
| 47 offerOptions |
| 48 ).then( |
| 49 gotDescription1, |
| 50 onCreateSessionDescriptionError |
| 51 ); |
| 52 |
| 53 bitrateSeries = new TimelineDataSeries(); |
| 54 bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas'); |
| 55 bitrateGraph.updateEndDate(); |
| 56 |
| 57 packetSeries = new TimelineDataSeries(); |
| 58 packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas'); |
| 59 packetGraph.updateEndDate(); |
| 60 } |
| 61 |
| 62 function onCreateSessionDescriptionError(error) { |
| 63 trace('Failed to create session description: ' + error.toString()); |
| 64 } |
| 65 |
| 66 function call() { |
| 67 callButton.disabled = true; |
| 68 codecSelector.disabled = true; |
| 69 trace('Starting call'); |
| 70 var servers = null; |
| 71 var pcConstraints = { |
| 72 'optional': [] |
| 73 }; |
| 74 pc1 = new RTCPeerConnection(servers, pcConstraints); |
| 75 trace('Created local peer connection object pc1'); |
| 76 pc1.onicecandidate = function(e) { |
| 77 onIceCandidate(pc1, e); |
| 78 }; |
| 79 pc2 = new RTCPeerConnection(servers, pcConstraints); |
| 80 trace('Created remote peer connection object pc2'); |
| 81 pc2.onicecandidate = function(e) { |
| 82 onIceCandidate(pc2, e); |
| 83 }; |
| 84 pc2.onaddstream = gotRemoteStream; |
| 85 trace('Requesting local stream'); |
| 86 navigator.mediaDevices.getUserMedia({ |
| 87 audio: true, |
| 88 video: false |
| 89 }) |
| 90 .then(gotStream) |
| 91 .catch(function(e) { |
| 92 alert('getUserMedia() error: ' + e.name); |
| 93 }); |
| 94 } |
| 95 |
| 96 function gotDescription1(desc) { |
| 97 trace('Offer from pc1 \n' + desc.sdp); |
| 98 pc1.setLocalDescription(desc).then( |
| 99 function() { |
| 100 desc.sdp = forceChosenAudioCodec(desc.sdp); |
| 101 pc2.setRemoteDescription(desc).then( |
| 102 function() { |
| 103 pc2.createAnswer().then( |
| 104 gotDescription2, |
| 105 onCreateSessionDescriptionError |
| 106 ); |
| 107 }, |
| 108 onSetSessionDescriptionError |
| 109 ); |
| 110 }, |
| 111 onSetSessionDescriptionError |
| 112 ); |
| 113 } |
| 114 |
| 115 function gotDescription2(desc) { |
| 116 trace('Answer from pc2 \n' + desc.sdp); |
| 117 pc2.setLocalDescription(desc).then( |
| 118 function() { |
| 119 desc.sdp = forceChosenAudioCodec(desc.sdp); |
| 120 pc1.setRemoteDescription(desc).then( |
| 121 function() { |
| 122 }, |
| 123 onSetSessionDescriptionError |
| 124 ); |
| 125 }, |
| 126 onSetSessionDescriptionError |
| 127 ); |
| 128 } |
| 129 |
| 130 function hangup() { |
| 131 trace('Ending call'); |
| 132 localStream.getTracks().forEach(function(track) { |
| 133 track.stop(); |
| 134 }); |
| 135 pc1.close(); |
| 136 pc2.close(); |
| 137 pc1 = null; |
| 138 pc2 = null; |
| 139 hangupButton.disabled = true; |
| 140 callButton.disabled = false; |
| 141 codecSelector.disabled = false; |
| 142 } |
| 143 |
| 144 function gotRemoteStream(e) { |
| 145 audio2.srcObject = e.stream; |
| 146 trace('Received remote stream'); |
| 147 } |
| 148 |
| 149 function getOtherPc(pc) { |
| 150 return (pc === pc1) ? pc2 : pc1; |
| 151 } |
| 152 |
| 153 function getName(pc) { |
| 154 return (pc === pc1) ? 'pc1' : 'pc2'; |
| 155 } |
| 156 |
| 157 function onIceCandidate(pc, event) { |
| 158 getOtherPc(pc).addIceCandidate(event.candidate) |
| 159 .then( |
| 160 function() { |
| 161 onAddIceCandidateSuccess(pc); |
| 162 }, |
| 163 function(err) { |
| 164 onAddIceCandidateError(pc, err); |
| 165 } |
| 166 ); |
| 167 trace(getName(pc) + ' ICE candidate: \n' + (event.candidate ? |
| 168 event.candidate.candidate : '(null)')); |
| 169 } |
| 170 |
| 171 function onAddIceCandidateSuccess() { |
| 172 trace('AddIceCandidate success.'); |
| 173 } |
| 174 |
| 175 function onAddIceCandidateError(error) { |
| 176 trace('Failed to add ICE Candidate: ' + error.toString()); |
| 177 } |
| 178 |
| 179 function onSetSessionDescriptionError(error) { |
| 180 trace('Failed to set session description: ' + error.toString()); |
| 181 } |
| 182 |
| 183 function forceChosenAudioCodec(sdp) { |
| 184 return maybePreferCodec(sdp, 'audio', 'send', codecSelector.value); |
| 185 } |
| 186 |
| 187 // Copied from AppRTC's sdputils.js: |
| 188 |
| 189 // Sets |codec| as the default |type| codec if it's present. |
| 190 // The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'. |
| 191 function maybePreferCodec(sdp, type, dir, codec) { |
| 192 var str = type + ' ' + dir + ' codec'; |
| 193 if (codec === '') { |
| 194 trace('No preference on ' + str + '.'); |
| 195 return sdp; |
| 196 } |
| 197 |
| 198 trace('Prefer ' + str + ': ' + codec); |
| 199 |
| 200 var sdpLines = sdp.split('\r\n'); |
| 201 |
| 202 // Search for m line. |
| 203 var mLineIndex = findLine(sdpLines, 'm=', type); |
| 204 if (mLineIndex === null) { |
| 205 return sdp; |
| 206 } |
| 207 |
| 208 // If the codec is available, set it as the default in m line. |
| 209 var codecIndex = findLine(sdpLines, 'a=rtpmap', codec); |
| 210 console.log('codecIndex', codecIndex); |
| 211 if (codecIndex) { |
| 212 var payload = getCodecPayloadType(sdpLines[codecIndex]); |
| 213 if (payload) { |
| 214 sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload); |
| 215 } |
| 216 } |
| 217 |
| 218 sdp = sdpLines.join('\r\n'); |
| 219 return sdp; |
| 220 } |
| 221 |
| 222 // Find the line in sdpLines that starts with |prefix|, and, if specified, |
| 223 // contains |substr| (case-insensitive search). |
| 224 function findLine(sdpLines, prefix, substr) { |
| 225 return findLineInRange(sdpLines, 0, -1, prefix, substr); |
| 226 } |
| 227 |
| 228 // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| |
| 229 // and, if specified, contains |substr| (case-insensitive search). |
| 230 function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { |
| 231 var realEndLine = endLine !== -1 ? endLine : sdpLines.length; |
| 232 for (var i = startLine; i < realEndLine; ++i) { |
| 233 if (sdpLines[i].indexOf(prefix) === 0) { |
| 234 if (!substr || |
| 235 sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { |
| 236 return i; |
| 237 } |
| 238 } |
| 239 } |
| 240 return null; |
| 241 } |
| 242 |
| 243 // Gets the codec payload type from an a=rtpmap:X line. |
| 244 function getCodecPayloadType(sdpLine) { |
| 245 var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); |
| 246 var result = sdpLine.match(pattern); |
| 247 return (result && result.length === 2) ? result[1] : null; |
| 248 } |
| 249 |
| 250 // Returns a new m= line with the specified codec as the first one. |
| 251 function setDefaultCodec(mLine, payload) { |
| 252 var elements = mLine.split(' '); |
| 253 |
| 254 // Just copy the first three parameters; codec order starts on fourth. |
| 255 var newLine = elements.slice(0, 3); |
| 256 |
| 257 // Put target payload first and copy in the rest. |
| 258 newLine.push(payload); |
| 259 for (var i = 3; i < elements.length; i++) { |
| 260 if (elements[i] !== payload) { |
| 261 newLine.push(elements[i]); |
| 262 } |
| 263 } |
| 264 return newLine.join(' '); |
| 265 } |
| 266 |
| 267 // query getStats every second |
| 268 window.setInterval(function() { |
| 269 if (!window.pc1) { |
| 270 return; |
| 271 } |
| 272 window.pc1.getStats(null).then(function(res) { |
| 273 res.forEach(function(report) { |
| 274 var bytes; |
| 275 var packets; |
| 276 var now = report.timestamp; |
| 277 if ((report.type === 'outboundrtp') || |
| 278 (report.type === 'outbound-rtp') || |
| 279 (report.type === 'ssrc' && report.bytesSent)) { |
| 280 bytes = report.bytesSent; |
| 281 packets = report.packetsSent; |
| 282 if (lastResult && lastResult.get(report.id)) { |
| 283 // calculate bitrate |
| 284 var bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) / |
| 285 (now - lastResult.get(report.id).timestamp); |
| 286 |
| 287 // append to chart |
| 288 bitrateSeries.addPoint(now, bitrate); |
| 289 bitrateGraph.setDataSeries([bitrateSeries]); |
| 290 bitrateGraph.updateEndDate(); |
| 291 |
| 292 // calculate number of packets and append to chart |
| 293 packetSeries.addPoint(now, packets - |
| 294 lastResult.get(report.id).packetsSent); |
| 295 packetGraph.setDataSeries([packetSeries]); |
| 296 packetGraph.updateEndDate(); |
| 297 } |
| 298 } |
| 299 }); |
| 300 lastResult = res; |
| 301 }); |
| 302 }, 1000); |
OLD | NEW |