mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 00:55:37 +00:00
441 lines
14 KiB
HTML
441 lines
14 KiB
HTML
<?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
|
|
-->
|