| <!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> |