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