|
1 <?xml version="1.0" encoding="UTF-8"?> |
|
2 <!-- This Source Code Form is subject to the terms of the Mozilla Public |
|
3 - License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> |
|
5 |
|
6 |
|
7 <!DOCTYPE html [ |
|
8 <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD; |
|
9 ]> |
|
10 |
|
11 <html xmlns="http://www.w3.org/1999/xhtml"> |
|
12 <head> |
|
13 <title>Webrtc Internals</title> |
|
14 </head> |
|
15 <script> |
|
16 |
|
17 |
|
18 function displayLogs(logs) { |
|
19 var logsDiv = document.getElementById('logs'); |
|
20 while (logsDiv.lastChild) { |
|
21 logsDiv.removeChild(logsDiv.lastChild); |
|
22 } |
|
23 logsDiv.appendChild(document.createElement('h3')) |
|
24 .appendChild(document.createTextNode('Logging:')); |
|
25 logs.forEach(function(logLine){ |
|
26 logsDiv.appendChild(document.createElement('div')) |
|
27 .appendChild(document.createTextNode(logLine)); |
|
28 }); |
|
29 } |
|
30 |
|
31 function candidateTypeString(cand) { |
|
32 if (cand.type == "localcandidate") { |
|
33 if (cand.candidateType == "relayed") { |
|
34 return cand.candidateType + '-' + cand.mozLocalTransport; |
|
35 } |
|
36 } |
|
37 return cand.candidateType; |
|
38 } |
|
39 |
|
40 function candidateAddrString(cand) { |
|
41 return cand.ipAddress + ':' + |
|
42 cand.portNumber + '/' + |
|
43 cand.transport + '(' + |
|
44 candidateTypeString(cand) + ')'; |
|
45 } |
|
46 |
|
47 function buildCandPairTableRow(candPair, localCand, remoteCand) { |
|
48 var row = document.createElement('tr'); |
|
49 row.onclick = function() { |
|
50 WebrtcGlobalInformation.getLogging("CAND-PAIR(" + row.id, displayLogs); |
|
51 } |
|
52 |
|
53 if (localCand) { |
|
54 row.appendChild(document.createElement('td')) |
|
55 .appendChild(document.createTextNode(candidateAddrString(localCand))); |
|
56 } else { |
|
57 row.appendChild(document.createElement('td')) |
|
58 .appendChild(document.createTextNode(candPair.localCandidateId)); |
|
59 } |
|
60 |
|
61 if (remoteCand) { |
|
62 row.appendChild(document.createElement('td')) |
|
63 .appendChild(document.createTextNode(candidateAddrString(remoteCand))); |
|
64 } else { |
|
65 row.appendChild(document.createElement('td')) |
|
66 .appendChild(document.createTextNode(candPair.remoteCandidateId)); |
|
67 } |
|
68 |
|
69 row.appendChild(document.createElement('td')) |
|
70 .appendChild(document.createTextNode(candPair.state)); |
|
71 row.appendChild(document.createElement('td')) |
|
72 .appendChild(document.createTextNode(candPair.mozPriority)); |
|
73 |
|
74 row.appendChild(document.createElement('td')) |
|
75 .appendChild(document.createTextNode(candPair.nominated ? '*' : '')); |
|
76 row.appendChild(document.createElement('td')) |
|
77 .appendChild(document.createTextNode(candPair.selected ? '*' : '')); |
|
78 return row; |
|
79 } |
|
80 |
|
81 function buildCandTableRow(cand) { |
|
82 var row = document.createElement('tr'); |
|
83 |
|
84 row.appendChild(document.createElement('td')) |
|
85 .appendChild(document.createTextNode(cand.ipAddress + ':' + |
|
86 cand.portNumber + '/' + |
|
87 cand.transport)); |
|
88 |
|
89 row.appendChild(document.createElement('td')) |
|
90 .appendChild(document.createTextNode(candidateTypeString(cand))); |
|
91 return row; |
|
92 } |
|
93 |
|
94 function buildCandPairTableHeader() { |
|
95 var headerRow = document.createElement('tr'); |
|
96 headerRow.appendChild(document.createElement('th')) |
|
97 .appendChild(document.createTextNode('Local candidate')); |
|
98 headerRow.appendChild(document.createElement('th')) |
|
99 .appendChild(document.createTextNode('Remote candidate')); |
|
100 headerRow.appendChild(document.createElement('th')) |
|
101 .appendChild(document.createTextNode('ICE State')); |
|
102 headerRow.appendChild(document.createElement('th')) |
|
103 .appendChild(document.createTextNode('Priority')); |
|
104 headerRow.appendChild(document.createElement('th')) |
|
105 .appendChild(document.createTextNode('Nominated')); |
|
106 headerRow.appendChild(document.createElement('th')) |
|
107 .appendChild(document.createTextNode('Selected')); |
|
108 return headerRow; |
|
109 } |
|
110 |
|
111 function buildCandTableHeader(isLocal) { |
|
112 var headerRow = document.createElement('tr'); |
|
113 headerRow.appendChild(document.createElement('th')) |
|
114 .appendChild(document.createTextNode(isLocal ? |
|
115 'Local candidate addr' : |
|
116 'Remote candidate addr')); |
|
117 headerRow.appendChild(document.createElement('th')) |
|
118 .appendChild(document.createTextNode('Type')); |
|
119 return headerRow; |
|
120 } |
|
121 |
|
122 function buildEmptyCandPairTable() { |
|
123 var candPairTable = document.createElement('table'); |
|
124 candPairTable.appendChild(buildCandPairTableHeader()); |
|
125 return candPairTable; |
|
126 } |
|
127 |
|
128 function buildEmptyCandTable(local) { |
|
129 var candTable = document.createElement('table'); |
|
130 candTable.appendChild(buildCandTableHeader(local)); |
|
131 return candTable; |
|
132 } |
|
133 |
|
134 function round00(num) { |
|
135 return Math.round(num * 100) / 100; |
|
136 } |
|
137 |
|
138 function dumpAvStat(stat) { |
|
139 var div = document.createElement('div'); |
|
140 var statsString = ""; |
|
141 if (stat.mozAvSyncDelay !== undefined) { |
|
142 statsString += "A/V sync: " + stat.mozAvSyncDelay + " ms "; |
|
143 } |
|
144 if (stat.mozJitterBufferDelay !== undefined) { |
|
145 statsString += "Jitter-buffer delay: " + stat.mozJitterBufferDelay + " ms"; |
|
146 } |
|
147 div.appendChild(document.createTextNode(statsString)); |
|
148 return div; |
|
149 } |
|
150 |
|
151 function dumpRtpStat(stat, label) { |
|
152 var div = document.createElement('div'); |
|
153 var statsString = " " + label + new Date(stat.timestamp).toTimeString() + |
|
154 " " + stat.type + " SSRC: " + stat.ssrc; |
|
155 if (stat.packetsReceived !== undefined) { |
|
156 statsString += " Received: " + stat.packetsReceived + " packets"; |
|
157 if (stat.bytesReceived !== undefined) { |
|
158 statsString += " (" + round00(stat.bytesReceived/1024) + " Kb)"; |
|
159 } |
|
160 statsString += " Lost: " + stat.packetsLost + " Jitter: " + stat.jitter; |
|
161 if (stat.mozRtt !== undefined) { |
|
162 statsString += " RTT: " + stat.mozRtt + " ms"; |
|
163 } |
|
164 } else if (stat.packetsSent !== undefined) { |
|
165 statsString += " Sent: " + stat.packetsSent + " packets"; |
|
166 if (stat.bytesSent !== undefined) { |
|
167 statsString += " (" + round00(stat.bytesSent/1024) + " Kb)"; |
|
168 } |
|
169 } |
|
170 div.appendChild(document.createTextNode(statsString)); |
|
171 return div; |
|
172 } |
|
173 |
|
174 function buildPcDiv(stats, pcDivHeading) { |
|
175 var newPcDiv = document.createElement('div'); |
|
176 |
|
177 var heading = document.createElement('h3'); |
|
178 heading.appendChild(document.createTextNode(pcDivHeading)); |
|
179 newPcDiv.appendChild(heading); |
|
180 |
|
181 // First, ICE stats |
|
182 var iceHeading = document.createElement('h4'); |
|
183 iceHeading.appendChild(document.createTextNode("ICE statistics")); |
|
184 newPcDiv.appendChild(iceHeading); |
|
185 |
|
186 var iceTablesByComponent = {}; |
|
187 |
|
188 function getIceTables(componentId) { |
|
189 if (!iceTablesByComponent[componentId]) { |
|
190 iceTablesByComponent[componentId] = { |
|
191 candidatePairTable: buildEmptyCandPairTable(), |
|
192 localCandidateTable: buildEmptyCandTable(true), |
|
193 remoteCandidateTable: buildEmptyCandTable(false) |
|
194 }; |
|
195 } |
|
196 return iceTablesByComponent[componentId]; |
|
197 } |
|
198 |
|
199 // Candidates |
|
200 var candidateMap = {}; // Used later to speed up recording of candidate pairs |
|
201 |
|
202 if (stats.iceCandidateStats) { |
|
203 stats.iceCandidateStats.forEach(function(cand) { |
|
204 var tables = getIceTables(cand.componentId); |
|
205 |
|
206 candidateMap[cand.id] = cand; |
|
207 |
|
208 if (cand.type == "localcandidate") { |
|
209 tables.localCandidateTable.appendChild(buildCandTableRow(cand)); |
|
210 } else { |
|
211 tables.remoteCandidateTable.appendChild(buildCandTableRow(cand)); |
|
212 } |
|
213 }); |
|
214 } |
|
215 |
|
216 // Candidate pairs |
|
217 if (stats.iceCandidatePairStats) { |
|
218 stats.iceCandidatePairStats.forEach(function(candPair) { |
|
219 var candPairTable = |
|
220 getIceTables(candPair.componentId).candidatePairTable; |
|
221 candPairTable.appendChild( |
|
222 buildCandPairTableRow(candPair, |
|
223 candidateMap[candPair.localCandidateId], |
|
224 candidateMap[candPair.remoteCandidateId])); |
|
225 }); |
|
226 } |
|
227 |
|
228 // Now that tables are completely built, put them on the page. |
|
229 for (var cid in iceTablesByComponent) { |
|
230 if (iceTablesByComponent.hasOwnProperty(cid)) { |
|
231 var tables = iceTablesByComponent[cid]; |
|
232 newPcDiv.appendChild(document.createElement('h4')) |
|
233 .appendChild(document.createTextNode(cid)); |
|
234 newPcDiv.appendChild(tables.candidatePairTable); |
|
235 newPcDiv.appendChild(tables.localCandidateTable); |
|
236 newPcDiv.appendChild(tables.remoteCandidateTable); |
|
237 } |
|
238 } |
|
239 |
|
240 // end of ICE stats |
|
241 |
|
242 // Now, RTP stats |
|
243 var rtpHeading = document.createElement('h4'); |
|
244 rtpHeading.appendChild(document.createTextNode("RTP statistics")); |
|
245 newPcDiv.appendChild(rtpHeading); |
|
246 |
|
247 // Build map from id -> remote RTP stats (ie; stats obtained from RTCP |
|
248 // from the other end). This allows us to pair up local/remote stats for |
|
249 // the same stream more easily. |
|
250 var remoteRtpStatsMap = {}; |
|
251 |
|
252 var addRemoteStatToMap = function (rtpStat) { |
|
253 if (rtpStat.isRemote) { |
|
254 remoteRtpStatsMap[rtpStat.id] = rtpStat; |
|
255 } |
|
256 } |
|
257 |
|
258 if (stats.inboundRTPStreamStats) { |
|
259 stats.inboundRTPStreamStats.forEach(addRemoteStatToMap); |
|
260 } |
|
261 |
|
262 if (stats.outboundRTPStreamStats) { |
|
263 stats.outboundRTPStreamStats.forEach(addRemoteStatToMap); |
|
264 } |
|
265 |
|
266 var addRtpStatPairToDocument = function (rtpStat) { |
|
267 if (!rtpStat.isRemote) { |
|
268 newPcDiv.appendChild(document.createElement('h5')) |
|
269 .appendChild(document.createTextNode(rtpStat.id)); |
|
270 if (rtpStat.mozAvSyncDelay !== undefined || |
|
271 rtpStat.mozJitterBufferDelay !== undefined) { |
|
272 newPcDiv.appendChild(dumpAvStat(rtpStat)); |
|
273 } |
|
274 newPcDiv.appendChild(dumpRtpStat(rtpStat, "Local: ")); |
|
275 |
|
276 // Might not be receiving RTCP, so we have no idea what the |
|
277 // statistics look like from the perspective of the other end. |
|
278 if (rtpStat.remoteId) { |
|
279 var remoteRtpStat = remoteRtpStatsMap[rtpStat.remoteId]; |
|
280 newPcDiv.appendChild(dumpRtpStat(remoteRtpStat, "Remote: ")); |
|
281 } |
|
282 } |
|
283 } |
|
284 |
|
285 if (stats.outboundRTPStreamStats) { |
|
286 stats.outboundRTPStreamStats.forEach(addRtpStatPairToDocument); |
|
287 } |
|
288 |
|
289 if (stats.inboundRTPStreamStats) { |
|
290 stats.inboundRTPStreamStats.forEach(addRtpStatPairToDocument); |
|
291 } |
|
292 |
|
293 return newPcDiv; |
|
294 } |
|
295 |
|
296 function displayStats(globalReport) { |
|
297 console.log("Got stats callback."); |
|
298 globalReport.reports.forEach(function (report) { |
|
299 var pcDivHeading = 'PeerConnection:' + report.pcid; |
|
300 |
|
301 var pcDiv = document.getElementById(pcDivHeading); |
|
302 var newPcDiv = buildPcDiv(report, pcDivHeading); |
|
303 newPcDiv.id = pcDivHeading; |
|
304 |
|
305 if (!pcDiv) { |
|
306 document.getElementById('stats').appendChild(newPcDiv); |
|
307 } else { |
|
308 document.getElementById('stats').replaceChild(newPcDiv, pcDiv); |
|
309 } |
|
310 }); |
|
311 |
|
312 globalReport.errors.forEach(function (error) { |
|
313 var pcDivHeading = 'PeerConnection:' + error.pcid; |
|
314 |
|
315 var pcDiv = document.getElementById(pcDivHeading); |
|
316 var newPcDiv = buildPcDiv(error, pcDivHeading); |
|
317 newPcDiv.id = pcDivHeading; |
|
318 |
|
319 if (pcDiv) { |
|
320 document.getElementById('stats').replaceChild(newPcDiv, pcDiv); |
|
321 } else { |
|
322 document.getElementById('stats').appendChild(newPcDiv); |
|
323 } |
|
324 }); |
|
325 } |
|
326 |
|
327 </script> |
|
328 |
|
329 <body id="body" onload="WebrtcGlobalInformation.getAllStats(displayStats)"> |
|
330 <div id="stats"> |
|
331 </div> |
|
332 <button onclick="WebrtcGlobalInformation.getLogging('', displayLogs)"> |
|
333 Show/refresh logging |
|
334 </button> |
|
335 <div id="logs"> |
|
336 </div> |
|
337 </body> |
|
338 </html> |
|
339 <!-- vim: softtabstop=2:shiftwidth=2:expandtab |
|
340 --> |