| /* |
| * 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 getMediaButton = document.querySelector('button#getMedia'); |
| var connectButton = document.querySelector('button#connect'); |
| var hangupButton = document.querySelector('button#hangup'); |
| |
| getMediaButton.onclick = getMedia; |
| connectButton.onclick = createPeerConnection; |
| hangupButton.onclick = hangup; |
| |
| var minWidthInput = document.querySelector('div#minWidth input'); |
| var maxWidthInput = document.querySelector('div#maxWidth input'); |
| var minHeightInput = document.querySelector('div#minHeight input'); |
| var maxHeightInput = document.querySelector('div#maxHeight input'); |
| var minFramerateInput = document.querySelector('div#minFramerate input'); |
| var maxFramerateInput = document.querySelector('div#maxFramerate input'); |
| |
| minWidthInput.onchange = maxWidthInput.onchange = |
| minHeightInput.onchange = maxHeightInput.onchange = |
| minFramerateInput.onchange = maxFramerateInput.onchange = displayRangeValue; |
| |
| var getUserMediaConstraintsDiv = |
| document.querySelector('div#getUserMediaConstraints'); |
| var bitrateDiv = document.querySelector('div#bitrate'); |
| var peerDiv = document.querySelector('div#peer'); |
| var senderStatsDiv = document.querySelector('div#senderStats'); |
| var receiverStatsDiv = document.querySelector('div#receiverStats'); |
| |
| var localVideo = document.querySelector('div#localVideo video'); |
| var remoteVideo = document.querySelector('div#remoteVideo video'); |
| var localVideoStatsDiv = document.querySelector('div#localVideo div'); |
| var remoteVideoStatsDiv = document.querySelector('div#remoteVideo div'); |
| |
| var localPeerConnection; |
| var remotePeerConnection; |
| var localStream; |
| var bytesPrev; |
| var timestampPrev; |
| |
| main(); |
| |
| function main() { |
| displayGetUserMediaConstraints(); |
| } |
| |
| function hangup() { |
| trace('Ending call'); |
| localPeerConnection.close(); |
| remotePeerConnection.close(); |
| localPeerConnection = null; |
| remotePeerConnection = null; |
| |
| localStream.getTracks().forEach(function(track) { |
| track.stop(); |
| }); |
| localStream = null; |
| |
| hangupButton.disabled = true; |
| getMediaButton.disabled = false; |
| } |
| |
| function getMedia() { |
| getMediaButton.disabled = true; |
| if (localStream) { |
| localStream.getTracks().forEach(function(track) { |
| track.stop(); |
| }); |
| var videoTracks = localStream.getVideoTracks(); |
| for (var i = 0; i !== videoTracks.length; ++i) { |
| videoTracks[i].stop(); |
| } |
| } |
| navigator.mediaDevices.getUserMedia(getUserMediaConstraints()) |
| .then(gotStream) |
| .catch(function(e) { |
| var message = 'getUserMedia error: ' + e.name + '\n' + |
| 'PermissionDeniedError may mean invalid constraints.'; |
| alert(message); |
| console.log(message); |
| getMediaButton.disabled = false; |
| }); |
| } |
| |
| function gotStream(stream) { |
| connectButton.disabled = false; |
| console.log('GetUserMedia succeeded'); |
| localStream = stream; |
| localVideo.srcObject = stream; |
| } |
| |
| function getUserMediaConstraints() { |
| var constraints = {}; |
| constraints.audio = true; |
| constraints.video = {}; |
| if (minWidthInput.value !== '0') { |
| constraints.video.width = {}; |
| constraints.video.width.min = minWidthInput.value; |
| } |
| if (maxWidthInput.value !== '0') { |
| constraints.video.width = constraints.video.width || {}; |
| constraints.video.width.max = maxWidthInput.value; |
| } |
| if (minHeightInput.value !== '0') { |
| constraints.video.height = {}; |
| constraints.video.height.min = minHeightInput.value; |
| } |
| if (maxHeightInput.value !== '0') { |
| constraints.video.height = constraints.video.height || {}; |
| constraints.video.height.max = maxHeightInput.value; |
| } |
| if (minFramerateInput.value !== '0') { |
| constraints.video.frameRate = {}; |
| constraints.video.frameRate.min = minFramerateInput.value; |
| } |
| if (maxFramerateInput.value !== '0') { |
| constraints.video.frameRate = constraints.video.frameRate || {}; |
| constraints.video.frameRate.max = maxFramerateInput.value; |
| } |
| |
| return constraints; |
| } |
| |
| function displayGetUserMediaConstraints() { |
| var constraints = getUserMediaConstraints(); |
| console.log('getUserMedia constraints', constraints); |
| getUserMediaConstraintsDiv.textContent = |
| JSON.stringify(constraints, null, ' '); |
| } |
| |
| function createPeerConnection() { |
| connectButton.disabled = true; |
| hangupButton.disabled = false; |
| |
| bytesPrev = 0; |
| timestampPrev = 0; |
| localPeerConnection = new RTCPeerConnection(null); |
| remotePeerConnection = new RTCPeerConnection(null); |
| localStream.getTracks().forEach( |
| function(track) { |
| localPeerConnection.addTrack( |
| track, |
| localStream |
| ); |
| } |
| ); |
| console.log('localPeerConnection creating offer'); |
| localPeerConnection.onnegotiationeeded = function() { |
| console.log('Negotiation needed - localPeerConnection'); |
| }; |
| remotePeerConnection.onnegotiationeeded = function() { |
| console.log('Negotiation needed - remotePeerConnection'); |
| }; |
| localPeerConnection.onicecandidate = function(e) { |
| console.log('Candidate localPeerConnection'); |
| remotePeerConnection.addIceCandidate(e.candidate) |
| .then( |
| onAddIceCandidateSuccess, |
| onAddIceCandidateError |
| ); |
| }; |
| remotePeerConnection.onicecandidate = function(e) { |
| console.log('Candidate remotePeerConnection'); |
| localPeerConnection.addIceCandidate(e.candidate) |
| .then( |
| onAddIceCandidateSuccess, |
| onAddIceCandidateError |
| ); |
| }; |
| remotePeerConnection.ontrack = function(e) { |
| if (remoteVideo.srcObject !== e.streams[0]) { |
| console.log('remotePeerConnection got stream'); |
| remoteVideo.srcObject = e.streams[0]; |
| } |
| }; |
| localPeerConnection.createOffer().then( |
| function(desc) { |
| console.log('localPeerConnection offering'); |
| localPeerConnection.setLocalDescription(desc); |
| remotePeerConnection.setRemoteDescription(desc); |
| remotePeerConnection.createAnswer().then( |
| function(desc2) { |
| console.log('remotePeerConnection answering'); |
| remotePeerConnection.setLocalDescription(desc2); |
| localPeerConnection.setRemoteDescription(desc2); |
| }, |
| function(err) { |
| console.log(err); |
| } |
| ); |
| }, |
| function(err) { |
| console.log(err); |
| } |
| ); |
| } |
| |
| function onAddIceCandidateSuccess() { |
| trace('AddIceCandidate success.'); |
| } |
| |
| function onAddIceCandidateError(error) { |
| trace('Failed to add Ice Candidate: ' + error.toString()); |
| } |
| |
| // Display statistics |
| setInterval(function() { |
| if (remotePeerConnection && remotePeerConnection.getRemoteStreams()[0]) { |
| remotePeerConnection.getStats(null) |
| .then(function(results) { |
| var statsString = dumpStats(results); |
| receiverStatsDiv.innerHTML = '<h2>Receiver stats</h2>' + statsString; |
| // calculate video bitrate |
| results.forEach(function(report) { |
| var now = report.timestamp; |
| |
| var bitrate; |
| if (report.type === 'inboundrtp' && report.mediaType === 'video') { |
| // firefox calculates the bitrate for us |
| // https://bugzilla.mozilla.org/show_bug.cgi?id=951496 |
| bitrate = Math.floor(report.bitrateMean / 1024); |
| } else if (report.type === 'ssrc' && report.bytesReceived && |
| report.googFrameHeightReceived) { |
| // chrome does not so we need to do it ourselves |
| var bytes = report.bytesReceived; |
| if (timestampPrev) { |
| bitrate = 8 * (bytes - bytesPrev) / (now - timestampPrev); |
| bitrate = Math.floor(bitrate); |
| } |
| bytesPrev = bytes; |
| timestampPrev = now; |
| } |
| if (bitrate) { |
| bitrate += ' kbits/sec'; |
| bitrateDiv.innerHTML = '<strong>Bitrate:</strong> ' + bitrate; |
| } |
| }); |
| |
| // figure out the peer's ip |
| var activeCandidatePair = null; |
| var remoteCandidate = null; |
| |
| // Search for the candidate pair, spec-way first. |
| results.forEach(function(report) { |
| if (report.type === 'transport') { |
| activeCandidatePair = results.get(report.selectedCandidatePairId); |
| } |
| }); |
| // Fallback for Firefox and Chrome legacy stats. |
| if (!activeCandidatePair) { |
| results.forEach(function(report) { |
| if (report.type === 'candidate-pair' && report.selected || |
| report.type === 'googCandidatePair' && |
| report.googActiveConnection === 'true') { |
| activeCandidatePair = report; |
| } |
| }); |
| } |
| if (activeCandidatePair && activeCandidatePair.remoteCandidateId) { |
| remoteCandidate = results.get(activeCandidatePair.remoteCandidateId); |
| } |
| if (remoteCandidate) { |
| if (remoteCandidate.ip && remoteCandidate.port) { |
| peerDiv.innerHTML = '<strong>Connected to:</strong> ' + |
| remoteCandidate.ip + ':' + remoteCandidate.port; |
| } else if (remoteCandidate.ipAddress && remoteCandidate.portNumber) { |
| // Fall back to old names. |
| peerDiv.innerHTML = '<strong>Connected to:</strong> ' + |
| remoteCandidate.ipAddress + |
| ':' + remoteCandidate.portNumber; |
| } |
| } |
| }, function(err) { |
| console.log(err); |
| }); |
| localPeerConnection.getStats(null) |
| .then(function(results) { |
| var statsString = dumpStats(results); |
| senderStatsDiv.innerHTML = '<h2>Sender stats</h2>' + statsString; |
| }, function(err) { |
| console.log(err); |
| }); |
| } else { |
| console.log('Not connected yet'); |
| } |
| // Collect some stats from the video tags. |
| if (localVideo.videoWidth) { |
| localVideoStatsDiv.innerHTML = '<strong>Video dimensions:</strong> ' + |
| localVideo.videoWidth + 'x' + localVideo.videoHeight + 'px'; |
| } |
| if (remoteVideo.videoWidth) { |
| remoteVideoStatsDiv.innerHTML = '<strong>Video dimensions:</strong> ' + |
| remoteVideo.videoWidth + 'x' + remoteVideo.videoHeight + 'px'; |
| } |
| }, 1000); |
| |
| // Dumping a stats variable as a string. |
| // might be named toString? |
| function dumpStats(results) { |
| var statsString = ''; |
| results.forEach(function(res) { |
| statsString += '<h3>Report type='; |
| statsString += res.type; |
| statsString += '</h3>\n'; |
| statsString += 'id ' + res.id + '<br>\n'; |
| statsString += 'time ' + res.timestamp + '<br>\n'; |
| Object.keys(res).forEach(function(k) { |
| if (k !== 'timestamp' && k !== 'type' && k !== 'id') { |
| statsString += k + ': ' + res[k] + '<br>\n'; |
| } |
| }); |
| }); |
| return statsString; |
| } |
| |
| // Utility to show the value of a range in a sibling span element |
| function displayRangeValue(e) { |
| var span = e.target.parentElement.querySelector('span'); |
| span.textContent = e.target.value; |
| displayGetUserMediaConstraints(); |
| } |