| /* |
| * Copyright 2017 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| 'use strict'; |
| |
| var audio2 = document.querySelector('audio#audio2'); |
| var callButton = document.querySelector('button#callButton'); |
| var hangupButton = document.querySelector('button#hangupButton'); |
| var codecSelector = document.querySelector('select#codec'); |
| hangupButton.disabled = true; |
| callButton.onclick = call; |
| hangupButton.onclick = hangup; |
| |
| var pc1; |
| var pc2; |
| var localStream; |
| |
| var bitrateGraph; |
| var bitrateSeries; |
| |
| var packetGraph; |
| var packetSeries; |
| |
| var lastResult; |
| |
| var offerOptions = { |
| offerToReceiveAudio: 1, |
| offerToReceiveVideo: 0, |
| voiceActivityDetection: false |
| }; |
| |
| function gotStream(stream) { |
| hangupButton.disabled = false; |
| trace('Received local stream'); |
| localStream = stream; |
| var audioTracks = localStream.getAudioTracks(); |
| if (audioTracks.length > 0) { |
| trace('Using Audio device: ' + audioTracks[0].label); |
| } |
| localStream.getTracks().forEach( |
| function(track) { |
| pc1.addTrack( |
| track, |
| localStream |
| ); |
| } |
| ); |
| trace('Adding Local Stream to peer connection'); |
| |
| pc1.createOffer( |
| offerOptions |
| ).then( |
| gotDescription1, |
| onCreateSessionDescriptionError |
| ); |
| |
| bitrateSeries = new TimelineDataSeries(); |
| bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas'); |
| bitrateGraph.updateEndDate(); |
| |
| packetSeries = new TimelineDataSeries(); |
| packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas'); |
| packetGraph.updateEndDate(); |
| } |
| |
| function onCreateSessionDescriptionError(error) { |
| trace('Failed to create session description: ' + error.toString()); |
| } |
| |
| function call() { |
| callButton.disabled = true; |
| codecSelector.disabled = true; |
| trace('Starting call'); |
| var servers = null; |
| var pcConstraints = { |
| 'optional': [] |
| }; |
| pc1 = new RTCPeerConnection(servers, pcConstraints); |
| trace('Created local peer connection object pc1'); |
| pc1.onicecandidate = function(e) { |
| onIceCandidate(pc1, e); |
| }; |
| pc2 = new RTCPeerConnection(servers, pcConstraints); |
| trace('Created remote peer connection object pc2'); |
| pc2.onicecandidate = function(e) { |
| onIceCandidate(pc2, e); |
| }; |
| pc2.ontrack = gotRemoteStream; |
| trace('Requesting local stream'); |
| navigator.mediaDevices.getUserMedia({ |
| audio: true, |
| video: false |
| }) |
| .then(gotStream) |
| .catch(function(e) { |
| alert('getUserMedia() error: ' + e.name); |
| }); |
| } |
| |
| function gotDescription1(desc) { |
| trace('Offer from pc1 \n' + desc.sdp); |
| pc1.setLocalDescription(desc).then( |
| function() { |
| desc.sdp = forceChosenAudioCodec(desc.sdp); |
| pc2.setRemoteDescription(desc).then( |
| function() { |
| pc2.createAnswer().then( |
| gotDescription2, |
| onCreateSessionDescriptionError |
| ); |
| }, |
| onSetSessionDescriptionError |
| ); |
| }, |
| onSetSessionDescriptionError |
| ); |
| } |
| |
| function gotDescription2(desc) { |
| trace('Answer from pc2 \n' + desc.sdp); |
| pc2.setLocalDescription(desc).then( |
| function() { |
| desc.sdp = forceChosenAudioCodec(desc.sdp); |
| pc1.setRemoteDescription(desc).then( |
| function() { |
| }, |
| onSetSessionDescriptionError |
| ); |
| }, |
| onSetSessionDescriptionError |
| ); |
| } |
| |
| function hangup() { |
| trace('Ending call'); |
| localStream.getTracks().forEach(function(track) { |
| track.stop(); |
| }); |
| pc1.close(); |
| pc2.close(); |
| pc1 = null; |
| pc2 = null; |
| hangupButton.disabled = true; |
| callButton.disabled = false; |
| codecSelector.disabled = false; |
| } |
| |
| function gotRemoteStream(e) { |
| if (audio2.srcObject !== e.streams[0]) { |
| audio2.srcObject = e.streams[0]; |
| trace('Received remote stream'); |
| } |
| } |
| |
| function getOtherPc(pc) { |
| return (pc === pc1) ? pc2 : pc1; |
| } |
| |
| function getName(pc) { |
| return (pc === pc1) ? 'pc1' : 'pc2'; |
| } |
| |
| function onIceCandidate(pc, event) { |
| getOtherPc(pc).addIceCandidate(event.candidate) |
| .then( |
| function() { |
| onAddIceCandidateSuccess(pc); |
| }, |
| function(err) { |
| onAddIceCandidateError(pc, err); |
| } |
| ); |
| trace(getName(pc) + ' ICE candidate: \n' + (event.candidate ? |
| event.candidate.candidate : '(null)')); |
| } |
| |
| function onAddIceCandidateSuccess() { |
| trace('AddIceCandidate success.'); |
| } |
| |
| function onAddIceCandidateError(error) { |
| trace('Failed to add ICE Candidate: ' + error.toString()); |
| } |
| |
| function onSetSessionDescriptionError(error) { |
| trace('Failed to set session description: ' + error.toString()); |
| } |
| |
| function forceChosenAudioCodec(sdp) { |
| return maybePreferCodec(sdp, 'audio', 'send', codecSelector.value); |
| } |
| |
| // Copied from AppRTC's sdputils.js: |
| |
| // Sets |codec| as the default |type| codec if it's present. |
| // The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'. |
| function maybePreferCodec(sdp, type, dir, codec) { |
| var str = type + ' ' + dir + ' codec'; |
| if (codec === '') { |
| trace('No preference on ' + str + '.'); |
| return sdp; |
| } |
| |
| trace('Prefer ' + str + ': ' + codec); |
| |
| var sdpLines = sdp.split('\r\n'); |
| |
| // Search for m line. |
| var mLineIndex = findLine(sdpLines, 'm=', type); |
| if (mLineIndex === null) { |
| return sdp; |
| } |
| |
| // If the codec is available, set it as the default in m line. |
| var codecIndex = findLine(sdpLines, 'a=rtpmap', codec); |
| console.log('codecIndex', codecIndex); |
| if (codecIndex) { |
| var payload = getCodecPayloadType(sdpLines[codecIndex]); |
| if (payload) { |
| sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload); |
| } |
| } |
| |
| sdp = sdpLines.join('\r\n'); |
| return sdp; |
| } |
| |
| // Find the line in sdpLines that starts with |prefix|, and, if specified, |
| // contains |substr| (case-insensitive search). |
| function findLine(sdpLines, prefix, substr) { |
| return findLineInRange(sdpLines, 0, -1, prefix, substr); |
| } |
| |
| // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| |
| // and, if specified, contains |substr| (case-insensitive search). |
| function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { |
| var realEndLine = endLine !== -1 ? endLine : sdpLines.length; |
| for (var i = startLine; i < realEndLine; ++i) { |
| if (sdpLines[i].indexOf(prefix) === 0) { |
| if (!substr || |
| sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { |
| return i; |
| } |
| } |
| } |
| return null; |
| } |
| |
| // Gets the codec payload type from an a=rtpmap:X line. |
| function getCodecPayloadType(sdpLine) { |
| var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); |
| var result = sdpLine.match(pattern); |
| return (result && result.length === 2) ? result[1] : null; |
| } |
| |
| // Returns a new m= line with the specified codec as the first one. |
| function setDefaultCodec(mLine, payload) { |
| var elements = mLine.split(' '); |
| |
| // Just copy the first three parameters; codec order starts on fourth. |
| var newLine = elements.slice(0, 3); |
| |
| // Put target payload first and copy in the rest. |
| newLine.push(payload); |
| for (var i = 3; i < elements.length; i++) { |
| if (elements[i] !== payload) { |
| newLine.push(elements[i]); |
| } |
| } |
| return newLine.join(' '); |
| } |
| |
| // query getStats every second |
| window.setInterval(function() { |
| if (!window.pc1) { |
| return; |
| } |
| window.pc1.getStats(null).then(function(res) { |
| res.forEach(function(report) { |
| var bytes; |
| var packets; |
| var now = report.timestamp; |
| if ((report.type === 'outboundrtp') || |
| (report.type === 'outbound-rtp') || |
| (report.type === 'ssrc' && report.bytesSent)) { |
| bytes = report.bytesSent; |
| packets = report.packetsSent; |
| if (lastResult && lastResult.get(report.id)) { |
| // calculate bitrate |
| var bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) / |
| (now - lastResult.get(report.id).timestamp); |
| |
| // append to chart |
| bitrateSeries.addPoint(now, bitrate); |
| bitrateGraph.setDataSeries([bitrateSeries]); |
| bitrateGraph.updateEndDate(); |
| |
| // calculate number of packets and append to chart |
| packetSeries.addPoint(now, packets - |
| lastResult.get(report.id).packetsSent); |
| packetGraph.setDataSeries([packetSeries]); |
| packetGraph.updateEndDate(); |
| } |
| } |
| }); |
| lastResult = res; |
| }); |
| }, 1000); |