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