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 redrawAudioComponentList_: function(componentType
, components
) {
126 function redrawList(renderer
, baseName
, element
) {
127 var fragment
= document
.createDocumentFragment();
128 for (id
in components
) {
129 var li
= document
.createElement('li');
130 var friendlyName
= baseName
+ ' ' + id
;
131 li
.appendChild(createButton(
132 friendlyName
, renderer
.selectAudioComponent_
.bind(
133 renderer
, componentType
, id
, components
[id
], friendlyName
)));
134 fragment
.appendChild(li
);
136 removeChildren(element
);
137 element
.appendChild(fragment
);
140 switch (componentType
) {
142 redrawList(this, 'Controller', document
.getElementById(
143 'audio-input-controller-list'));
146 redrawList(this, 'Controller', document
.getElementById(
147 'audio-output-controller-list'));
150 redrawList(this, 'Stream', document
.getElementById(
151 'audio-output-stream-list'));
158 selectAudioComponent_: function(
159 componentType
, componentId
, componentData
, friendlyName
) {
160 this.selectedPlayer
= null;
161 this.selectedAudioComponentType
= componentType
;
162 this.selectedAudioComponentId
= componentId
;
163 this.selectedAudioCompontentData
= componentData
;
164 this.drawProperties_(componentData
);
165 removeChildren(this.logTable
);
166 removeChildren(this.graphElement
);
168 removeChildren(this.propertyName
);
169 this.propertyName
.appendChild(document
.createTextNode(friendlyName
));
172 redrawPlayerList_: function(players
) {
173 var fragment
= document
.createDocumentFragment();
174 for (id
in players
) {
175 var player
= players
[id
];
176 var usableName
= player
.properties
.name
||
177 player
.properties
.url
||
178 'Player ' + player
.id
;
180 var li
= document
.createElement('li');
181 li
.appendChild(createButton(
182 usableName
, this.selectPlayer_
.bind(this, player
)));
183 fragment
.appendChild(li
);
185 removeChildren(this.playerListElement
);
186 this.playerListElement
.appendChild(fragment
);
189 selectPlayer_: function(player
) {
190 this.selectedPlayer
= player
;
191 this.selectedPlayerLogIndex
= 0;
192 this.selectedAudioComponentType
= null;
193 this.selectedAudioComponentId
= null;
194 this.selectedAudioCompontentData
= null;
195 this.drawProperties_(player
.properties
);
197 removeChildren(this.logTable
);
198 removeChildren(this.graphElement
);
201 removeChildren(this.propertyName
);
202 this.propertyName
.appendChild(document
.createTextNode('Player'));
205 drawProperties_: function(propertyMap
) {
206 removeChildren(this.propertiesTable
);
207 var sortedKeys
= Object
.keys(propertyMap
).sort();
208 for (var i
= 0; i
< sortedKeys
.length
; ++i
) {
209 var key
= sortedKeys
[i
];
210 if (this.hiddenKeys
.indexOf(key
) >= 0)
213 var value
= propertyMap
[key
];
214 var row
= this.propertiesTable
.insertRow(-1);
215 var keyCell
= row
.insertCell(-1);
216 var valueCell
= row
.insertCell(-1);
218 keyCell
.appendChild(document
.createTextNode(key
));
219 valueCell
.appendChild(document
.createTextNode(value
));
223 appendEventToLog_: function(event
) {
224 if (this.filterFunction(event
.key
)) {
225 var row
= this.logTable
.insertRow(-1);
227 var timestampCell
= row
.insertCell(-1);
228 timestampCell
.classList
.add('timestamp');
229 timestampCell
.appendChild(document
.createTextNode(
230 util
.millisecondsToString(event
.time
)));
231 row
.insertCell(-1).appendChild(document
.createTextNode(event
.key
));
232 row
.insertCell(-1).appendChild(document
.createTextNode(event
.value
));
236 drawLog_: function() {
237 var toDraw
= this.selectedPlayer
.allEvents
.slice(
238 this.selectedPlayerLogIndex
);
239 toDraw
.forEach(this.appendEventToLog_
.bind(this));
240 this.selectedPlayerLogIndex
= this.selectedPlayer
.allEvents
.length
;
243 drawGraphs_: function() {
244 function addToGraphs(name
, graph
, graphElement
) {
245 var li
= document
.createElement('li');
246 li
.appendChild(graph
);
247 li
.appendChild(document
.createTextNode(name
));
248 graphElement
.appendChild(li
);
251 var url
= this.selectedPlayer
.properties
.url
;
256 var cache
= media
.cacheForUrl(url
);
258 var player
= this.selectedPlayer
;
259 var props
= player
.properties
;
261 var cacheExists
= false;
262 var bufferExists
= false;
264 if (props
['buffer_start'] !== undefined &&
265 props
['buffer_current'] !== undefined &&
266 props
['buffer_end'] !== undefined &&
267 props
['total_bytes'] !== undefined) {
268 this.drawBufferGraph_(props
['buffer_start'],
269 props
['buffer_current'],
271 props
['total_bytes']);
276 if (player
.properties
['total_bytes']) {
277 cache
.size
= Number(player
.properties
['total_bytes']);
279 cache
.generateDetails();
284 if (!this.graphElement
.hasChildNodes()) {
286 addToGraphs('buffer', this.bufferCanvas
, this.graphElement
);
289 addToGraphs('cache read', cache
.readCanvas
, this.graphElement
);
290 addToGraphs('cache write', cache
.writeCanvas
, this.graphElement
);
295 drawBufferGraph_: function(start
, current
, end
, size
) {
296 var ctx
= this.bufferCanvas
.getContext('2d');
297 var width
= this.bufferCanvas
.width
;
298 var height
= this.bufferCanvas
.height
;
299 ctx
.fillStyle
= '#aaa';
300 ctx
.fillRect(0, 0, width
, height
);
302 var scale_factor
= width
/ size
;
303 var left
= start
* scale_factor
;
304 var middle
= current
* scale_factor
;
305 var right
= end
* scale_factor
;
307 ctx
.fillStyle
= '#a0a';
308 ctx
.fillRect(left
, 0, middle
- left
, height
);
309 ctx
.fillStyle
= '#aa0';
310 ctx
.fillRect(middle
, 0, right
- middle
, height
);
313 copyToClipboard_: function() {
314 var properties
= this.selectedAudioCompontentData
||
315 this.selectedPlayer
.properties
|| false;
319 var stringBuffer
= [];
321 for (var key
in properties
) {
322 var value
= properties
[key
];
323 stringBuffer
.push(key
.toString());
324 stringBuffer
.push(': ');
325 stringBuffer
.push(value
.toString());
326 stringBuffer
.push('\n');
329 this.clipboardTextarea
.value
= stringBuffer
.join('');
330 this.clipboardTextarea
.classList
.remove('hiddenClipboard');
331 this.clipboardTextarea
.focus();
332 this.clipboardTextarea
.select();
334 // Hide the clipboard element when it loses focus.
335 this.clipboardTextarea
.onblur = function(event
) {
336 setTimeout(function(element
) {
337 event
.target
.classList
.add('hiddenClipboard');
342 onTextChange_: function(event
) {
343 var text
= this.filterText
.value
.toLowerCase();
344 var parts
= text
.split(',').map(function(part
) {
346 }).filter(function(part
) {
347 return part
.trim().length
> 0;
350 this.filterFunction = function(text
) {
351 text
= text
.toLowerCase();
352 return parts
.length
=== 0 || parts
.some(function(part
) {
353 return text
.indexOf(part
) != -1;
357 if (this.selectedPlayer
) {
358 removeChildren(this.logTable
);
359 this.selectedPlayerLogIndex
= 0;
365 return ClientRenderer
;