blob: 83088d96b60f08833e600c46db58152fa5338c0e [file] [log] [blame]
andresp@webrtc.org44eb87e2014-03-17 14:23:22 +00001<!DOCTYPE html>
2<!--
3 This page was created to help debug and study webrtc issues such as
4 bandwidth estimation problems. It allows one to easily launch a test
5 case that establishs a connection between 2 peer connections
6-->
7<html>
8<head>
9<title>Loopback test</title>
10
11<!-- In order to plot graphs, this tools uses google visualization API which is
12 loaded via goog.load provided by google api. -->
13<script src="//www.google.com/jsapi"></script>
14
15<!-- This file is included to allow loopback_test.js instantiate a
16 RTCPeerConnection on a browser and version agnostic way. -->
17<script src="adapter.js"></script>
18
19<!-- Provides class StatTracker used by loopback_test.js to keep track of
20 RTCPeerConnection stats -->
21<script src="stat_tracker.js"></script>
22
23<!-- Provides LoopbackTest class which has the core logic for the test itself.
24 Such as: create 2 peer connections, establish a call, filter turn
25 candidates, constraint video bitrate etc.
26 -->
27<script src="loopback_test.js"></script>
28
29<style>
30#chart {
31 height: 400px;
32}
33
34#control-range {
35 height: 100px;
36}
37</style>
38</head>
39<body>
40<div id="test-launcher">
41 <p>Duration (s): <input id="duration" type="text"></p>
42 <p>Max video bitrate (kbps): <input id="max-video-bitrate" type="text"></p>
andresp@webrtc.org0273fa92014-04-10 09:40:16 +000043 <p>Peer connection constraints: <input id="pc-constraints" type="text"></p>
andresp@webrtc.org44eb87e2014-03-17 14:23:22 +000044 <p>Force TURN: <input id="force-turn" type="checkbox" checked></p>
45 <p><input id="launcher-button" type="button" value="Run test">
46 <div id="test-status" style="display:none"></div>
47 <div id="dashboard">
48 <div id="control-category"></div>
49 <div id="chart"></div>
50 <div id="control-range"></div>
51 </div>
52</div>
53<script>
54google.load('visualization', '1.0', {'packages':['controls']});
55
56var durationInput = document.getElementById('duration');
57var maxVideoBitrateInput = document.getElementById('max-video-bitrate');
58var forceTurnInput = document.getElementById('force-turn');
59var launcherButton = document.getElementById('launcher-button');
60var autoModeInput = document.createElement('input');
61var testStatus = document.getElementById('test-status');
andresp@webrtc.org0273fa92014-04-10 09:40:16 +000062var pcConstraintsInput = document.getElementById('pc-constraints');
andresp@webrtc.org44eb87e2014-03-17 14:23:22 +000063
64launcherButton.onclick = start;
65
66// Load parameters from the url if present. This allows one to link to
67// a specific test configuration and is used to automatically pass parameters
68// for scripts such as record-test.sh
69function getURLParameter(name, default_value) {
70 var search =
71 RegExp('(^\\?|&)' + name + '=' + '(.+?)(&|$)').exec(location.search);
72 if (search)
73 return decodeURI(search[2]);
74 else
75 return default_value;
76}
77
78durationInput.value = getURLParameter('duration', 10);
79maxVideoBitrateInput.value = getURLParameter('max-video-bitrate', 2000);
80forceTurnInput.checked = (getURLParameter('force-turn', 'true') === 'true');
81autoModeInput.checked = (getURLParameter('auto-mode', 'false') === 'true');
andresp@webrtc.org0273fa92014-04-10 09:40:16 +000082pcConstraintsInput.value = getURLParameter('pc-constraints', '');
andresp@webrtc.org44eb87e2014-03-17 14:23:22 +000083
84if (autoModeInput.checked) start();
85
86function start() {
87 var durationMs = parseInt(durationInput.value) * 1000;
88 var maxVideoBitrateKbps = parseInt(maxVideoBitrateInput.value);
89 var forceTurn = forceTurnInput.checked;
90 var autoClose = autoModeInput.checked;
andresp@webrtc.org0273fa92014-04-10 09:40:16 +000091 var pcConstraints = JSON.parse(pcConstraintsInput.value);
andresp@webrtc.org44eb87e2014-03-17 14:23:22 +000092
93 var updateStatusInterval;
94 var testFinished = false;
95 function updateStatus() {
96 if (testFinished) {
97 testStatus.innerHTML = 'Test finished';
98 if (updateStatusInterval) {
99 clearInterval(updateStatusInterval);
100 updateStatusInterval = null;
101 }
102 } else {
103 if (!updateStatusInterval) {
104 updateStatusInterval = setInterval(updateStatus, 1000);
105 testStatus.innerHTML = 'Running';
106 }
107 testStatus.innerHTML += '.';
108 }
109 }
110
111 if (!(isFinite(maxVideoBitrateKbps) && maxVideoBitrateKbps > 0)) {
112 // TODO(andresp): Get a better way to show errors than alert.
113 alert("Invalid max video bitrate");
114 return;
115 }
116
117 if (!(isFinite(durationMs) && durationMs > 0)) {
118 alert("Invalid duration");
119 return;
120 }
121
122 durationInput.disabled = true;
123 forceTurnInput.disabled = true;
124 maxVideoBitrateInput.disabled = true;
125 launcherButton.style.display = 'none';
126 testStatus.style.display = 'block';
127
128 getUserMedia({audio:true, video:true},
129 gotStream, function() {});
130
131 function gotStream(stream) {
132 updateStatus();
andresp@webrtc.org0273fa92014-04-10 09:40:16 +0000133 var test = new LoopbackTest(stream, durationMs,
134 forceTurn,
135 pcConstraints,
andresp@webrtc.org44eb87e2014-03-17 14:23:22 +0000136 maxVideoBitrateKbps);
137 test.run(onTestFinished.bind(test));
138 }
139
140 function onTestFinished() {
141 testFinished = true;
142 updateStatus();
143 if (autoClose) {
144 window.close();
145 } else {
146 plotStats(this.getResults());
147 }
148 }
149}
150
151function plotStats(data) {
152 var dashboard = new google.visualization.Dashboard(
153 document.getElementById('dashboard'));
154
155 var chart = new google.visualization.ChartWrapper({
156 'containerId': 'chart',
157 'chartType': 'LineChart',
158 'options': { 'pointSize': 0, 'lineWidth': 1, 'interpolateNulls': true },
159 });
160
161 var rangeFilter = new google.visualization.ControlWrapper({
162 'controlType': 'ChartRangeFilter',
163 'containerId': 'control-range',
164 'options': {
165 'filterColumnIndex': 0,
166 'ui': {
167 'chartType': 'ScatterChart',
168 'chartOptions': {
169 'hAxis': {'baselineColor': 'none'}
170 },
171 'chartView': {
172 'columns': [0, 1]
173 },
174 'minRangeSize': 1000 // 1 second
175 }
176 },
177 });
178
179 // Create a table with the columns of the dataset.
180 var columnsTable = new google.visualization.DataTable();
181 columnsTable.addColumn('number', 'columnIndex');
182 columnsTable.addColumn('string', 'columnLabel');
183 var initState = {selectedValues: []};
184 for (var i = 1; i < data.getNumberOfColumns(); i++) {
185 columnsTable.addRow([i, data.getColumnLabel(i)]);
186 initState.selectedValues.push(data.getColumnLabel(i));
187 }
188
189 var columnFilter = new google.visualization.ControlWrapper({
190 controlType: 'CategoryFilter',
191 containerId: 'control-category',
192 dataTable: columnsTable,
193 options: {
194 filterColumnLabel: 'columnLabel',
195 ui: {
196 label: '',
197 allowNone: false,
198 selectedValuesLayout: 'aside'
199 }
200 },
201 state: initState
202 });
203 google.visualization.events.addListener(columnFilter, 'statechange',
204 function () {
205 var state = columnFilter.getState();
206 var row;
207 var columnIndices = [0];
208 for (var i = 0; i < state.selectedValues.length; i++) {
209 row = columnsTable.getFilteredRows([{
210 column: 1,
211 value: state.selectedValues[i]}])[0];
212 columnIndices.push(columnsTable.getValue(row, 0));
213 }
214 // Sort the indices into their original order
215 columnIndices.sort(function (a, b) { return (a - b); });
216 chart.setView({columns: columnIndices});
217 chart.draw();
218 });
219
220 columnFilter.draw();
221 dashboard.bind([rangeFilter], [chart]);
222 dashboard.draw(data);
223}
224</script>
225</body>
226</html>