| OLD | NEW |
| (Empty) |
| 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> | |
| 54 google.load('visualization', '1.0', {'packages':['controls']}); | |
| 55 | |
| 56 var durationInput = document.getElementById('duration'); | |
| 57 var maxVideoBitrateInput = document.getElementById('max-video-bitrate'); | |
| 58 var forceTurnInput = document.getElementById('force-turn'); | |
| 59 var launcherButton = document.getElementById('launcher-button'); | |
| 60 var autoModeInput = document.createElement('input'); | |
| 61 var testStatus = document.getElementById('test-status'); | |
| 62 var pcConstraintsInput = document.getElementById('pc-constraints'); | |
| 63 | |
| 64 launcherButton.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 | |
| 69 function 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 | |
| 78 durationInput.value = getURLParameter('duration', 10); | |
| 79 maxVideoBitrateInput.value = getURLParameter('max-video-bitrate', 2000); | |
| 80 forceTurnInput.checked = (getURLParameter('force-turn', 'true') === 'true'); | |
| 81 autoModeInput.checked = (getURLParameter('auto-mode', 'false') === 'true'); | |
| 82 pcConstraintsInput.value = getURLParameter('pc-constraints', ''); | |
| 83 | |
| 84 if (autoModeInput.checked) start(); | |
| 85 | |
| 86 function 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 | |
| 152 function 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> | |
| OLD | NEW |