mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 971110: Prettify about:webrtc r=niko,pkerr
This commit is contained in:
parent
39350f9a37
commit
830662648f
@ -67,7 +67,7 @@ static RedirEntry kRedirMap[] = {
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "networking", "chrome://global/content/aboutNetworking.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "webrtc", "chrome://global/content/aboutWebrtc.xhtml",
|
||||
{ "webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
// about:srcdoc is unresolvable by specification. It is included here
|
||||
// because the security manager would disallow srcdoc iframes otherwise.
|
||||
|
@ -1,440 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Webrtc Internals</title>
|
||||
</head>
|
||||
<script>
|
||||
|
||||
|
||||
function displayLogs(logs) {
|
||||
var logsDiv = document.getElementById('logs');
|
||||
while (logsDiv.lastChild) {
|
||||
logsDiv.removeChild(logsDiv.lastChild);
|
||||
}
|
||||
logsDiv.appendChild(document.createElement('h3'))
|
||||
.appendChild(document.createTextNode('Logging:'));
|
||||
logs.forEach(function(logLine){
|
||||
logsDiv.appendChild(document.createElement('div'))
|
||||
.appendChild(document.createTextNode(logLine));
|
||||
});
|
||||
}
|
||||
|
||||
function candidateTypeString(cand) {
|
||||
if (cand.type == "localcandidate") {
|
||||
if (cand.candidateType == "relayed") {
|
||||
return cand.candidateType + '-' + cand.mozLocalTransport;
|
||||
}
|
||||
}
|
||||
return cand.candidateType;
|
||||
}
|
||||
|
||||
function candidateAddrString(cand) {
|
||||
return cand.ipAddress + ':' +
|
||||
cand.portNumber + '/' +
|
||||
cand.transport + '(' +
|
||||
candidateTypeString(cand) + ')';
|
||||
}
|
||||
|
||||
function buildCandPairTableRow(candPair, localCand, remoteCand) {
|
||||
var row = document.createElement('tr');
|
||||
row.onclick = function() {
|
||||
WebrtcGlobalInformation.getLogging("CAND-PAIR(" + row.id, displayLogs);
|
||||
}
|
||||
|
||||
if (localCand) {
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candidateAddrString(localCand)));
|
||||
} else {
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candPair.localCandidateId));
|
||||
}
|
||||
|
||||
if (remoteCand) {
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candidateAddrString(remoteCand)));
|
||||
} else {
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candPair.remoteCandidateId));
|
||||
}
|
||||
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candPair.state));
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candPair.mozPriority));
|
||||
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candPair.nominated ? '*' : ''));
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candPair.selected ? '*' : ''));
|
||||
return row;
|
||||
}
|
||||
|
||||
function buildCandTableRow(cand) {
|
||||
var row = document.createElement('tr');
|
||||
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(cand.ipAddress + ':' +
|
||||
cand.portNumber + '/' +
|
||||
cand.transport));
|
||||
|
||||
row.appendChild(document.createElement('td'))
|
||||
.appendChild(document.createTextNode(candidateTypeString(cand)));
|
||||
return row;
|
||||
}
|
||||
|
||||
function buildCandPairTableHeader() {
|
||||
var headerRow = document.createElement('tr');
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('Local candidate'));
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('Remote candidate'));
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('ICE State'));
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('Priority'));
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('Nominated'));
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('Selected'));
|
||||
return headerRow;
|
||||
}
|
||||
|
||||
function buildCandTableHeader(isLocal) {
|
||||
var headerRow = document.createElement('tr');
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode(isLocal ?
|
||||
'Local candidate addr' :
|
||||
'Remote candidate addr'));
|
||||
headerRow.appendChild(document.createElement('th'))
|
||||
.appendChild(document.createTextNode('Type'));
|
||||
return headerRow;
|
||||
}
|
||||
|
||||
function buildEmptyCandPairTable() {
|
||||
var candPairTable = document.createElement('table');
|
||||
candPairTable.appendChild(buildCandPairTableHeader());
|
||||
return candPairTable;
|
||||
}
|
||||
|
||||
function buildEmptyCandTable(local) {
|
||||
var candTable = document.createElement('table');
|
||||
candTable.appendChild(buildCandTableHeader(local));
|
||||
return candTable;
|
||||
}
|
||||
|
||||
function round00(num) {
|
||||
return Math.round(num * 100) / 100;
|
||||
}
|
||||
|
||||
function dumpAvStat(stat) {
|
||||
var div = document.createElement('div');
|
||||
var statsString = "";
|
||||
if (stat.mozAvSyncDelay !== undefined) {
|
||||
statsString += "A/V sync: " + stat.mozAvSyncDelay + " ms ";
|
||||
}
|
||||
if (stat.mozJitterBufferDelay !== undefined) {
|
||||
statsString += "Jitter-buffer delay: " + stat.mozJitterBufferDelay + " ms";
|
||||
}
|
||||
div.appendChild(document.createTextNode(statsString));
|
||||
return div;
|
||||
}
|
||||
|
||||
function dumpRtpStat(stat, label) {
|
||||
var div = document.createElement('div');
|
||||
var statsString = " " + label + new Date(stat.timestamp).toTimeString() +
|
||||
" " + stat.type + " SSRC: " + stat.ssrc;
|
||||
if (stat.packetsReceived !== undefined) {
|
||||
statsString += " Received: " + stat.packetsReceived + " packets";
|
||||
if (stat.bytesReceived !== undefined) {
|
||||
statsString += " (" + round00(stat.bytesReceived/1024) + " Kb)";
|
||||
}
|
||||
statsString += " Lost: " + stat.packetsLost + " Jitter: " + stat.jitter;
|
||||
if (stat.mozRtt !== undefined) {
|
||||
statsString += " RTT: " + stat.mozRtt + " ms";
|
||||
}
|
||||
} else if (stat.packetsSent !== undefined) {
|
||||
statsString += " Sent: " + stat.packetsSent + " packets";
|
||||
if (stat.bytesSent !== undefined) {
|
||||
statsString += " (" + round00(stat.bytesSent/1024) + " Kb)";
|
||||
}
|
||||
}
|
||||
div.appendChild(document.createTextNode(statsString));
|
||||
return div;
|
||||
}
|
||||
|
||||
function dumpCoderStat(stat) {
|
||||
var div = document.createElement('div');
|
||||
if (stat.bitrateMean !== undefined ||
|
||||
stat.framerateMean !== undefined ||
|
||||
stat.droppedFrames !== undefined ||
|
||||
stat.discardedPackets !== undefined) {
|
||||
var statsString = (stat.packetsReceived !== undefined)? " Decoder:" : " Encoder:";
|
||||
if (stat.bitrateMean !== undefined) {
|
||||
statsString += " Avg. bitrate: " + (stat.bitrateMean/1000000).toFixed(2) + " Mbps";
|
||||
if (stat.bitrateStdDev !== undefined) {
|
||||
statsString += " (" + (stat.bitrateStdDev/1000000).toFixed(2) + " SD)";
|
||||
}
|
||||
}
|
||||
if (stat.framerateMean !== undefined) {
|
||||
statsString += " Avg. framerate: " + (stat.framerateMean).toFixed(2) + " fps";
|
||||
if (stat.framerateStdDev !== undefined) {
|
||||
statsString += " (" + stat.framerateStdDev.toFixed(2) + " SD)";
|
||||
}
|
||||
}
|
||||
if (stat.droppedFrames !== undefined) {
|
||||
statsString += " Dropped frames: " + stat.droppedFrames;
|
||||
}
|
||||
if (stat.discardedPackets !== undefined) {
|
||||
statsString += " Discarded packets: " + stat.discardedPackets;
|
||||
}
|
||||
div.appendChild(document.createTextNode(statsString));
|
||||
}
|
||||
return div;
|
||||
}
|
||||
|
||||
function buildPcDiv(stats, pcDivHeading) {
|
||||
var newPcDiv = document.createElement('div');
|
||||
|
||||
var heading = document.createElement('h3');
|
||||
|
||||
if (stats.closed) {
|
||||
heading.appendChild(document.createTextNode("Closed "));
|
||||
}
|
||||
|
||||
heading.appendChild(document.createTextNode(pcDivHeading));
|
||||
|
||||
heading.appendChild(document.createTextNode(" " +
|
||||
new Date(stats.timestamp).toTimeString()));
|
||||
|
||||
newPcDiv.appendChild(heading);
|
||||
|
||||
// First, ICE stats
|
||||
var iceHeading = document.createElement('h4');
|
||||
iceHeading.appendChild(document.createTextNode("ICE statistics"));
|
||||
newPcDiv.appendChild(iceHeading);
|
||||
|
||||
var iceTablesByComponent = {};
|
||||
|
||||
function getIceTables(componentId) {
|
||||
if (!iceTablesByComponent[componentId]) {
|
||||
iceTablesByComponent[componentId] = {
|
||||
candidatePairTable: buildEmptyCandPairTable(),
|
||||
localCandidateTable: buildEmptyCandTable(true),
|
||||
remoteCandidateTable: buildEmptyCandTable(false)
|
||||
};
|
||||
}
|
||||
return iceTablesByComponent[componentId];
|
||||
}
|
||||
|
||||
// Candidates
|
||||
var candidateMap = {}; // Used later to speed up recording of candidate pairs
|
||||
|
||||
if (stats.iceCandidateStats) {
|
||||
stats.iceCandidateStats.forEach(function(cand) {
|
||||
var tables = getIceTables(cand.componentId);
|
||||
|
||||
candidateMap[cand.id] = cand;
|
||||
|
||||
if (cand.type == "localcandidate") {
|
||||
tables.localCandidateTable.appendChild(buildCandTableRow(cand));
|
||||
} else {
|
||||
tables.remoteCandidateTable.appendChild(buildCandTableRow(cand));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Candidate pairs
|
||||
if (stats.iceCandidatePairStats) {
|
||||
stats.iceCandidatePairStats.forEach(function(candPair) {
|
||||
var candPairTable =
|
||||
getIceTables(candPair.componentId).candidatePairTable;
|
||||
candPairTable.appendChild(
|
||||
buildCandPairTableRow(candPair,
|
||||
candidateMap[candPair.localCandidateId],
|
||||
candidateMap[candPair.remoteCandidateId]));
|
||||
});
|
||||
}
|
||||
|
||||
// Now that tables are completely built, put them on the page.
|
||||
for (var cid in iceTablesByComponent) {
|
||||
if (iceTablesByComponent.hasOwnProperty(cid)) {
|
||||
var tables = iceTablesByComponent[cid];
|
||||
newPcDiv.appendChild(document.createElement('h4'))
|
||||
.appendChild(document.createTextNode(cid));
|
||||
newPcDiv.appendChild(tables.candidatePairTable);
|
||||
newPcDiv.appendChild(tables.localCandidateTable);
|
||||
newPcDiv.appendChild(tables.remoteCandidateTable);
|
||||
}
|
||||
}
|
||||
|
||||
// end of ICE stats
|
||||
|
||||
// Now, SDP
|
||||
var localSdpHeading = document.createElement('h4');
|
||||
localSdpHeading.appendChild(document.createTextNode("Local SDP"));
|
||||
newPcDiv.appendChild(localSdpHeading);
|
||||
|
||||
var localSdpDiv = document.createElement('pre');
|
||||
localSdpDiv.appendChild(document.createTextNode(stats.localSdp));
|
||||
|
||||
newPcDiv.appendChild(localSdpDiv);
|
||||
|
||||
var remoteSdpHeading = document.createElement('h4');
|
||||
remoteSdpHeading.appendChild(document.createTextNode("Remote SDP"));
|
||||
newPcDiv.appendChild(remoteSdpHeading);
|
||||
|
||||
var remoteSdpDiv = document.createElement('pre');
|
||||
remoteSdpDiv.appendChild(document.createTextNode(stats.remoteSdp));
|
||||
|
||||
newPcDiv.appendChild(remoteSdpDiv);
|
||||
// End of SDP
|
||||
|
||||
// Now, RTP stats
|
||||
var rtpHeading = document.createElement('h4');
|
||||
rtpHeading.appendChild(document.createTextNode("RTP statistics"));
|
||||
newPcDiv.appendChild(rtpHeading);
|
||||
|
||||
// Build map from id -> remote RTP stats (ie; stats obtained from RTCP
|
||||
// from the other end). This allows us to pair up local/remote stats for
|
||||
// the same stream more easily.
|
||||
var remoteRtpStatsMap = {};
|
||||
|
||||
var addRemoteStatToMap = function (rtpStat) {
|
||||
if (rtpStat.isRemote) {
|
||||
remoteRtpStatsMap[rtpStat.id] = rtpStat;
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.inboundRTPStreamStats) {
|
||||
stats.inboundRTPStreamStats.forEach(addRemoteStatToMap);
|
||||
}
|
||||
|
||||
if (stats.outboundRTPStreamStats) {
|
||||
stats.outboundRTPStreamStats.forEach(addRemoteStatToMap);
|
||||
}
|
||||
|
||||
var addRtpStatPairToDocument = function (rtpStat) {
|
||||
if (!rtpStat.isRemote) {
|
||||
newPcDiv.appendChild(document.createElement('h5'))
|
||||
.appendChild(document.createTextNode(rtpStat.id));
|
||||
if (rtpStat.mozAvSyncDelay !== undefined ||
|
||||
rtpStat.mozJitterBufferDelay !== undefined) {
|
||||
newPcDiv.appendChild(dumpAvStat(rtpStat));
|
||||
}
|
||||
newPcDiv.appendChild(dumpCoderStat(rtpStat));
|
||||
newPcDiv.appendChild(dumpRtpStat(rtpStat, "Local: "));
|
||||
|
||||
// Might not be receiving RTCP, so we have no idea what the
|
||||
// statistics look like from the perspective of the other end.
|
||||
if (rtpStat.remoteId) {
|
||||
var remoteRtpStat = remoteRtpStatsMap[rtpStat.remoteId];
|
||||
newPcDiv.appendChild(dumpRtpStat(remoteRtpStat, "Remote: "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.outboundRTPStreamStats) {
|
||||
stats.outboundRTPStreamStats.forEach(addRtpStatPairToDocument);
|
||||
}
|
||||
|
||||
if (stats.inboundRTPStreamStats) {
|
||||
stats.inboundRTPStreamStats.forEach(addRtpStatPairToDocument);
|
||||
}
|
||||
|
||||
return newPcDiv;
|
||||
}
|
||||
|
||||
function displayStats(globalReport) {
|
||||
console.log("Got stats callback.");
|
||||
globalReport.reports.forEach(function (report) {
|
||||
var pcDivHeading = 'PeerConnection:' + report.pcid;
|
||||
|
||||
var pcDiv = document.getElementById(pcDivHeading);
|
||||
var newPcDiv = buildPcDiv(report, pcDivHeading);
|
||||
newPcDiv.id = pcDivHeading;
|
||||
|
||||
if (!pcDiv) {
|
||||
document.getElementById('stats').appendChild(newPcDiv);
|
||||
} else {
|
||||
document.getElementById('stats').replaceChild(newPcDiv, pcDiv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
WebrtcGlobalInformation.getAllStats(displayStats);
|
||||
if (WebrtcGlobalInformation.debugLevel) {
|
||||
setDebugButton(true);
|
||||
} else {
|
||||
setDebugButton(false);
|
||||
}
|
||||
if (WebrtcGlobalInformation.aecDebug) {
|
||||
setAECDebugButton(true);
|
||||
} else {
|
||||
setAECDebugButton(false);
|
||||
}
|
||||
}
|
||||
|
||||
function startDebugMode() {
|
||||
WebrtcGlobalInformation.debugLevel = 65535;
|
||||
setDebugButton(true);
|
||||
}
|
||||
|
||||
function stopDebugMode() {
|
||||
WebrtcGlobalInformation.debugLevel = 0;
|
||||
setDebugButton(false);
|
||||
}
|
||||
|
||||
function setDebugButton(on) {
|
||||
var button = document.getElementById("debug-toggle-button");
|
||||
button.innerHTML = on ? "Stop debug mode" : "Start debug mode";
|
||||
button.onclick = on ? stopDebugMode : startDebugMode;
|
||||
}
|
||||
|
||||
function startAECDebugMode() {
|
||||
WebrtcGlobalInformation.aecDebug = true;
|
||||
setAECDebugButton(true);
|
||||
}
|
||||
|
||||
function stopAECDebugMode() {
|
||||
WebrtcGlobalInformation.aecDebug = false;
|
||||
setAECDebugButton(false);
|
||||
}
|
||||
|
||||
function setAECDebugButton(on) {
|
||||
var button = document.getElementById("aec-debug-toggle-button");
|
||||
button.innerHTML = on ? "Stop AEC logging" : "Start AEC logging";
|
||||
button.onclick = on ? stopAECDebugMode : startAECDebugMode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<body id="body" onload="onLoad()">
|
||||
<div id="stats">
|
||||
</div>
|
||||
<button onclick="WebrtcGlobalInformation.getLogging('', displayLogs)">
|
||||
Connection log
|
||||
</button>
|
||||
<button id="debug-toggle-button" onclick="startDebugMode()">
|
||||
Start debug mode
|
||||
</button>
|
||||
<button id="aec-debug-toggle-button" onclick="startAECDebugMode()">
|
||||
Start AEC logging
|
||||
</button>
|
||||
<div id="logs">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim: softtabstop=2:shiftwidth=2:expandtab
|
||||
-->
|
21
toolkit/content/aboutwebrtc/README.txt
Normal file
21
toolkit/content/aboutwebrtc/README.txt
Normal file
@ -0,0 +1,21 @@
|
||||
Working with React JSX files
|
||||
============================
|
||||
|
||||
The about:webrtc page uses [React](http://facebook.github.io/react/).
|
||||
The UI is written in JSX files and transpiled to JS before we
|
||||
commit. You need to install the JSX compiler using npm in order to
|
||||
compile the .jsx files into regular .js ones:
|
||||
|
||||
npm install -g react-tools
|
||||
|
||||
Run the following command:
|
||||
|
||||
jsx -w -x jsx . .
|
||||
|
||||
jsx can also be do a one-time compile pass instead of watching if the
|
||||
-w argument is omitted. Be sure to commit any transpiled files at the
|
||||
same time as changes to their sources.
|
||||
|
||||
IMPORTANT: do not modify the generated files, only their JSX
|
||||
counterpart.
|
||||
|
78
toolkit/content/aboutwebrtc/aboutWebrtc.css
Normal file
78
toolkit/content/aboutwebrtc/aboutWebrtc.css
Normal file
@ -0,0 +1,78 @@
|
||||
html {
|
||||
background-color: #EDECEB;
|
||||
font: message-box;
|
||||
}
|
||||
|
||||
#logs {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.peer-connections {
|
||||
padding: 2em;
|
||||
margin: 1em 0em;
|
||||
border: 1px solid #AFACA9;
|
||||
border-radius: 10px;
|
||||
background: none repeat scroll 0% 0% #FFF;
|
||||
}
|
||||
|
||||
.peer-connection h3 {
|
||||
background-color: #DDD;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.peer-connection h3 span:last-child {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.peer-connection section {
|
||||
border: 1px solid #AFACA9;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.peer-connection table {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.peer-connection table th,
|
||||
.peer-connection table td {
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
.peer-connection table tr:nth-child(even) {
|
||||
background-color: #DDD;
|
||||
}
|
||||
|
||||
.pcid span:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tabs > ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.tabs li {
|
||||
display: inline;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.tabs li > a {
|
||||
text-decoration: none;
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
.tabs li.active {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #AFACA9;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tabs section {
|
||||
clear: both;
|
||||
}
|
||||
|
529
toolkit/content/aboutwebrtc/aboutWebrtc.js
Normal file
529
toolkit/content/aboutwebrtc/aboutWebrtc.js
Normal file
@ -0,0 +1,529 @@
|
||||
/** @jsx React.DOM */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var Tabs = React.createClass({displayName: 'Tabs',
|
||||
getDefaultProps: function() {
|
||||
return {selectedIndex: 0};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {selectedIndex: this.props.selectedIndex};
|
||||
},
|
||||
|
||||
selectTab: function(index) {
|
||||
return function(event) {
|
||||
event.preventDefault();
|
||||
this.setState({selectedIndex: index});
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
_findSelectedTabContent: function() {
|
||||
// Using map() to filter children…
|
||||
// https://github.com/facebook/react/issues/1644#issuecomment-45138113
|
||||
return React.Children.map(this.props.children, function(tab, i) {
|
||||
return i === this.state.selectedIndex ? tab : null;
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cx = React.addons.classSet;
|
||||
return (
|
||||
React.DOM.div({className: "tabs"},
|
||||
React.DOM.ul(null,
|
||||
React.Children.map(this.props.children, function(tab, i) {
|
||||
return (
|
||||
React.DOM.li({className: cx({active: i === this.state.selectedIndex})},
|
||||
React.DOM.a({href: "#", key: i, onClick: this.selectTab(i)},
|
||||
tab.props.title
|
||||
)
|
||||
)
|
||||
);
|
||||
}.bind(this))
|
||||
),
|
||||
this._findSelectedTabContent()
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var Tab = React.createClass({displayName: 'Tab',
|
||||
render: function() {
|
||||
return React.DOM.section(null, this.props.children);
|
||||
}
|
||||
});
|
||||
|
||||
var AboutWebRTC = React.createClass({displayName: 'AboutWebRTC',
|
||||
getInitialState: function() {
|
||||
return {logs: null, reports: this.props.reports};
|
||||
},
|
||||
|
||||
displayLogs: function() {
|
||||
WebrtcGlobalInformation.getLogging('', function(logs) {
|
||||
this.setState({logs: logs, reports: this.state.reports});
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
React.DOM.div({id: "stats"},
|
||||
PeerConnections({reports: this.state.reports})
|
||||
),
|
||||
React.DOM.div({id: "buttons"},
|
||||
LogsButton({handler: this.displayLogs}),
|
||||
DebugButton(null),
|
||||
AECButton(null)
|
||||
),
|
||||
React.DOM.div({id: "logs"},
|
||||
this.state.logs ? Logs({logs: this.state.logs}) : null
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var PeerConnections = React.createClass({displayName: 'PeerConnections',
|
||||
getInitialState: function() {
|
||||
// Sort the reports to have the more recent at the top
|
||||
var reports = this.props.reports.slice().sort(function(r1, r2) {
|
||||
return r2.timestamp - r1.timestamp;
|
||||
});
|
||||
|
||||
return {reports: reports};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div({className: "peer-connections"},
|
||||
|
||||
this.state.reports.map(function(report, i) {
|
||||
return PeerConnection({key: i, report: report});
|
||||
})
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var PeerConnection = React.createClass({displayName: 'PeerConnection',
|
||||
getPCInfo: function(report) {
|
||||
return {
|
||||
id: report.pcid.match(/id=(\S+)/)[1],
|
||||
url: report.pcid.match(/http[^)]+/)[0],
|
||||
closed: report.closed
|
||||
};
|
||||
},
|
||||
|
||||
getIceCandidatePairs: function(report) {
|
||||
var candidates =
|
||||
report.iceCandidateStats.reduce(function(candidates, candidate) {
|
||||
candidates[candidate.id] = candidate;
|
||||
|
||||
return candidates;
|
||||
}, {});
|
||||
|
||||
var pairs = report.iceCandidatePairStats.map(function(pair) {
|
||||
var localCandidate = candidates[pair.localCandidateId];
|
||||
var remoteCandidate = candidates[pair.remoteCandidateId];
|
||||
|
||||
return {
|
||||
localCandidate: candidateToString(localCandidate),
|
||||
remoteCandidate: candidateToString(remoteCandidate),
|
||||
state: pair.state,
|
||||
priority: pair.mozPriority,
|
||||
nominated: pair.nominated,
|
||||
selected: pair.selected
|
||||
};
|
||||
});
|
||||
|
||||
var pairedCandidates = pairs.reduce(function(paired, pair) {
|
||||
paired.add(pair.localCandidate.id);
|
||||
paired.add(pair.remoteCandidate.id);
|
||||
|
||||
return paired
|
||||
}, new Set());
|
||||
|
||||
var unifiedPairs =
|
||||
report.iceCandidateStats.reduce(function(pairs, candidate) {
|
||||
if (pairedCandidates.has(candidate)) {
|
||||
return pairs;
|
||||
}
|
||||
|
||||
var isLocal = candidate.type === "localcandidate";
|
||||
|
||||
pairs.push({
|
||||
localCandidate: isLocal ? candidateToString(candidate) : null,
|
||||
remoteCandidate: isLocal ? null : candidateToString(candidate),
|
||||
state: null,
|
||||
priority: null,
|
||||
nominated: null,
|
||||
selected: null
|
||||
});
|
||||
|
||||
return pairs;
|
||||
}, pairs);
|
||||
|
||||
return unifiedPairs;
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
unfolded: false
|
||||
};
|
||||
},
|
||||
|
||||
onFold: function() {
|
||||
this.setState({unfolded: !this.state.unfolded});
|
||||
},
|
||||
|
||||
body: function(report, pairs) {
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
React.DOM.p({className: "pcid"}, "PeerConnection ID: ", report.pcid),
|
||||
Tabs(null,
|
||||
Tab({title: "Ice Stats"},
|
||||
IceStats({pairs: pairs})
|
||||
),
|
||||
Tab({title: "SDP"},
|
||||
SDP({type: "local", sdp: report.localSdp}),
|
||||
SDP({type: "remote", sdp: report.remoteSdp})
|
||||
),
|
||||
Tab({title: "RTP Stats"},
|
||||
RTPStats({report: report})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var report = this.props.report;
|
||||
var pcInfo = this.getPCInfo(report);
|
||||
var pairs = this.getIceCandidatePairs(report);
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "peer-connection"},
|
||||
React.DOM.h3({onClick: this.onFold},
|
||||
"[", pcInfo.id, "] ", pcInfo.url, " ", pcInfo.closed ? "(closed)" : null,
|
||||
new Date(report.timestamp).toTimeString()
|
||||
),
|
||||
this.state.unfolded ? this.body(report, pairs) : undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var IceStats = React.createClass({displayName: 'IceStats',
|
||||
sortHeadings: {
|
||||
"Local candidate": "localCandidate",
|
||||
"Remote candidate": "remoteCandidate",
|
||||
"Ice State": "state",
|
||||
"Priority": "priority",
|
||||
"Nominated": "nominated",
|
||||
"Selected": "selected"
|
||||
},
|
||||
|
||||
sort: function(key) {
|
||||
var sorting = this.state.sorting;
|
||||
var pairs = this.state.pairs.slice().sort(function(pair1, pair2) {
|
||||
var value1 = pair1[key] ? pair1[key].toString() : "";
|
||||
var value2 = pair2[key] ? pair2[key].toString() : "";
|
||||
|
||||
// Reverse sorting
|
||||
if (key === sorting) {
|
||||
return value2.localeCompare(value1);
|
||||
}
|
||||
|
||||
return value1.localeCompare(value2);
|
||||
}.bind(this));
|
||||
|
||||
sorting = (key === sorting) ? null : key;
|
||||
this.setState({pairs: pairs, sorting: sorting});
|
||||
},
|
||||
|
||||
generateSortHeadings: function() {
|
||||
return Object.keys(this.sortHeadings).map(function(heading, i) {
|
||||
var sortKey = this.sortHeadings[heading];
|
||||
return (
|
||||
React.DOM.th(null,
|
||||
React.DOM.a({href: "#", onClick: this.sort.bind(this, sortKey)},
|
||||
heading
|
||||
)
|
||||
)
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {pairs: this.props.pairs, sorting: null};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.table(null,
|
||||
React.DOM.tbody(null,
|
||||
React.DOM.tr(null, this.generateSortHeadings()),
|
||||
this.state.pairs.map(function(pair, i) {
|
||||
return IceCandidatePair({key: i, pair: pair});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var IceCandidatePair = React.createClass({displayName: 'IceCandidatePair',
|
||||
render: function() {
|
||||
var pair = this.props.pair;
|
||||
return (
|
||||
React.DOM.tr(null,
|
||||
React.DOM.td(null, pair.localCandidate),
|
||||
React.DOM.td(null, pair.remoteCandidate),
|
||||
React.DOM.td(null, pair.state),
|
||||
React.DOM.td(null, pair.priority),
|
||||
React.DOM.td(null, pair.nominated ? '✔' : null),
|
||||
React.DOM.td(null, pair.selected ? '✔' : null)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SDP = React.createClass({displayName: 'SDP',
|
||||
render: function() {
|
||||
var type = labelize(this.props.type);
|
||||
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
React.DOM.h4(null, type, " SDP"),
|
||||
React.DOM.pre(null,
|
||||
this.props.sdp
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var RTPStats = React.createClass({displayName: 'RTPStats',
|
||||
getRtpStats: function(report) {
|
||||
var remoteRtpStats = {};
|
||||
var rtpStats = [];
|
||||
|
||||
rtpStats = rtpStats.concat(report.inboundRTPStreamStats || []);
|
||||
rtpStats = rtpStats.concat(report.outboundRTPStreamStats || []);
|
||||
|
||||
rtpStats.forEach(function(stats) {
|
||||
if (stats.isRemote) {
|
||||
remoteRtpStats[stats.id] = stats;
|
||||
}
|
||||
});
|
||||
|
||||
rtpStats.forEach(function(stats) {
|
||||
if (stats.remoteId) {
|
||||
stats.remoteRtpStats = remoteRtpStats[stats.remoteId];
|
||||
}
|
||||
});
|
||||
|
||||
return rtpStats;
|
||||
},
|
||||
|
||||
dumpAvStats: function(stats) {
|
||||
var statsString = "";
|
||||
if (stats.mozAvSyncDelay) {
|
||||
statsString += `A/V sync: ${stats.mozAvSyncDelay} ms `;
|
||||
}
|
||||
if (stats.mozJitterBufferDelay) {
|
||||
statsString += `Jitter-buffer delay: ${stats.mozJitterBufferDelay} ms`;
|
||||
}
|
||||
|
||||
return React.DOM.div(null, statsString);
|
||||
},
|
||||
|
||||
dumpCoderStats: function(stats) {
|
||||
var statsString = "";
|
||||
var label;
|
||||
|
||||
if (stats.bitrateMean) {
|
||||
statsString += ` Avg. bitrate: ${(stats.bitrateMean/1000000).toFixed(2)} Mbps`;
|
||||
if (stats.bitrateStdDev) {
|
||||
statsString += ` (${(stats.bitrateStdDev/1000000).toFixed(2)} SD)`;
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.framerateMean) {
|
||||
statsString += ` Avg. framerate: ${(stats.framerateMean).toFixed(2)} fps`;
|
||||
if (stats.framerateStdDev) {
|
||||
statsString += ` (${stats.framerateStdDev.toFixed(2)} SD)`;
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.droppedFrames) {
|
||||
statsString += ` Dropped frames: ${stats.droppedFrames}`;
|
||||
}
|
||||
if (stats.discardedPackets) {
|
||||
statsString += ` Discarded packets: ${stats.discardedPackets}`;
|
||||
}
|
||||
|
||||
if (statsString) {
|
||||
label = (stats.packetsReceived)? " Decoder:" : " Encoder:";
|
||||
statsString = label + statsString;
|
||||
}
|
||||
|
||||
return React.DOM.div(null, statsString);
|
||||
},
|
||||
|
||||
dumpRtpStats: function(stats, type) {
|
||||
var label = labelize(type);
|
||||
var time = new Date(stats.timestamp).toTimeString();
|
||||
|
||||
var statsString = `${label}: ${time} ${stats.type} SSRC: ${stats.ssrc}`;
|
||||
|
||||
if (stats.packetsReceived) {
|
||||
statsString += ` Received: ${stats.packetsReceived} packets`;
|
||||
|
||||
if (stats.bytesReceived) {
|
||||
statsString += ` (${round00(stats.bytesReceived/1024)} Kb)`;
|
||||
}
|
||||
|
||||
statsString += ` Lost: ${stats.packetsLost} Jitter: ${stats.jitter}`;
|
||||
|
||||
if (stats.mozRtt) {
|
||||
statsString += ` RTT: ${stats.mozRtt} ms`;
|
||||
}
|
||||
} else if (stats.packetsSent) {
|
||||
statsString += ` Sent: ${stats.packetsSent} packets`;
|
||||
if (stats.bytesSent) {
|
||||
statsString += ` (${round00(stats.bytesSent/1024)} Kb)`;
|
||||
}
|
||||
}
|
||||
|
||||
return React.DOM.div(null, statsString);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var rtpStats = this.getRtpStats(this.props.report);
|
||||
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
rtpStats.map(function(stats) {
|
||||
var isAvStats = (stats.mozAvSyncDelay || stats.mozJitterBufferDelay);
|
||||
var remoteRtpStats = stats.remoteId ? stats.remoteRtpStats : null;
|
||||
|
||||
return [
|
||||
React.DOM.h5(null, stats.id),
|
||||
isAvStats ? this.dumpAvStats(stats) : null,
|
||||
this.dumpCoderStats(stats),
|
||||
this.dumpRtpStats(stats, "local"),
|
||||
remoteRtpStats ? this.dumpRtpStats(remoteRtpStats, "remote") : null
|
||||
]
|
||||
}.bind(this))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var LogsButton = React.createClass({displayName: 'LogsButton',
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.button({onClick: this.props.handler}, "Connection log")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DebugButton = React.createClass({displayName: 'DebugButton',
|
||||
getInitialState: function() {
|
||||
var on = (WebrtcGlobalInformation.debugLevel > 0);
|
||||
return {on: on};
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
if (this.state.on)
|
||||
WebrtcGlobalInformation.debugLevel = 0;
|
||||
else
|
||||
WebrtcGlobalInformation.debugLevel = 65535;
|
||||
|
||||
this.setState({on: !this.state.on});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.button({onClick: this.onClick},
|
||||
this.state.on ? "Stop debug mode" : "Start debug mode"
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var AECButton = React.createClass({displayName: 'AECButton',
|
||||
getInitialState: function() {
|
||||
return {on: WebrtcGlobalInformation.aecDebug};
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
WebrtcGlobalInformation.aecDebug = !this.state.on;
|
||||
this.setState({on: WebrtcGlobalInformation.aecDebug});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.button({onClick: this.onClick},
|
||||
this.state.on ? "Stop AEC logging" : "Start AEC logging"
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var Logs = React.createClass({displayName: 'Logs',
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
React.DOM.h3(null, "Logging:"),
|
||||
React.DOM.div(null,
|
||||
this.props.logs.map(function(line, i) {
|
||||
return React.DOM.div({key: i}, line);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function iceCandidateMapping(iceCandidates) {
|
||||
var candidates = {};
|
||||
iceCandidates = iceCandidates || [];
|
||||
|
||||
iceCandidates.forEach(function(candidate) {
|
||||
candidates[candidate.id] = candidate;
|
||||
});
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function round00(num) {
|
||||
return Math.round(num * 100) / 100;
|
||||
}
|
||||
|
||||
function labelize(label) {
|
||||
return `${label.charAt(0).toUpperCase()}${label.slice(1)}`;
|
||||
}
|
||||
|
||||
function candidateToString(c) {
|
||||
var type = c.candidateType;
|
||||
|
||||
if (c.type == "localcandidate" && c.candidateType == "relayed") {
|
||||
type = `${c.candidateType}-${c.mozLocalTransport}`;
|
||||
}
|
||||
|
||||
return `${c.ipAddress}:${c.portNumber}/${c.transport}(${type})`
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
WebrtcGlobalInformation.getAllStats(function(globalReport) {
|
||||
var reports = globalReport.reports;
|
||||
React.renderComponent(AboutWebRTC({reports: reports}),
|
||||
document.querySelector("#body"));
|
||||
});
|
||||
}
|
529
toolkit/content/aboutwebrtc/aboutWebrtc.jsx
Normal file
529
toolkit/content/aboutwebrtc/aboutWebrtc.jsx
Normal file
@ -0,0 +1,529 @@
|
||||
/** @jsx React.DOM */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var Tabs = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {selectedIndex: 0};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {selectedIndex: this.props.selectedIndex};
|
||||
},
|
||||
|
||||
selectTab: function(index) {
|
||||
return function(event) {
|
||||
event.preventDefault();
|
||||
this.setState({selectedIndex: index});
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
_findSelectedTabContent: function() {
|
||||
// Using map() to filter children…
|
||||
// https://github.com/facebook/react/issues/1644#issuecomment-45138113
|
||||
return React.Children.map(this.props.children, function(tab, i) {
|
||||
return i === this.state.selectedIndex ? tab : null;
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cx = React.addons.classSet;
|
||||
return (
|
||||
<div className="tabs">
|
||||
<ul>{
|
||||
React.Children.map(this.props.children, function(tab, i) {
|
||||
return (
|
||||
<li className={cx({active: i === this.state.selectedIndex})}>
|
||||
<a href="#" key={i} onClick={this.selectTab(i)}>
|
||||
{tab.props.title}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}.bind(this))
|
||||
}</ul>
|
||||
{this._findSelectedTabContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var Tab = React.createClass({
|
||||
render: function() {
|
||||
return <section>{this.props.children}</section>;
|
||||
}
|
||||
});
|
||||
|
||||
var AboutWebRTC = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {logs: null, reports: this.props.reports};
|
||||
},
|
||||
|
||||
displayLogs: function() {
|
||||
WebrtcGlobalInformation.getLogging('', function(logs) {
|
||||
this.setState({logs: logs, reports: this.state.reports});
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<div id="stats">
|
||||
<PeerConnections reports={this.state.reports}/>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<LogsButton handler={this.displayLogs}/>
|
||||
<DebugButton/>
|
||||
<AECButton/>
|
||||
</div>
|
||||
<div id="logs">{
|
||||
this.state.logs ? <Logs logs={this.state.logs} /> : null
|
||||
}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var PeerConnections = React.createClass({
|
||||
getInitialState: function() {
|
||||
// Sort the reports to have the more recent at the top
|
||||
var reports = this.props.reports.slice().sort(function(r1, r2) {
|
||||
return r2.timestamp - r1.timestamp;
|
||||
});
|
||||
|
||||
return {reports: reports};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="peer-connections">
|
||||
{
|
||||
this.state.reports.map(function(report, i) {
|
||||
return <PeerConnection key={i} report={report}/>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var PeerConnection = React.createClass({
|
||||
getPCInfo: function(report) {
|
||||
return {
|
||||
id: report.pcid.match(/id=(\S+)/)[1],
|
||||
url: report.pcid.match(/http[^)]+/)[0],
|
||||
closed: report.closed
|
||||
};
|
||||
},
|
||||
|
||||
getIceCandidatePairs: function(report) {
|
||||
var candidates =
|
||||
report.iceCandidateStats.reduce(function(candidates, candidate) {
|
||||
candidates[candidate.id] = candidate;
|
||||
|
||||
return candidates;
|
||||
}, {});
|
||||
|
||||
var pairs = report.iceCandidatePairStats.map(function(pair) {
|
||||
var localCandidate = candidates[pair.localCandidateId];
|
||||
var remoteCandidate = candidates[pair.remoteCandidateId];
|
||||
|
||||
return {
|
||||
localCandidate: candidateToString(localCandidate),
|
||||
remoteCandidate: candidateToString(remoteCandidate),
|
||||
state: pair.state,
|
||||
priority: pair.mozPriority,
|
||||
nominated: pair.nominated,
|
||||
selected: pair.selected
|
||||
};
|
||||
});
|
||||
|
||||
var pairedCandidates = pairs.reduce(function(paired, pair) {
|
||||
paired.add(pair.localCandidate.id);
|
||||
paired.add(pair.remoteCandidate.id);
|
||||
|
||||
return paired
|
||||
}, new Set());
|
||||
|
||||
var unifiedPairs =
|
||||
report.iceCandidateStats.reduce(function(pairs, candidate) {
|
||||
if (pairedCandidates.has(candidate)) {
|
||||
return pairs;
|
||||
}
|
||||
|
||||
var isLocal = candidate.type === "localcandidate";
|
||||
|
||||
pairs.push({
|
||||
localCandidate: isLocal ? candidateToString(candidate) : null,
|
||||
remoteCandidate: isLocal ? null : candidateToString(candidate),
|
||||
state: null,
|
||||
priority: null,
|
||||
nominated: null,
|
||||
selected: null
|
||||
});
|
||||
|
||||
return pairs;
|
||||
}, pairs);
|
||||
|
||||
return unifiedPairs;
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
unfolded: false
|
||||
};
|
||||
},
|
||||
|
||||
onFold: function() {
|
||||
this.setState({unfolded: !this.state.unfolded});
|
||||
},
|
||||
|
||||
body: function(report, pairs) {
|
||||
return (
|
||||
<div>
|
||||
<p className="pcid">PeerConnection ID: {report.pcid}</p>
|
||||
<Tabs>
|
||||
<Tab title="Ice Stats">
|
||||
<IceStats pairs={pairs} />
|
||||
</Tab>
|
||||
<Tab title="SDP">
|
||||
<SDP type="local" sdp={report.localSdp} />
|
||||
<SDP type="remote" sdp={report.remoteSdp} />
|
||||
</Tab>
|
||||
<Tab title="RTP Stats">
|
||||
<RTPStats report={report} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var report = this.props.report;
|
||||
var pcInfo = this.getPCInfo(report);
|
||||
var pairs = this.getIceCandidatePairs(report);
|
||||
|
||||
return (
|
||||
<div className="peer-connection">
|
||||
<h3 onClick={this.onFold}>
|
||||
[{pcInfo.id}] {pcInfo.url} {pcInfo.closed ? "(closed)" : null}
|
||||
{new Date(report.timestamp).toTimeString()}
|
||||
</h3>
|
||||
{this.state.unfolded ? this.body(report, pairs) : undefined}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var IceStats = React.createClass({
|
||||
sortHeadings: {
|
||||
"Local candidate": "localCandidate",
|
||||
"Remote candidate": "remoteCandidate",
|
||||
"Ice State": "state",
|
||||
"Priority": "priority",
|
||||
"Nominated": "nominated",
|
||||
"Selected": "selected"
|
||||
},
|
||||
|
||||
sort: function(key) {
|
||||
var sorting = this.state.sorting;
|
||||
var pairs = this.state.pairs.slice().sort(function(pair1, pair2) {
|
||||
var value1 = pair1[key] ? pair1[key].toString() : "";
|
||||
var value2 = pair2[key] ? pair2[key].toString() : "";
|
||||
|
||||
// Reverse sorting
|
||||
if (key === sorting) {
|
||||
return value2.localeCompare(value1);
|
||||
}
|
||||
|
||||
return value1.localeCompare(value2);
|
||||
}.bind(this));
|
||||
|
||||
sorting = (key === sorting) ? null : key;
|
||||
this.setState({pairs: pairs, sorting: sorting});
|
||||
},
|
||||
|
||||
generateSortHeadings: function() {
|
||||
return Object.keys(this.sortHeadings).map(function(heading, i) {
|
||||
var sortKey = this.sortHeadings[heading];
|
||||
return (
|
||||
<th>
|
||||
<a href="#" onClick={this.sort.bind(this, sortKey)}>
|
||||
{heading}
|
||||
</a>
|
||||
</th>
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {pairs: this.props.pairs, sorting: null};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>{this.generateSortHeadings()}</tr>
|
||||
{this.state.pairs.map(function(pair, i) {
|
||||
return <IceCandidatePair key={i} pair={pair} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var IceCandidatePair = React.createClass({
|
||||
render: function() {
|
||||
var pair = this.props.pair;
|
||||
return (
|
||||
<tr>
|
||||
<td>{pair.localCandidate}</td>
|
||||
<td>{pair.remoteCandidate}</td>
|
||||
<td>{pair.state}</td>
|
||||
<td>{pair.priority}</td>
|
||||
<td>{pair.nominated ? '✔' : null}</td>
|
||||
<td>{pair.selected ? '✔' : null}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SDP = React.createClass({
|
||||
render: function() {
|
||||
var type = labelize(this.props.type);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>{type} SDP</h4>
|
||||
<pre>
|
||||
{this.props.sdp}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var RTPStats = React.createClass({
|
||||
getRtpStats: function(report) {
|
||||
var remoteRtpStats = {};
|
||||
var rtpStats = [];
|
||||
|
||||
rtpStats = rtpStats.concat(report.inboundRTPStreamStats || []);
|
||||
rtpStats = rtpStats.concat(report.outboundRTPStreamStats || []);
|
||||
|
||||
rtpStats.forEach(function(stats) {
|
||||
if (stats.isRemote) {
|
||||
remoteRtpStats[stats.id] = stats;
|
||||
}
|
||||
});
|
||||
|
||||
rtpStats.forEach(function(stats) {
|
||||
if (stats.remoteId) {
|
||||
stats.remoteRtpStats = remoteRtpStats[stats.remoteId];
|
||||
}
|
||||
});
|
||||
|
||||
return rtpStats;
|
||||
},
|
||||
|
||||
dumpAvStats: function(stats) {
|
||||
var statsString = "";
|
||||
if (stats.mozAvSyncDelay) {
|
||||
statsString += `A/V sync: ${stats.mozAvSyncDelay} ms `;
|
||||
}
|
||||
if (stats.mozJitterBufferDelay) {
|
||||
statsString += `Jitter-buffer delay: ${stats.mozJitterBufferDelay} ms`;
|
||||
}
|
||||
|
||||
return <div>{statsString}</div>;
|
||||
},
|
||||
|
||||
dumpCoderStats: function(stats) {
|
||||
var statsString = "";
|
||||
var label;
|
||||
|
||||
if (stats.bitrateMean) {
|
||||
statsString += ` Avg. bitrate: ${(stats.bitrateMean/1000000).toFixed(2)} Mbps`;
|
||||
if (stats.bitrateStdDev) {
|
||||
statsString += ` (${(stats.bitrateStdDev/1000000).toFixed(2)} SD)`;
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.framerateMean) {
|
||||
statsString += ` Avg. framerate: ${(stats.framerateMean).toFixed(2)} fps`;
|
||||
if (stats.framerateStdDev) {
|
||||
statsString += ` (${stats.framerateStdDev.toFixed(2)} SD)`;
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.droppedFrames) {
|
||||
statsString += ` Dropped frames: ${stats.droppedFrames}`;
|
||||
}
|
||||
if (stats.discardedPackets) {
|
||||
statsString += ` Discarded packets: ${stats.discardedPackets}`;
|
||||
}
|
||||
|
||||
if (statsString) {
|
||||
label = (stats.packetsReceived)? " Decoder:" : " Encoder:";
|
||||
statsString = label + statsString;
|
||||
}
|
||||
|
||||
return <div>{statsString}</div>;
|
||||
},
|
||||
|
||||
dumpRtpStats: function(stats, type) {
|
||||
var label = labelize(type);
|
||||
var time = new Date(stats.timestamp).toTimeString();
|
||||
|
||||
var statsString = `${label}: ${time} ${stats.type} SSRC: ${stats.ssrc}`;
|
||||
|
||||
if (stats.packetsReceived) {
|
||||
statsString += ` Received: ${stats.packetsReceived} packets`;
|
||||
|
||||
if (stats.bytesReceived) {
|
||||
statsString += ` (${round00(stats.bytesReceived/1024)} Kb)`;
|
||||
}
|
||||
|
||||
statsString += ` Lost: ${stats.packetsLost} Jitter: ${stats.jitter}`;
|
||||
|
||||
if (stats.mozRtt) {
|
||||
statsString += ` RTT: ${stats.mozRtt} ms`;
|
||||
}
|
||||
} else if (stats.packetsSent) {
|
||||
statsString += ` Sent: ${stats.packetsSent} packets`;
|
||||
if (stats.bytesSent) {
|
||||
statsString += ` (${round00(stats.bytesSent/1024)} Kb)`;
|
||||
}
|
||||
}
|
||||
|
||||
return <div>{statsString}</div>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var rtpStats = this.getRtpStats(this.props.report);
|
||||
|
||||
return (
|
||||
<div>{
|
||||
rtpStats.map(function(stats) {
|
||||
var isAvStats = (stats.mozAvSyncDelay || stats.mozJitterBufferDelay);
|
||||
var remoteRtpStats = stats.remoteId ? stats.remoteRtpStats : null;
|
||||
|
||||
return [
|
||||
<h5>{stats.id}</h5>,
|
||||
isAvStats ? this.dumpAvStats(stats) : null,
|
||||
this.dumpCoderStats(stats),
|
||||
this.dumpRtpStats(stats, "local"),
|
||||
remoteRtpStats ? this.dumpRtpStats(remoteRtpStats, "remote") : null
|
||||
]
|
||||
}.bind(this))
|
||||
}</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var LogsButton = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<button onClick={this.props.handler}>Connection log</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DebugButton = React.createClass({
|
||||
getInitialState: function() {
|
||||
var on = (WebrtcGlobalInformation.debugLevel > 0);
|
||||
return {on: on};
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
if (this.state.on)
|
||||
WebrtcGlobalInformation.debugLevel = 0;
|
||||
else
|
||||
WebrtcGlobalInformation.debugLevel = 65535;
|
||||
|
||||
this.setState({on: !this.state.on});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<button onClick={this.onClick}>{
|
||||
this.state.on ? "Stop debug mode" : "Start debug mode"
|
||||
}</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var AECButton = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {on: WebrtcGlobalInformation.aecDebug};
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
WebrtcGlobalInformation.aecDebug = !this.state.on;
|
||||
this.setState({on: WebrtcGlobalInformation.aecDebug});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<button onClick={this.onClick}>{
|
||||
this.state.on ? "Stop AEC logging" : "Start AEC logging"
|
||||
}</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var Logs = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h3>Logging:</h3>
|
||||
<div>{
|
||||
this.props.logs.map(function(line, i) {
|
||||
return <div key={i}>{line}</div>;
|
||||
})
|
||||
}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function iceCandidateMapping(iceCandidates) {
|
||||
var candidates = {};
|
||||
iceCandidates = iceCandidates || [];
|
||||
|
||||
iceCandidates.forEach(function(candidate) {
|
||||
candidates[candidate.id] = candidate;
|
||||
});
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function round00(num) {
|
||||
return Math.round(num * 100) / 100;
|
||||
}
|
||||
|
||||
function labelize(label) {
|
||||
return `${label.charAt(0).toUpperCase()}${label.slice(1)}`;
|
||||
}
|
||||
|
||||
function candidateToString(c) {
|
||||
var type = c.candidateType;
|
||||
|
||||
if (c.type == "localcandidate" && c.candidateType == "relayed") {
|
||||
type = `${c.candidateType}-${c.mozLocalTransport}`;
|
||||
}
|
||||
|
||||
return `${c.ipAddress}:${c.portNumber}/${c.transport}(${type})`
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
WebrtcGlobalInformation.getAllStats(function(globalReport) {
|
||||
var reports = globalReport.reports;
|
||||
React.renderComponent(<AboutWebRTC reports={reports}/>,
|
||||
document.querySelector("#body"));
|
||||
});
|
||||
}
|
22
toolkit/content/aboutwebrtc/aboutWebrtc.xhtml
Normal file
22
toolkit/content/aboutwebrtc/aboutWebrtc.xhtml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Webrtc Internals</title>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="chrome://global/content/aboutwebrtc/aboutWebrtc.css"/>
|
||||
</head>
|
||||
<body id="body" onload="onLoad()">
|
||||
</body>
|
||||
|
||||
<script type="text/javascript" src="chrome://browser/content/loop/shared/libs/react-0.11.2.js"></script>
|
||||
<script type="text/javascript;version=1.8" src="chrome://global/content/aboutwebrtc/aboutWebrtc.js"/>
|
||||
</html>
|
||||
|
@ -20,7 +20,9 @@ toolkit.jar:
|
||||
content/global/aboutRights-unbranded.xhtml (aboutRights-unbranded.xhtml)
|
||||
content/global/aboutNetworking.js
|
||||
content/global/aboutNetworking.xhtml
|
||||
content/global/aboutWebrtc.xhtml
|
||||
content/global/aboutwebrtc/aboutWebrtc.css (aboutwebrtc/aboutWebrtc.css)
|
||||
content/global/aboutwebrtc/aboutWebrtc.js (aboutwebrtc/aboutWebrtc.js)
|
||||
content/global/aboutwebrtc/aboutWebrtc.xhtml (aboutwebrtc/aboutWebrtc.xhtml)
|
||||
* content/global/aboutSupport.js
|
||||
* content/global/aboutSupport.xhtml
|
||||
* content/global/aboutTelemetry.js
|
||||
|
Loading…
Reference in New Issue
Block a user