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;