| <!DOCTYPE html> | |
| <!-- | |
| This page was created to help debug and study webrtc issues such as | |
| bandwidth estimation problems. It allows one to easily launch a test | |
| case that establishs a connection between 2 peer connections | |
| --> | |
| <html> | |
| <head> | |
| <title>Loopback test</title> | |
| <!-- In order to plot graphs, this tools uses google visualization API which is | |
| loaded via goog.load provided by google api. --> | |
| <script src="//www.google.com/jsapi"></script> | |
| <!-- This file is included to allow loopback_test.js instantiate a | |
| RTCPeerConnection on a browser and version agnostic way. --> | |
| <script src="adapter.js"></script> | |
| <!-- Provides class StatTracker used by loopback_test.js to keep track of | |
| RTCPeerConnection stats --> | |
| <script src="stat_tracker.js"></script> | |
| <!-- Provides LoopbackTest class which has the core logic for the test itself. | |
| Such as: create 2 peer connections, establish a call, filter turn | |
| candidates, constraint video bitrate etc. | |
| --> | |
| <script src="loopback_test.js"></script> | |
| <style> | |
| #chart { | |
| height: 400px; | |
| } | |
| #control-range { | |
| height: 100px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="test-launcher"> | |
| <p>Duration (s): <input id="duration" type="text"></p> | |
| <p>Max video bitrate (kbps): <input id="max-video-bitrate" type="text"></p> | |
| <p>Peer connection constraints: <input id="pc-constraints" type="text"></p> | |
| <p>Force TURN: <input id="force-turn" type="checkbox" checked></p> | |
| <p><input id="launcher-button" type="button" value="Run test"> | |
| <div id="test-status" style="display:none"></div> | |
| <div id="dashboard"> | |
| <div id="control-category"></div> | |
| <div id="chart"></div> | |
| <div id="control-range"></div> | |
| </div> | |
| </div> | |
| <script> | |
| google.load('visualization', '1.0', {'packages':['controls']}); | |
| var durationInput = document.getElementById('duration'); | |
| var maxVideoBitrateInput = document.getElementById('max-video-bitrate'); | |
| var forceTurnInput = document.getElementById('force-turn'); | |
| var launcherButton = document.getElementById('launcher-button'); | |
| var autoModeInput = document.createElement('input'); | |
| var testStatus = document.getElementById('test-status'); | |
| var pcConstraintsInput = document.getElementById('pc-constraints'); | |
| launcherButton.onclick = start; | |
| // Load parameters from the url if present. This allows one to link to | |
| // a specific test configuration and is used to automatically pass parameters | |
| // for scripts such as record-test.sh | |
| function getURLParameter(name, default_value) { | |
| var search = | |
| RegExp('(^\\?|&)' + name + '=' + '(.+?)(&|$)').exec(location.search); | |
| if (search) | |
| return decodeURI(search[2]); | |
| else | |
| return default_value; | |
| } | |
| durationInput.value = getURLParameter('duration', 10); | |
| maxVideoBitrateInput.value = getURLParameter('max-video-bitrate', 2000); | |
| forceTurnInput.checked = (getURLParameter('force-turn', 'true') === 'true'); | |
| autoModeInput.checked = (getURLParameter('auto-mode', 'false') === 'true'); | |
| pcConstraintsInput.value = getURLParameter('pc-constraints', ''); | |
| if (autoModeInput.checked) start(); | |
| function start() { | |
| var durationMs = parseInt(durationInput.value) * 1000; | |
| var maxVideoBitrateKbps = parseInt(maxVideoBitrateInput.value); | |
| var forceTurn = forceTurnInput.checked; | |
| var autoClose = autoModeInput.checked; | |
| var pcConstraints = pcConstraintsInput.value == "" ? | |
| null : JSON.parse(pcConstraintsInput.value); | |
| var updateStatusInterval; | |
| var testFinished = false; | |
| function updateStatus() { | |
| if (testFinished) { | |
| testStatus.innerHTML = 'Test finished'; | |
| if (updateStatusInterval) { | |
| clearInterval(updateStatusInterval); | |
| updateStatusInterval = null; | |
| } | |
| } else { | |
| if (!updateStatusInterval) { | |
| updateStatusInterval = setInterval(updateStatus, 1000); | |
| testStatus.innerHTML = 'Running'; | |
| } | |
| testStatus.innerHTML += '.'; | |
| } | |
| } | |
| if (!(isFinite(maxVideoBitrateKbps) && maxVideoBitrateKbps > 0)) { | |
| // TODO(andresp): Get a better way to show errors than alert. | |
| alert("Invalid max video bitrate"); | |
| return; | |
| } | |
| if (!(isFinite(durationMs) && durationMs > 0)) { | |
| alert("Invalid duration"); | |
| return; | |
| } | |
| durationInput.disabled = true; | |
| forceTurnInput.disabled = true; | |
| maxVideoBitrateInput.disabled = true; | |
| launcherButton.style.display = 'none'; | |
| testStatus.style.display = 'block'; | |
| getUserMedia({audio:true, video:true}, | |
| gotStream, function() {}); | |
| function gotStream(stream) { | |
| updateStatus(); | |
| var test = new LoopbackTest(stream, durationMs, | |
| forceTurn, | |
| pcConstraints, | |
| maxVideoBitrateKbps); | |
| test.run(onTestFinished.bind(test)); | |
| } | |
| function onTestFinished() { | |
| testFinished = true; | |
| updateStatus(); | |
| if (autoClose) { | |
| window.close(); | |
| } else { | |
| plotStats(this.getResults()); | |
| } | |
| } | |
| } | |
| function plotStats(data) { | |
| var dashboard = new google.visualization.Dashboard( | |
| document.getElementById('dashboard')); | |
| var chart = new google.visualization.ChartWrapper({ | |
| 'containerId': 'chart', | |
| 'chartType': 'LineChart', | |
| 'options': { 'pointSize': 0, 'lineWidth': 1, 'interpolateNulls': true }, | |
| }); | |
| var rangeFilter = new google.visualization.ControlWrapper({ | |
| 'controlType': 'ChartRangeFilter', | |
| 'containerId': 'control-range', | |
| 'options': { | |
| 'filterColumnIndex': 0, | |
| 'ui': { | |
| 'chartType': 'ScatterChart', | |
| 'chartOptions': { | |
| 'hAxis': {'baselineColor': 'none'} | |
| }, | |
| 'chartView': { | |
| 'columns': [0, 1] | |
| }, | |
| 'minRangeSize': 1000 // 1 second | |
| } | |
| }, | |
| }); | |
| // Create a table with the columns of the dataset. | |
| var columnsTable = new google.visualization.DataTable(); | |
| columnsTable.addColumn('number', 'columnIndex'); | |
| columnsTable.addColumn('string', 'columnLabel'); | |
| var initState = {selectedValues: []}; | |
| for (var i = 1; i < data.getNumberOfColumns(); i++) { | |
| columnsTable.addRow([i, data.getColumnLabel(i)]); | |
| initState.selectedValues.push(data.getColumnLabel(i)); | |
| } | |
| var columnFilter = new google.visualization.ControlWrapper({ | |
| controlType: 'CategoryFilter', | |
| containerId: 'control-category', | |
| dataTable: columnsTable, | |
| options: { | |
| filterColumnLabel: 'columnLabel', | |
| ui: { | |
| label: '', | |
| allowNone: false, | |
| selectedValuesLayout: 'aside' | |
| } | |
| }, | |
| state: initState | |
| }); | |
| google.visualization.events.addListener(columnFilter, 'statechange', | |
| function () { | |
| var state = columnFilter.getState(); | |
| var row; | |
| var columnIndices = [0]; | |
| for (var i = 0; i < state.selectedValues.length; i++) { | |
| row = columnsTable.getFilteredRows([{ | |
| column: 1, | |
| value: state.selectedValues[i]}])[0]; | |
| columnIndices.push(columnsTable.getValue(row, 0)); | |
| } | |
| // Sort the indices into their original order | |
| columnIndices.sort(function (a, b) { return (a - b); }); | |
| chart.setView({columns: columnIndices}); | |
| chart.draw(); | |
| }); | |
| columnFilter.draw(); | |
| dashboard.bind([rangeFilter], [chart]); | |
| dashboard.draw(data); | |
| } | |
| </script> | |
| </body> | |
| </html> |