1<!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> 43 <p>Peer connection constraints: <input id="pc-constraints" type="text"></p> 44 <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'); 62var pcConstraintsInput = document.getElementById('pc-constraints'); 63 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'); 82pcConstraintsInput.value = getURLParameter('pc-constraints', ''); 83 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; 91 var pcConstraints = pcConstraintsInput.value == "" ? 92 null : JSON.parse(pcConstraintsInput.value); 93 94 var updateStatusInterval; 95 var testFinished = false; 96 function updateStatus() { 97 if (testFinished) { 98 testStatus.innerHTML = 'Test finished'; 99 if (updateStatusInterval) { 100 clearInterval(updateStatusInterval); 101 updateStatusInterval = null; 102 } 103 } else { 104 if (!updateStatusInterval) { 105 updateStatusInterval = setInterval(updateStatus, 1000); 106 testStatus.innerHTML = 'Running'; 107 } 108 testStatus.innerHTML += '.'; 109 } 110 } 111 112 if (!(isFinite(maxVideoBitrateKbps) && maxVideoBitrateKbps > 0)) { 113 // TODO(andresp): Get a better way to show errors than alert. 114 alert("Invalid max video bitrate"); 115 return; 116 } 117 118 if (!(isFinite(durationMs) && durationMs > 0)) { 119 alert("Invalid duration"); 120 return; 121 } 122 123 durationInput.disabled = true; 124 forceTurnInput.disabled = true; 125 maxVideoBitrateInput.disabled = true; 126 launcherButton.style.display = 'none'; 127 testStatus.style.display = 'block'; 128 129 getUserMedia({audio:true, video:true}, 130 gotStream, function() {}); 131 132 function gotStream(stream) { 133 updateStatus(); 134 var test = new LoopbackTest(stream, durationMs, 135 forceTurn, 136 pcConstraints, 137 maxVideoBitrateKbps); 138 test.run(onTestFinished.bind(test)); 139 } 140 141 function onTestFinished() { 142 testFinished = true; 143 updateStatus(); 144 if (autoClose) { 145 window.close(); 146 } else { 147 plotStats(this.getResults()); 148 } 149 } 150} 151 152function plotStats(data) { 153 var dashboard = new google.visualization.Dashboard( 154 document.getElementById('dashboard')); 155 156 var chart = new google.visualization.ChartWrapper({ 157 'containerId': 'chart', 158 'chartType': 'LineChart', 159 'options': { 'pointSize': 0, 'lineWidth': 1, 'interpolateNulls': true }, 160 }); 161 162 var rangeFilter = new google.visualization.ControlWrapper({ 163 'controlType': 'ChartRangeFilter', 164 'containerId': 'control-range', 165 'options': { 166 'filterColumnIndex': 0, 167 'ui': { 168 'chartType': 'ScatterChart', 169 'chartOptions': { 170 'hAxis': {'baselineColor': 'none'} 171 }, 172 'chartView': { 173 'columns': [0, 1] 174 }, 175 'minRangeSize': 1000 // 1 second 176 } 177 }, 178 }); 179 180 // Create a table with the columns of the dataset. 181 var columnsTable = new google.visualization.DataTable(); 182 columnsTable.addColumn('number', 'columnIndex'); 183 columnsTable.addColumn('string', 'columnLabel'); 184 var initState = {selectedValues: []}; 185 for (var i = 1; i < data.getNumberOfColumns(); i++) { 186 columnsTable.addRow([i, data.getColumnLabel(i)]); 187 initState.selectedValues.push(data.getColumnLabel(i)); 188 } 189 190 var columnFilter = new google.visualization.ControlWrapper({ 191 controlType: 'CategoryFilter', 192 containerId: 'control-category', 193 dataTable: columnsTable, 194 options: { 195 filterColumnLabel: 'columnLabel', 196 ui: { 197 label: '', 198 allowNone: false, 199 selectedValuesLayout: 'aside' 200 } 201 }, 202 state: initState 203 }); 204 google.visualization.events.addListener(columnFilter, 'statechange', 205 function () { 206 var state = columnFilter.getState(); 207 var row; 208 var columnIndices = [0]; 209 for (var i = 0; i < state.selectedValues.length; i++) { 210 row = columnsTable.getFilteredRows([{ 211 column: 1, 212 value: state.selectedValues[i]}])[0]; 213 columnIndices.push(columnsTable.getValue(row, 0)); 214 } 215 // Sort the indices into their original order 216 columnIndices.sort(function (a, b) { return (a - b); }); 217 chart.setView({columns: columnIndices}); 218 chart.draw(); 219 }); 220 221 columnFilter.draw(); 222 dashboard.bind([rangeFilter], [chart]); 223 dashboard.draw(data); 224} 225</script> 226</body> 227</html> 228