• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4
5var 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');
13
14    this.selectedPlayer = null;
15    this.selectedAudioComponentType = null;
16    this.selectedAudioComponentId = null;
17    this.selectedAudioCompontentData = null;
18
19    this.selectedPlayerLogIndex = 0;
20
21    this.filterFunction = function() { return true; };
22    this.filterText = document.getElementById('filter-text');
23    this.filterText.onkeyup = this.onTextChange_.bind(this);
24
25    this.bufferCanvas = document.createElement('canvas');
26    this.bufferCanvas.width = media.BAR_WIDTH;
27    this.bufferCanvas.height = media.BAR_HEIGHT;
28
29    this.clipboardTextarea = document.getElementById('clipboard-textarea');
30    this.clipboardButton = document.getElementById('copy-button');
31    this.clipboardButton.onclick = this.copyToClipboard_.bind(this);
32
33    this.hiddenKeys = ['component_id', 'component_type', 'owner_id'];
34  };
35
36  function removeChildren(element) {
37    while (element.hasChildNodes()) {
38      element.removeChild(element.lastChild);
39    }
40  };
41
42  function createButton(text, select_cb) {
43    var button = document.createElement('button');
44
45    button.appendChild(document.createTextNode(text));
46    button.onclick = function() {
47      select_cb();
48    };
49
50    return button;
51  };
52
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);
62
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    },
72
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);
81
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    },
88
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    },
97
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    },
106
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    },
124
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);
135        }
136        removeChildren(element);
137        element.appendChild(fragment);
138      }
139
140      switch (componentType) {
141        case 0:
142          redrawList(this, 'Controller', document.getElementById(
143              'audio-input-controller-list'));
144          break;
145        case 1:
146          redrawList(this, 'Controller', document.getElementById(
147              'audio-output-controller-list'));
148          break;
149        case 2:
150          redrawList(this, 'Stream', document.getElementById(
151              'audio-output-stream-list'));
152          break;
153        default:
154          break;
155      }
156    },
157
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);
167
168      removeChildren(this.propertyName);
169      this.propertyName.appendChild(document.createTextNode(friendlyName));
170    },
171
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;
179
180        var li = document.createElement('li');
181        li.appendChild(createButton(
182            usableName, this.selectPlayer_.bind(this, player)));
183        fragment.appendChild(li);
184      }
185      removeChildren(this.playerListElement);
186      this.playerListElement.appendChild(fragment);
187    },
188
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);
196
197      removeChildren(this.logTable);
198      removeChildren(this.graphElement);
199      this.drawLog_();
200      this.drawGraphs_();
201      removeChildren(this.propertyName);
202      this.propertyName.appendChild(document.createTextNode('Player'));
203    },
204
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)
211          continue;
212
213        var value = propertyMap[key];
214        var row = this.propertiesTable.insertRow(-1);
215        var keyCell = row.insertCell(-1);
216        var valueCell = row.insertCell(-1);
217
218        keyCell.appendChild(document.createTextNode(key));
219        valueCell.appendChild(document.createTextNode(value));
220      }
221    },
222
223    appendEventToLog_: function(event) {
224      if (this.filterFunction(event.key)) {
225        var row = this.logTable.insertRow(-1);
226
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));
233      }
234    },
235
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;
241    },
242
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);
249      }
250
251      var url = this.selectedPlayer.properties.url;
252      if (!url) {
253        return;
254      }
255
256      var cache = media.cacheForUrl(url);
257
258      var player = this.selectedPlayer;
259      var props = player.properties;
260
261      var cacheExists = false;
262      var bufferExists = false;
263
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'],
270                              props['buffer_end'],
271                              props['total_bytes']);
272        bufferExists = true;
273      }
274
275      if (cache) {
276        if (player.properties['total_bytes']) {
277          cache.size = Number(player.properties['total_bytes']);
278        }
279        cache.generateDetails();
280        cacheExists = true;
281
282      }
283
284      if (!this.graphElement.hasChildNodes()) {
285        if (bufferExists) {
286          addToGraphs('buffer', this.bufferCanvas, this.graphElement);
287        }
288        if (cacheExists) {
289          addToGraphs('cache read', cache.readCanvas, this.graphElement);
290          addToGraphs('cache write', cache.writeCanvas, this.graphElement);
291        }
292      }
293    },
294
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);
301
302      var scale_factor = width / size;
303      var left = start * scale_factor;
304      var middle = current * scale_factor;
305      var right = end * scale_factor;
306
307      ctx.fillStyle = '#a0a';
308      ctx.fillRect(left, 0, middle - left, height);
309      ctx.fillStyle = '#aa0';
310      ctx.fillRect(middle, 0, right - middle, height);
311    },
312
313    copyToClipboard_: function() {
314      var properties = this.selectedAudioCompontentData ||
315          this.selectedPlayer.properties || false;
316      if (!properties) {
317        return;
318      }
319      var stringBuffer = [];
320
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');
327      }
328
329      this.clipboardTextarea.value = stringBuffer.join('');
330      this.clipboardTextarea.classList.remove('hiddenClipboard');
331      this.clipboardTextarea.focus();
332      this.clipboardTextarea.select();
333
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');
338        }, 0);
339      };
340    },
341
342    onTextChange_: function(event) {
343      var text = this.filterText.value.toLowerCase();
344      var parts = text.split(',').map(function(part) {
345        return part.trim();
346      }).filter(function(part) {
347        return part.trim().length > 0;
348      });
349
350      this.filterFunction = function(text) {
351        text = text.toLowerCase();
352        return parts.length === 0 || parts.some(function(part) {
353          return text.indexOf(part) != -1;
354        });
355      };
356
357      if (this.selectedPlayer) {
358        removeChildren(this.logTable);
359        this.selectedPlayerLogIndex = 0;
360        this.drawLog_();
361      }
362    },
363  };
364
365  return ClientRenderer;
366})();
367