webrtc/samples/js/demos/html/constraints-and-stats.html
2014-01-30 23:38:44 +00:00

368 lines
11 KiB
HTML

<html>
<head>
<title>Constraints and Statistics</title>
<!-- Load the polyfill to switch-hit between Chrome and Firefox -->
<script src="../../base/adapter.js"></script>
<style type="text/css">
td { vertical-align: top; }
</style>
<script>
var mystream;
var pc1;
var pc2;
var bytesPrev = 0;
var timestampPrev = 0;
$ = function(id) {
return document.getElementById(id);
}
function log(txt) {
console.log(txt);
}
function openCamera() {
if (mystream) {
mystream.stop();
}
navigator.getUserMedia(cameraConstraints(), gotStream, function() {
log("GetUserMedia failed");
});
}
function gotStream(stream) {
log("GetUserMedia succeeded");
mystream = stream;
attachMediaStream($("local-video"), stream);
}
function cameraConstraints() {
var constraints = {};
constraints.audio = true;
constraints.video = { mandatory: {}, optional: [] };
if ($("minwidth").value != "0") {
constraints.video.mandatory.minWidth = $("minwidth").value;
}
if ($("maxwidth").value != "0") {
constraints.video.mandatory.maxWidth = $("maxwidth").value;
}
if ($("minheight").value != "0") {
constraints.video.mandatory.minHeight = $("minheight").value;
}
if ($("maxheight").value != "0") {
constraints.video.mandatory.maxHeight = $("maxheight").value;
}
if ($("frameRate").value != "0") {
constraints.video.mandatory.minFrameRate = $("frameRate").value;
}
log('Camera constraints are ' + JSON.stringify(constraints));
$("cameraConstraints").innerHTML = JSON.stringify(constraints, null, ' ');
return constraints;
}
function streamConstraints() {
var constraints = { mandatory: {}, optional: [] };
if ($("bandwidth").value != "0") {
constraints.optional[0] = { 'bandwidth' : $('bandwidth').value };
}
log('Constraints are ' + JSON.stringify(constraints));
$("addStreamConstraints").innerHTML = JSON.stringify(constraints, null, ' ');
return constraints;
}
function connect() {
pc1 = new RTCPeerConnection(null);
pc2 = new RTCPeerConnection(null);
pc1.addStream(mystream, streamConstraints());
log('PC1 creating offer');
pc1.onnegotiationeeded = function() {
log('Negotiation needed - PC1');
}
pc2.onnegotiationeeded = function() {
log('Negotiation needed - PC2');
}
pc1.onicecandidate = function(e) {
log('Candidate PC1');
if (e.candidate) {
pc2.addIceCandidate(new RTCIceCandidate(e.candidate));
}
}
pc2.onicecandidate = function(e) {
log('Candidate PC2');
if (e.candidate) {
pc1.addIceCandidate(new RTCIceCandidate(e.candidate));
}
}
pc2.onaddstream = function(e) {
log('PC2 got stream');
attachMediaStream($('remote-video'), e.stream);
log('Remote video is ' + $('remote-video').src);
}
pc1.createOffer(function(desc) {
log('PC1 offering');
pc1.setLocalDescription(desc);
pc2.setRemoteDescription(desc);
pc2.createAnswer(function(desc2) {
log('PC2 answering');
pc2.setLocalDescription(desc2);
pc1.setRemoteDescription(desc2);
});
});
}
// Augumentation of stats entries with utility functions.
// The augumented entry does what the stats entry does, but adds
// utility functions.
function AugumentedStatsResponse(response) {
this.response = response;
this.addressPairMap = [];
}
AugumentedStatsResponse.prototype.collectAddressPairs = function(componentId) {
if (!this.addressPairMap[componentId]) {
this.addressPairMap[componentId] = [];
for (var i = 0; i < this.response.result().length; ++i) {
var res = this.response.result()[i];
if (res.type == 'googCandidatePair' &&
res.stat('googChannelId') == componentId) {
this.addressPairMap[componentId].push(res);
}
}
}
return this.addressPairMap[componentId];
}
AugumentedStatsResponse.prototype.result = function() {
return this.response.result();
}
// The indexed getter isn't easy to prototype.
AugumentedStatsResponse.prototype.get = function(key) {
return this.response[key];
}
// Display statistics
var statCollector = setInterval(function() {
var display = function(str) {
$('bitrate').innerHTML = str;
}
display("No stream");
if (pc2 && pc2.getRemoteStreams()[0]) {
if (pc2.getStats) {
pc2.getStats(function(rawStats) {
stats = new AugumentedStatsResponse(rawStats);
var statsString = '';
var results = stats.result();
var videoFlowInfo = 'No bitrate stats';
for (var i = 0; i < results.length; ++i) {
var res = results[i];
statsString += '<h3>Report ';
statsString += i;
statsString += '</h3>';
if (!res.local || res.local === res) {
statsString += dumpStats(res);
// The bandwidth info for video is in a type ssrc stats record
// with googFrameHeightReceived defined.
// Should check for mediatype = video, but this is not
// implemented yet.
if (res.type == 'ssrc' && res.stat('googFrameHeightReceived')) {
// This is the video flow.
videoFlowInfo = extractVideoFlowInfo(res, stats);
}
} else {
// Pre-227.0.1445 (188719) browser
if (res.local) {
statsString += "<p>Local ";
statsString += dumpStats(res.local);
}
if (res.remote) {
statsString += "<p>Remote ";
statsString += dumpStats(res.remote);
}
}
}
$('receiverstats').innerHTML = statsString;
display(videoFlowInfo);
});
pc1.getStats(function(stats) {
var statsString = '';
var results = stats.result();
for (var i = 0; i < results.length; ++i) {
var res = results[i];
statsString += '<h3>Report ';
statsString += i;
statsString += '</h3>';
if (!res.local || res.local === res) {
statsString += dumpStats(res);
}
}
$('senderstats').innerHTML = statsString;
});
} else {
display('No stats function. Use at least Chrome 24.0.1285');
}
} else {
log('Not connected yet');
}
// Collect some stats from the video tags.
local_video = $('local-video');
if (local_video) {
$('local-video-stats').innerHTML = local_video.videoWidth +
'x' + local_video.videoHeight;
}
remote_video = $('remote-video');
if (remote_video) {
$('remote-video-stats').innerHTML = remote_video.videoWidth +
'x' + remote_video.videoHeight;
}
}, 1000);
function extractVideoFlowInfo(res, allStats) {
var description = '';
var bytesNow = res.stat('bytesReceived');
if (timestampPrev > 0) {
var bitRate = Math.round((bytesNow - bytesPrev) * 8 /
(res.timestamp - timestampPrev));
description = bitRate + ' kbits/sec';
}
timestampPrev = res.timestamp;
bytesPrev = bytesNow;
if (res.stat('transportId')) {
component = allStats.get(res.stat('transportId'));
if (component) {
addresses = allStats.collectAddressPairs(component.id);
if (addresses.length > 0) {
description += ' from IP ';
description += addresses[0].stat('googRemoteAddress');
} else {
description += ' no address';
}
} else {
description += ' No component stats';
}
} else {
description += ' No component ID';
}
return description;
}
// Dumping a stats variable as a string.
// might be named toString?
function dumpStats(obj) {
var statsString = 'Timestamp:';
statsString += obj.timestamp;
if (obj.id) {
statsString += "<br>id ";
statsString += obj.id;
}
if (obj.type) {
statsString += " type ";
statsString += obj.type;
}
if (obj.names) {
names = obj.names();
for (var i = 0; i < names.length; ++i) {
statsString += '<br>';
statsString += names[i];
statsString += ':';
statsString += obj.stat(names[i]);
}
} else {
if (obj.stat('audioOutputLevel')) {
statsString += "audioOutputLevel: ";
statsString += obj.stat('audioOutputLevel');
statsString += "<br>";
}
}
return statsString;
}
// Utility to show the value of a field in a span called name+Display
function showValue(name, value) {
$(name + 'Display').innerHTML = value;
}
</script>
</head>
<body>
<h1>Constraints and Statistics</h1>
This page is meant to give some hints on how one can use constraints and statistics in WebRTC applications.
<p>
The form to the left gives constraints you can set on the getUserMedia call.
When you hit "open", it will (re)open the camera with these constraints.
<p>
The left picture is the local preview. The right picture is the picture
after being passed through the PeerConnection (locally).
<p>
Underneath the picture you will see a running display of how many Kbits/sec
the video feed uses for transmission.
<hr>
<table>
<tr>
<td align="top">
<h2>getUserMedia constraints</h2>
<table>
<tr><td><td>Min<td>Max
<tr><td>Horizontal
<td><input type="range" id="minwidth" min="0" max="1280" value="300"
onchange="showValue(this.id, this.value)">
<td><input type="range" id="maxwidth" min="0" max="1280" value="640"
onchange="showValue(this.id, this.value)">
<td><span id="minwidthDisplay">300</span>-<span id="maxwidthDisplay">640</span>
<tr><td>Vertical
<td><input type="range" id="minheight" min="0" max="1280" value="200"
onchange="showValue(this.id, this.value)">
<td><input type="range" id="maxheight" min="0" max="1280" value="480"
onchange="showValue(this.id, this.value)">
<td><span id="minheightDisplay">200</span>-<span id="maxheightDisplay">480</span>
<tr><td>
FrameRate
<td colspan=2><input type="range" id="frameRate" min="0" max="60" value="30"
onchange="showValue(this.id, this.value)">
<td><span id="frameRateDisplay">30</span>
</table>
<input type="submit" name="capture" value="Capture!" onclick="openCamera()">
</td>
<td align="top">
<h2>addStream constraints</h2>
Maximum bitrate
<input type="range" id="bandwidth" min="0" max="2000" value="1000"
onchange="showValue(this.id, this.value)">
<span id="bandwidthDisplay">1000</span>
<br>
<input type="submit" name="connect" value="Connect!" onclick="connect()">
</td>
</tr>
<tr>
<td>
<video id="local-video" autoplay width=400 muted="true"></video>
</td>
<td>
<video id="remote-video" autoplay width=400></video>
</td>
<tr>
<td><span id="local-video-stats"></span>
<td><span id="remote-video-stats"></span>
<br>
<span id="bitrate">Bitrate unknown</span>
</td>
</tr>
<tr>
<td><pre><span id="cameraConstraints"></span></pre>
<td><pre><span id="addStreamConstraints"></span></pre>
</table>
<h2>Statistics report display</h2>
<table>
<tr>
<th>Sender side<th>Receiver side
<tr>
<td align="top"><div id="senderstats">Stats will appear here.</div>
<td align="top"><div id="receiverstats">Stats will appear here.</div>
</table>
</body>
</html>