blob: 6134a3a3eae2ccc225243f44ff79f279a3650aa9 [file] [log] [blame]
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +00001<html>
2<head>
3<title>Constraints and Statistics</title>
hta@webrtc.orgdb3f4272013-03-05 15:23:40 +00004<!-- Load the polyfill to switch-hit between Chrome and Firefox -->
5<script src="../../base/adapter.js"></script>
6
hta@webrtc.org37bf5842013-04-06 10:05:55 +00007<style type="text/css">
8td { vertical-align: top; }
9</style>
10
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +000011<script>
12var mystream;
13var pc1;
14var pc2;
hta@webrtc.org3ed599a2013-03-22 08:48:16 +000015var bytesPrev = 0;
16var timestampPrev = 0;
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +000017
18$ = function(id) {
19 return document.getElementById(id);
20}
21
22function log(txt) {
23 console.log(txt);
24}
25
26function openCamera() {
27 if (mystream) {
28 mystream.stop();
29 }
30 navigator.webkitGetUserMedia(cameraConstraints(), gotStream, function() {
31 log("GetUserMedia failed");
32 });
33}
34
35function gotStream(stream) {
36 log("GetUserMedia succeeded");
37 mystream = stream;
38 $("local-video").src = webkitURL.createObjectURL(stream);
39}
40
41function cameraConstraints() {
42 var constraints = {};
43 constraints.audio = true;
44 constraints.video = { mandatory: {}, optional: [] };
45 if ($("minwidth").value != "0") {
46 constraints.video.mandatory.minWidth = $("minwidth").value;
47 }
48 if ($("maxwidth").value != "0") {
49 constraints.video.mandatory.maxWidth = $("maxwidth").value;
50 }
51 if ($("minheight").value != "0") {
52 constraints.video.mandatory.minHeight = $("minheight").value;
53 }
54 if ($("maxheight").value != "0") {
55 constraints.video.mandatory.maxHeight = $("maxheight").value;
56 }
57 if ($("frameRate").value != "0") {
58 constraints.video.mandatory.minFrameRate = $("frameRate").value;
59 }
60 log('Camera constraints are ' + JSON.stringify(constraints));
61 $("cameraConstraints").innerHTML = JSON.stringify(constraints, null, ' ');
62 return constraints;
63}
64
65function streamConstraints() {
66 var constraints = { mandatory: {}, optional: [] };
67 if ($("bandwidth").value != "0") {
68 constraints.optional[0] = { 'bandwidth' : $('bandwidth').value };
69 }
70 log('Constraints are ' + JSON.stringify(constraints));
71 $("addStreamConstraints").innerHTML = JSON.stringify(constraints, null, ' ');
72 return constraints;
73}
74
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +000075function connect() {
76 pc1 = new webkitRTCPeerConnection(null);
77 pc2 = new webkitRTCPeerConnection(null);
78 pc1.addStream(mystream, streamConstraints());
79 log('PC1 creating offer');
80 pc1.onnegotiationeeded = function() {
81 log('Negotiation needed - PC1');
82 }
83 pc2.onnegotiationeeded = function() {
84 log('Negotiation needed - PC2');
85 }
86 pc1.onicecandidate = function(e) {
87 log('Candidate PC1');
88 if (e.candidate) {
89 pc2.addIceCandidate(new RTCIceCandidate(e.candidate));
90 }
91 }
92 pc2.onicecandidate = function(e) {
93 log('Candidate PC2');
94 if (e.candidate) {
95 pc1.addIceCandidate(new RTCIceCandidate(e.candidate));
96 }
97 }
98 pc2.onaddstream = function(e) {
99 log('PC2 got stream');
100 $('remote-video').src = webkitURL.createObjectURL(e.stream);
101 log('Remote video is ' + $('remote-video').src);
102 }
103 pc1.createOffer(function(desc) {
104 log('PC1 offering');
105 pc1.setLocalDescription(desc);
106 pc2.setRemoteDescription(desc);
107 pc2.createAnswer(function(desc2) {
108 log('PC2 answering');
109 pc2.setLocalDescription(desc2);
110 pc1.setRemoteDescription(desc2);
111 });
112 });
113}
114
hta@webrtc.orgcc394842013-08-21 17:00:54 +0000115// Augumentation of stats entries with utility functions.
116// The augumented entry does what the stats entry does, but adds
117// utility functions.
118function AugumentedStatsResponse(response) {
119 this.response = response;
120 this.addressPairMap = [];
121}
122
123AugumentedStatsResponse.prototype.collectAddressPairs = function(componentId) {
124 if (!this.addressPairMap[componentId]) {
125 this.addressPairMap[componentId] = [];
126 for (var i = 0; i < this.response.result().length; ++i) {
127 var res = this.response.result()[i];
128 if (res.type == 'googCandidatePair' &&
129 res.stat('googChannelId') == componentId) {
130 this.addressPairMap[componentId].push(res);
131 }
132 }
133 }
134 return this.addressPairMap[componentId];
135}
136
137AugumentedStatsResponse.prototype.result = function() {
138 return this.response.result();
139}
140
141// The indexed getter isn't easy to prototype.
142AugumentedStatsResponse.prototype.get = function(key) {
143 return this.response[key];
144}
145
146
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000147// Display statistics
148var statCollector = setInterval(function() {
149 var display = function(str) {
150 $('bitrate').innerHTML = str;
151 }
152
153 display("No stream");
hta@webrtc.orgdb3f4272013-03-05 15:23:40 +0000154 if (pc2 && pc2.getRemoteStreams()[0]) {
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000155 if (pc2.getStats) {
hta@webrtc.orgcc394842013-08-21 17:00:54 +0000156 pc2.getStats(function(rawStats) {
157 stats = new AugumentedStatsResponse(rawStats);
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000158 var statsString = '';
159 var results = stats.result();
hta@webrtc.orgcc394842013-08-21 17:00:54 +0000160 var videoFlowInfo = 'No bitrate stats';
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000161 for (var i = 0; i < results.length; ++i) {
162 var res = results[i];
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000163 statsString += '<h3>Report ';
164 statsString += i;
165 statsString += '</h3>';
hta@webrtc.orgecfd3282013-03-19 08:45:47 +0000166 if (!res.local || res.local === res) {
167 statsString += dumpStats(res);
hta@webrtc.org3ed599a2013-03-22 08:48:16 +0000168 // The bandwidth info for video is in a type ssrc stats record
169 // with googFrameHeightReceived defined.
170 // Should check for mediatype = video, but this is not
171 // implemented yet.
172 if (res.type == 'ssrc' && res.stat('googFrameHeightReceived')) {
hta@webrtc.orgcc394842013-08-21 17:00:54 +0000173 // This is the video flow.
174 videoFlowInfo = extractVideoFlowInfo(res, stats);
hta@webrtc.org3ed599a2013-03-22 08:48:16 +0000175 }
hta@webrtc.orgecfd3282013-03-19 08:45:47 +0000176 } else {
177 // Pre-227.0.1445 (188719) browser
178 if (res.local) {
179 statsString += "<p>Local ";
180 statsString += dumpStats(res.local);
181 }
182 if (res.remote) {
183 statsString += "<p>Remote ";
184 statsString += dumpStats(res.remote);
185 }
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000186 }
187 }
hta@webrtc.org37bf5842013-04-06 10:05:55 +0000188 $('receiverstats').innerHTML = statsString;
hta@webrtc.orgcc394842013-08-21 17:00:54 +0000189 display(videoFlowInfo);
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000190 });
hta@webrtc.org37bf5842013-04-06 10:05:55 +0000191 pc1.getStats(function(stats) {
192 var statsString = '';
193 var results = stats.result();
194 for (var i = 0; i < results.length; ++i) {
195 var res = results[i];
196 statsString += '<h3>Report ';
197 statsString += i;
198 statsString += '</h3>';
199 if (!res.local || res.local === res) {
200 statsString += dumpStats(res);
201 }
202 }
203 $('senderstats').innerHTML = statsString;
204 });
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000205 } else {
206 display('No stats function. Use at least Chrome 24.0.1285');
207 }
208 } else {
209 log('Not connected yet');
210 }
211 // Collect some stats from the video tags.
212 local_video = $('local-video');
213 if (local_video) {
214 $('local-video-stats').innerHTML = local_video.videoWidth +
215 'x' + local_video.videoHeight;
216 }
217 remote_video = $('remote-video');
218 if (remote_video) {
219 $('remote-video-stats').innerHTML = remote_video.videoWidth +
220 'x' + remote_video.videoHeight;
221 }
222}, 1000);
223
hta@webrtc.orgcc394842013-08-21 17:00:54 +0000224function extractVideoFlowInfo(res, allStats) {
225 var description = '';
226 var bytesNow = res.stat('bytesReceived');
227 if (timestampPrev > 0) {
228 var bitRate = Math.round((bytesNow - bytesPrev) * 8 /
229 (res.timestamp - timestampPrev));
230 description = bitRate + ' kbits/sec';
231 }
232 timestampPrev = res.timestamp;
233 bytesPrev = bytesNow;
234 if (res.stat('transportId')) {
235 component = allStats.get(res.stat('transportId'));
236 if (component) {
237 addresses = allStats.collectAddressPairs(component.id);
238 if (addresses.length > 0) {
239 description += ' from IP ';
240 description += addresses[0].stat('googRemoteAddress');
241 } else {
242 description += ' no address';
243 }
244 } else {
245 description += ' No component stats';
246 }
247 } else {
248 description += ' No component ID';
249 }
250 return description;
251}
252
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000253// Dumping a stats variable as a string.
254// might be named toString?
255function dumpStats(obj) {
256 var statsString = 'Timestamp:';
257 statsString += obj.timestamp;
hta@webrtc.org3ed599a2013-03-22 08:48:16 +0000258 if (obj.id) {
hta@webrtc.org37bf5842013-04-06 10:05:55 +0000259 statsString += "<br>id ";
hta@webrtc.org3ed599a2013-03-22 08:48:16 +0000260 statsString += obj.id;
261 }
262 if (obj.type) {
263 statsString += " type ";
264 statsString += obj.type;
265 }
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000266 if (obj.names) {
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000267 names = obj.names();
268 for (var i = 0; i < names.length; ++i) {
269 statsString += '<br>';
270 statsString += names[i];
271 statsString += ':';
272 statsString += obj.stat(names[i]);
273 }
274 } else {
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000275 if (obj.stat('audioOutputLevel')) {
276 statsString += "audioOutputLevel: ";
277 statsString += obj.stat('audioOutputLevel');
278 statsString += "<br>";
279 }
280 }
281 return statsString;
282}
283
284
285// Utility to show the value of a field in a span called name+Display
286function showValue(name, value) {
287 $(name + 'Display').innerHTML = value;
288}
289</script>
290</head>
291<body>
292<h1>Constraints and Statistics</h1>
293This page is meant to give some hints on how one can use constraints and statistics in WebRTC applications.
294<p>
295The form to the left gives constraints you can set on the getUserMedia call.
296When you hit "open", it will (re)open the camera with these constraints.
297<p>
298The left picture is the local preview. The right picture is the picture
299after being passed through the PeerConnection (locally).
300<p>
301Underneath the picture you will see a running display of how many Kbits/sec
302the video feed uses for transmission.
303<hr>
304<table>
305<tr>
306<td align="top">
307<h2>getUserMedia constraints</h2>
308<table>
309<tr><td><td>Min<td>Max
310<tr><td>Horizontal
311<td><input type="range" id="minwidth" min="0" max="1280" value="300"
312 onchange="showValue(this.id, this.value)">
313<td><input type="range" id="maxwidth" min="0" max="1280" value="640"
314 onchange="showValue(this.id, this.value)">
315<td><span id="minwidthDisplay">300</span>-<span id="maxwidthDisplay">640</span>
316<tr><td>Vertical
317<td><input type="range" id="minheight" min="0" max="1280" value="200"
318 onchange="showValue(this.id, this.value)">
319<td><input type="range" id="maxheight" min="0" max="1280" value="480"
320 onchange="showValue(this.id, this.value)">
321<td><span id="minheightDisplay">200</span>-<span id="maxheightDisplay">480</span>
322<tr><td>
323FrameRate
324<td colspan=2><input type="range" id="frameRate" min="0" max="60" value="30"
325 onchange="showValue(this.id, this.value)">
326<td><span id="frameRateDisplay">30</span>
327</table>
328<input type="submit" name="capture" value="Capture!" onclick="openCamera()">
329</td>
330<td align="top">
331<h2>addStream constraints</h2>
332Maximum bitrate
333<input type="range" id="bandwidth" min="0" max="2000" value="1000"
334 onchange="showValue(this.id, this.value)">
335<span id="bandwidthDisplay">1000</span>
336<br>
337<input type="submit" name="connect" value="Connect!" onclick="connect()">
338</td>
339</tr>
340<tr>
341<td>
vikasmarwaha@webrtc.orgda0f7082013-03-11 16:58:07 +0000342<video id="local-video" autoplay width=400 muted="true"></video>
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000343</td>
344<td>
345<video id="remote-video" autoplay width=400></video>
346</td>
347<tr>
348<td><span id="local-video-stats"></span>
349<td><span id="remote-video-stats"></span>
350<br>
351<span id="bitrate">Bitrate unknown</span>
352</td>
353</tr>
354<tr>
355<td><pre><span id="cameraConstraints"></span></pre>
356<td><pre><span id="addStreamConstraints"></span></pre>
357</table>
358<h2>Statistics report display</h2>
hta@webrtc.org37bf5842013-04-06 10:05:55 +0000359<table>
360<tr>
361<th>Sender side<th>Receiver side
362<tr>
363<td align="top"><div id="senderstats">Stats will appear here.</div>
364<td align="top"><div id="receiverstats">Stats will appear here.</div>
365</table>
vikasmarwaha@webrtc.org98fce152013-02-27 23:22:10 +0000366</body>
367</html>