Apply _RELATIVE relocations ahead of others.
[chromium-blink-merge.git] / content / browser / resources / media / client_renderer.js
blob0eedb35df468e133347b387ead8e741e12b5854d
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');
8     this.propertiesTable =
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'];
34   };
36   function removeChildren(element) {
37     while (element.hasChildNodes()) {
38       element.removeChild(element.lastChild);
39     }
40   };
42   function createButton(text, select_cb) {
43     var button = document.createElement('button');
45     button.appendChild(document.createTextNode(text));
46     button.onclick = function() {
47       select_cb();
48     };
50     return button;
51   };
53   ClientRenderer.prototype = {
54     /**
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).
59      */
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]);
70       }
71     },
73     /**
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).
78      */
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, {});
86       }
87     },
89     /**
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.
93      */
94     playerAdded: function(players, playerAdded) {
95       this.redrawPlayerList_(players);
96     },
98     /**
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.
102      */
103     playerRemoved: function(players, playerRemoved) {
104       this.redrawPlayerList_(players);
105     },
107     /**
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.
113      */
114     playerUpdated: function(players, player, key, value) {
115       if (player === this.selectedPlayer) {
116         this.drawProperties_(player.properties);
117         this.drawLog_();
118         this.drawGraphs_();
119       }
120       if (key === 'name' || key === 'url') {
121         this.redrawPlayerList_(players);
122       }
123     },
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);
136       }
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]));
145           tr.appendChild(td);
146         }
147         tbody.appendChild(tr);
148       }
149       table.appendChild(tbody);
150       table.classList.add('video-capture-formats-table');
151       return table;
152     },
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))
160       }
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');
172           var cellElement;
173           if ((typeof value) == (typeof [])) {
174             cellElement = this.createVideoCaptureFormatTable(value);
175           } else {
176             cellElement = document.createTextNode(
177                 ((typeof value) == 'undefined') ? 'n/a' : value);
178           }
179           tableCell.appendChild(cellElement)
180           tableRow.appendChild(tableCell);
181         }
182         videoTableBodyElement.appendChild(tableRow);
183       }
184     },
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);
196         }
197         removeChildren(element);
198         element.appendChild(fragment);
199       }
201       switch (componentType) {
202         case 0:
203           redrawList(this, 'Controller', document.getElementById(
204               'audio-input-controller-list'));
205           break;
206         case 1:
207           redrawList(this, 'Controller', document.getElementById(
208               'audio-output-controller-list'));
209           break;
210         case 2:
211           redrawList(this, 'Stream', document.getElementById(
212               'audio-output-stream-list'));
213           break;
214         default:
215           break;
216       }
217     },
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));
231     },
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);
245       }
246       removeChildren(this.playerListElement);
247       this.playerListElement.appendChild(fragment);
248     },
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);
260       this.drawLog_();
261       this.drawGraphs_();
262       removeChildren(this.propertyName);
263       this.propertyName.appendChild(document.createTextNode('Player'));
264     },
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)
272           continue;
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));
281       }
282     },
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));
294       }
295     },
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;
302     },
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);
310       }
312       var url = this.selectedPlayer.properties.url;
313       if (!url) {
314         return;
315       }
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'],
331                               props['buffer_end'],
332                               props['total_bytes']);
333         bufferExists = true;
334       }
336       if (cache) {
337         if (player.properties['total_bytes']) {
338           cache.size = Number(player.properties['total_bytes']);
339         }
340         cache.generateDetails();
341         cacheExists = true;
343       }
345       if (!this.graphElement.hasChildNodes()) {
346         if (bufferExists) {
347           addToGraphs('buffer', this.bufferCanvas, this.graphElement);
348         }
349         if (cacheExists) {
350           addToGraphs('cache read', cache.readCanvas, this.graphElement);
351           addToGraphs('cache write', cache.writeCanvas, this.graphElement);
352         }
353       }
354     },
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);
372     },
374     copyToClipboard_: function() {
375       var properties = this.selectedAudioCompontentData ||
376           this.selectedPlayer.properties || false;
377       if (!properties) {
378         return;
379       }
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');
388       }
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');
399         }, 0);
400       };
401     },
403     onTextChange_: function(event) {
404       var text = this.filterText.value.toLowerCase();
405       var parts = text.split(',').map(function(part) {
406         return part.trim();
407       }).filter(function(part) {
408         return part.trim().length > 0;
409       });
411       this.filterFunction = function(text) {
412         text = text.toLowerCase();
413         return parts.length === 0 || parts.some(function(part) {
414           return text.indexOf(part) != -1;
415         });
416       };
418       if (this.selectedPlayer) {
419         removeChildren(this.logTable);
420         this.selectedPlayerLogIndex = 0;
421         this.drawLog_();
422       }
423     },
424   };
426   return ClientRenderer;
427 })();