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;