1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 var ClientRenderer
= (function() {
6 var ClientRenderer = function() {
7 this.playerListElement
= document
.getElementById('player-list');
9 document
.getElementById('property-table').querySelector('tbody');
10 this.logTable
= document
.getElementById('log').querySelector('tbody');
11 this.graphElement
= document
.getElementById('graphs');
12 this.propertyName
= document
.getElementById('property-name');
14 this.selectedPlayer
= null;
15 this.selectedAudioComponentType
= null;
16 this.selectedAudioComponentId
= null;
17 this.selectedAudioCompontentData
= null;
19 this.selectedPlayerLogIndex
= 0;
21 this.filterFunction = function() { return true; };
22 this.filterText
= document
.getElementById('filter-text');
23 this.filterText
.onkeyup
= this.onTextChange_
.bind(this);
25 this.bufferCanvas
= document
.createElement('canvas');
26 this.bufferCanvas
.width
= media
.BAR_WIDTH
;
27 this.bufferCanvas
.height
= media
.BAR_HEIGHT
;
29 this.clipboardTextarea
= document
.getElementById('clipboard-textarea');
30 this.clipboardButton
= document
.getElementById('copy-button');
31 this.clipboardButton
.onclick
= this.copyToClipboard_
.bind(this);
33 this.hiddenKeys
= ['component_id', 'component_type', 'owner_id'];
36 function removeChildren(element
) {
37 while (element
.hasChildNodes()) {
38 element
.removeChild(element
.lastChild
);
42 function createButton(text
, select_cb
) {
43 var button
= document
.createElement('button');
45 button
.appendChild(document
.createTextNode(text
));
46 button
.onclick = function() {
53 ClientRenderer
.prototype = {
55 * Called when an audio component is added to the collection.
56 * @param componentType Integer AudioComponent enum value; must match values
57 * from the AudioLogFactory::AudioComponent enum.
58 * @param components The entire map of components (name -> dict).
60 audioComponentAdded: function(componentType
, components
) {
61 this.redrawAudioComponentList_(componentType
, components
);
63 // Redraw the component if it's currently selected.
64 if (this.selectedAudioComponentType
== componentType
&&
65 this.selectedAudioComponentId
&&
66 this.selectedAudioComponentId
in components
) {
67 this.selectAudioComponent_(
68 componentType
, this.selectedAudioComponentId
,
69 components
[this.selectedAudioComponentId
]);
74 * Called when an audio component is removed from the collection.
75 * @param componentType Integer AudioComponent enum value; must match values
76 * from the AudioLogFactory::AudioComponent enum.
77 * @param components The entire map of components (name -> dict).
79 audioComponentRemoved: function(componentType
, components
) {
80 this.redrawAudioComponentList_(componentType
, components
);
82 // Clear the component if it was previously currently selected.
83 if (this.selectedAudioComponentType
== componentType
&&
84 !(this.selectedAudioComponentId
in components
)) {
85 this.selectAudioComponent_(null, null, {});
90 * Called when a player is added to the collection.
91 * @param players The entire map of id -> player.
92 * @param player_added The player that is added.
94 playerAdded: function(players
, playerAdded
) {
95 this.redrawPlayerList_(players
);
99 * Called when a playre is removed from the collection.
100 * @param players The entire map of id -> player.
101 * @param player_added The player that was removed.
103 playerRemoved: function(players
, playerRemoved
) {
104 this.redrawPlayerList_(players
);
108 * Called when a property on a player is changed.
109 * @param players The entire map of id -> player.
110 * @param player The player that had its property changed.
111 * @param key The name of the property that was changed.
112 * @param value The new value of the property.
114 playerUpdated: function(players
, player
, key
, value
) {
115 if (player
=== this.selectedPlayer
) {
116 this.drawProperties_(player
.properties
);
120 if (key
=== 'name' || key
=== 'url') {
121 this.redrawPlayerList_(players
);
125 createVideoCaptureFormatTable: function(formats
) {
126 if (!formats
|| formats
.length
== 0)
127 return document
.createTextNode('No formats');
129 var table
= document
.createElement('table');
130 var thead
= document
.createElement('thead');
131 var theadRow
= document
.createElement('tr');
132 for (var key
in formats
[0]) {
133 var th
= document
.createElement('th');
134 th
.appendChild(document
.createTextNode(key
));
135 theadRow
.appendChild(th
);
137 thead
.appendChild(theadRow
);
138 table
.appendChild(thead
);
139 var tbody
= document
.createElement('tbody');
140 for (var i
=0; i
< formats
.length
; ++i
) {
141 var tr
= document
.createElement('tr')
142 for (var key
in formats
[i
]) {
143 var td
= document
.createElement('td');
144 td
.appendChild(document
.createTextNode(formats
[i
][key
]));
147 tbody
.appendChild(tr
);
149 table
.appendChild(tbody
);
150 table
.classList
.add('video-capture-formats-table');
154 redrawVideoCaptureCapabilities: function(videoCaptureCapabilities
, keys
) {
155 var copyButtonElement
=
156 document
.getElementById('video-capture-capabilities-copy-button');
157 copyButtonElement
.onclick = function() {
158 window
.prompt('Copy to clipboard: Ctrl+C, Enter',
159 JSON
.stringify(videoCaptureCapabilities
))
162 var videoTableBodyElement
=
163 document
.getElementById('video-capture-capabilities-tbody');
164 removeChildren(videoTableBodyElement
);
166 for (var component
in videoCaptureCapabilities
) {
167 var tableRow
= document
.createElement('tr');
168 var device
= videoCaptureCapabilities
[ component
];
169 for (var i
in keys
) {
170 var value
= device
[keys
[i
]];
171 var tableCell
= document
.createElement('td');
173 if ((typeof value
) == (typeof [])) {
174 cellElement
= this.createVideoCaptureFormatTable(value
);
176 cellElement
= document
.createTextNode(
177 ((typeof value
) == 'undefined') ? 'n/a' : value
);
179 tableCell
.appendChild(cellElement
)
180 tableRow
.appendChild(tableCell
);
182 videoTableBodyElement
.appendChild(tableRow
);
186 redrawAudioComponentList_: function(componentType
, components
) {
187 function redrawList(renderer
, baseName
, element
) {
188 var fragment
= document
.createDocumentFragment();
189 for (id
in components
) {
190 var li
= document
.createElement('li');
191 var friendlyName
= baseName
+ ' ' + id
;
192 li
.appendChild(createButton(
193 friendlyName
, renderer
.selectAudioComponent_
.bind(
194 renderer
, componentType
, id
, components
[id
], friendlyName
)));
195 fragment
.appendChild(li
);
197 removeChildren(element
);
198 element
.appendChild(fragment
);
201 switch (componentType
) {
203 redrawList(this, 'Controller', document
.getElementById(
204 'audio-input-controller-list'));
207 redrawList(this, 'Controller', document
.getElementById(
208 'audio-output-controller-list'));
211 redrawList(this, 'Stream', document
.getElementById(
212 'audio-output-stream-list'));
219 selectAudioComponent_: function(
220 componentType
, componentId
, componentData
, friendlyName
) {
221 this.selectedPlayer
= null;
222 this.selectedAudioComponentType
= componentType
;
223 this.selectedAudioComponentId
= componentId
;
224 this.selectedAudioCompontentData
= componentData
;
225 this.drawProperties_(componentData
);
226 removeChildren(this.logTable
);
227 removeChildren(this.graphElement
);
229 removeChildren(this.propertyName
);
230 this.propertyName
.appendChild(document
.createTextNode(friendlyName
));
233 redrawPlayerList_: function(players
) {
234 var fragment
= document
.createDocumentFragment();
235 for (id
in players
) {
236 var player
= players
[id
];
237 var usableName
= player
.properties
.name
||
238 player
.properties
.url
||
239 'Player ' + player
.id
;
241 var li
= document
.createElement('li');
242 li
.appendChild(createButton(
243 usableName
, this.selectPlayer_
.bind(this, player
)));
244 fragment
.appendChild(li
);
246 removeChildren(this.playerListElement
);
247 this.playerListElement
.appendChild(fragment
);
250 selectPlayer_: function(player
) {
251 this.selectedPlayer
= player
;
252 this.selectedPlayerLogIndex
= 0;
253 this.selectedAudioComponentType
= null;
254 this.selectedAudioComponentId
= null;
255 this.selectedAudioCompontentData
= null;
256 this.drawProperties_(player
.properties
);
258 removeChildren(this.logTable
);
259 removeChildren(this.graphElement
);
262 removeChildren(this.propertyName
);
263 this.propertyName
.appendChild(document
.createTextNode('Player'));
266 drawProperties_: function(propertyMap
) {
267 removeChildren(this.propertiesTable
);
268 var sortedKeys
= Object
.keys(propertyMap
).sort();
269 for (var i
= 0; i
< sortedKeys
.length
; ++i
) {
270 var key
= sortedKeys
[i
];
271 if (this.hiddenKeys
.indexOf(key
) >= 0)
274 var value
= propertyMap
[key
];
275 var row
= this.propertiesTable
.insertRow(-1);
276 var keyCell
= row
.insertCell(-1);
277 var valueCell
= row
.insertCell(-1);
279 keyCell
.appendChild(document
.createTextNode(key
));
280 valueCell
.appendChild(document
.createTextNode(value
));
284 appendEventToLog_: function(event
) {
285 if (this.filterFunction(event
.key
)) {
286 var row
= this.logTable
.insertRow(-1);
288 var timestampCell
= row
.insertCell(-1);
289 timestampCell
.classList
.add('timestamp');
290 timestampCell
.appendChild(document
.createTextNode(
291 util
.millisecondsToString(event
.time
)));
292 row
.insertCell(-1).appendChild(document
.createTextNode(event
.key
));
293 row
.insertCell(-1).appendChild(document
.createTextNode(event
.value
));
297 drawLog_: function() {
298 var toDraw
= this.selectedPlayer
.allEvents
.slice(
299 this.selectedPlayerLogIndex
);
300 toDraw
.forEach(this.appendEventToLog_
.bind(this));
301 this.selectedPlayerLogIndex
= this.selectedPlayer
.allEvents
.length
;
304 drawGraphs_: function() {
305 function addToGraphs(name
, graph
, graphElement
) {
306 var li
= document
.createElement('li');
307 li
.appendChild(graph
);
308 li
.appendChild(document
.createTextNode(name
));
309 graphElement
.appendChild(li
);
312 var url
= this.selectedPlayer
.properties
.url
;
317 var cache
= media
.cacheForUrl(url
);
319 var player
= this.selectedPlayer
;
320 var props
= player
.properties
;
322 var cacheExists
= false;
323 var bufferExists
= false;
325 if (props
['buffer_start'] !== undefined &&
326 props
['buffer_current'] !== undefined &&
327 props
['buffer_end'] !== undefined &&
328 props
['total_bytes'] !== undefined) {
329 this.drawBufferGraph_(props
['buffer_start'],
330 props
['buffer_current'],
332 props
['total_bytes']);
337 if (player
.properties
['total_bytes']) {
338 cache
.size
= Number(player
.properties
['total_bytes']);
340 cache
.generateDetails();
345 if (!this.graphElement
.hasChildNodes()) {
347 addToGraphs('buffer', this.bufferCanvas
, this.graphElement
);
350 addToGraphs('cache read', cache
.readCanvas
, this.graphElement
);
351 addToGraphs('cache write', cache
.writeCanvas
, this.graphElement
);
356 drawBufferGraph_: function(start
, current
, end
, size
) {
357 var ctx
= this.bufferCanvas
.getContext('2d');
358 var width
= this.bufferCanvas
.width
;
359 var height
= this.bufferCanvas
.height
;
360 ctx
.fillStyle
= '#aaa';
361 ctx
.fillRect(0, 0, width
, height
);
363 var scale_factor
= width
/ size
;
364 var left
= start
* scale_factor
;
365 var middle
= current
* scale_factor
;
366 var right
= end
* scale_factor
;
368 ctx
.fillStyle
= '#a0a';
369 ctx
.fillRect(left
, 0, middle
- left
, height
);
370 ctx
.fillStyle
= '#aa0';
371 ctx
.fillRect(middle
, 0, right
- middle
, height
);
374 copyToClipboard_: function() {
375 var properties
= this.selectedAudioCompontentData
||
376 this.selectedPlayer
.properties
|| false;
380 var stringBuffer
= [];
382 for (var key
in properties
) {
383 var value
= properties
[key
];
384 stringBuffer
.push(key
.toString());
385 stringBuffer
.push(': ');
386 stringBuffer
.push(value
.toString());
387 stringBuffer
.push('\n');
390 this.clipboardTextarea
.value
= stringBuffer
.join('');
391 this.clipboardTextarea
.classList
.remove('hiddenClipboard');
392 this.clipboardTextarea
.focus();
393 this.clipboardTextarea
.select();
395 // Hide the clipboard element when it loses focus.
396 this.clipboardTextarea
.onblur = function(event
) {
397 setTimeout(function(element
) {
398 event
.target
.classList
.add('hiddenClipboard');
403 onTextChange_: function(event
) {
404 var text
= this.filterText
.value
.toLowerCase();
405 var parts
= text
.split(',').map(function(part
) {
407 }).filter(function(part
) {
408 return part
.trim().length
> 0;
411 this.filterFunction = function(text
) {
412 text
= text
.toLowerCase();
413 return parts
.length
=== 0 || parts
.some(function(part
) {
414 return text
.indexOf(part
) != -1;
418 if (this.selectedPlayer
) {
419 removeChildren(this.logTable
);
420 this.selectedPlayerLogIndex
= 0;
426 return ClientRenderer
;