mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
533 lines
15 KiB
JavaScript
533 lines
15 KiB
JavaScript
/** @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/. */
|
|
|
|
/* jshint newcap:false */
|
|
/* global React, WebrtcGlobalInformation, document */
|
|
|
|
"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.createElement("div", {className: "tabs"},
|
|
React.createElement("ul", null,
|
|
React.Children.map(this.props.children, function(tab, i) {
|
|
return (
|
|
React.createElement("li", {className: cx({active: i === this.state.selectedIndex})},
|
|
React.createElement("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.createElement("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.createElement("div", null,
|
|
React.createElement("div", {id: "stats"},
|
|
React.createElement(PeerConnections, {reports: this.state.reports})
|
|
),
|
|
React.createElement("div", {id: "buttons"},
|
|
React.createElement(LogsButton, {handler: this.displayLogs}),
|
|
React.createElement(DebugButton, null),
|
|
React.createElement(AECButton, null)
|
|
),
|
|
React.createElement("div", {id: "logs"},
|
|
this.state.logs ? React.createElement(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.createElement("div", {className: "peer-connections"},
|
|
|
|
this.state.reports.map(function(report, i) {
|
|
return React.createElement(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(/url=([^)]+)/)[1],
|
|
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.createElement("div", null,
|
|
React.createElement("p", {className: "pcid"}, "PeerConnection ID: ", report.pcid),
|
|
React.createElement(Tabs, null,
|
|
React.createElement(Tab, {title: "Ice Stats"},
|
|
React.createElement(IceStats, {pairs: pairs})
|
|
),
|
|
React.createElement(Tab, {title: "SDP"},
|
|
React.createElement(SDP, {type: "local", sdp: report.localSdp}),
|
|
React.createElement(SDP, {type: "remote", sdp: report.remoteSdp})
|
|
),
|
|
React.createElement(Tab, {title: "RTP Stats"},
|
|
React.createElement(RTPStats, {report: report})
|
|
)
|
|
)
|
|
)
|
|
);
|
|
},
|
|
|
|
render: function() {
|
|
var report = this.props.report;
|
|
var pcInfo = this.getPCInfo(report);
|
|
var pairs = this.getIceCandidatePairs(report);
|
|
|
|
return (
|
|
React.createElement("div", {className: "peer-connection"},
|
|
React.createElement("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.createElement("th", null,
|
|
React.createElement("a", {href: "#", onClick: this.sort.bind(this, sortKey)},
|
|
heading
|
|
)
|
|
)
|
|
);
|
|
}.bind(this));
|
|
},
|
|
|
|
getInitialState: function() {
|
|
return {pairs: this.props.pairs, sorting: null};
|
|
},
|
|
|
|
render: function() {
|
|
return (
|
|
React.createElement("table", null,
|
|
React.createElement("tbody", null,
|
|
React.createElement("tr", null, this.generateSortHeadings()),
|
|
this.state.pairs.map(function(pair, i) {
|
|
return React.createElement(IceCandidatePair, {key: i, pair: pair});
|
|
})
|
|
)
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
var IceCandidatePair = React.createClass({displayName: "IceCandidatePair",
|
|
render: function() {
|
|
var pair = this.props.pair;
|
|
return (
|
|
React.createElement("tr", null,
|
|
React.createElement("td", null, pair.localCandidate),
|
|
React.createElement("td", null, pair.remoteCandidate),
|
|
React.createElement("td", null, pair.state),
|
|
React.createElement("td", null, pair.priority),
|
|
React.createElement("td", null, pair.nominated ? 'âś”' : null),
|
|
React.createElement("td", null, pair.selected ? 'âś”' : null)
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
var SDP = React.createClass({displayName: "SDP",
|
|
render: function() {
|
|
var type = labelize(this.props.type);
|
|
|
|
return (
|
|
React.createElement("div", null,
|
|
React.createElement("h4", null, type, " SDP"),
|
|
React.createElement("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.createElement("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.createElement("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.createElement("div", null, statsString);
|
|
},
|
|
|
|
render: function() {
|
|
var rtpStats = this.getRtpStats(this.props.report);
|
|
|
|
return (
|
|
React.createElement("div", null,
|
|
rtpStats.map(function(stats) {
|
|
var isAvStats = (stats.mozAvSyncDelay || stats.mozJitterBufferDelay);
|
|
var remoteRtpStats = stats.remoteId ? stats.remoteRtpStats : null;
|
|
|
|
return [
|
|
React.createElement("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.createElement("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.createElement("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.createElement("button", {onClick: this.onClick},
|
|
this.state.on ? "Stop AEC logging" : "Start AEC logging"
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
var Logs = React.createClass({displayName: "Logs",
|
|
render: function() {
|
|
return (
|
|
React.createElement("div", null,
|
|
React.createElement("h3", null, "Logging:"),
|
|
React.createElement("div", null,
|
|
this.props.logs.map(function(line, i) {
|
|
return React.createElement("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.render(React.createElement(AboutWebRTC, {reports: reports}),
|
|
document.querySelector("#body"));
|
|
});
|
|
}
|