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.audioStreamListElement = document.getElementById('audio-stream-list');
9 this.propertiesTable = document.getElementById('property-table');
10 this.logTable = document.getElementById('log');
11 this.graphElement = document.getElementById('graphs');
13 this.selectedPlayer = null;
14 this.selectedStream = null;
16 this.selectedPlayerLogIndex = 0;
18 this.filterFunction = function() { return true; };
19 this.filterText = document.getElementById('filter-text');
20 this.filterText.onkeyup = this.onTextChange_.bind(this);
22 this.bufferCanvas = document.createElement('canvas');
23 this.bufferCanvas.width = media.BAR_WIDTH;
24 this.bufferCanvas.height = media.BAR_HEIGHT;
26 this.clipboardTextarea = document.getElementById('clipboard-textarea');
27 this.clipboardButton = document.getElementById('copy-button');
28 this.clipboardButton.onclick = this.copyToClipboard_.bind(this);
31 function removeChildren(element) {
32 while (element.hasChildNodes()) {
33 element.removeChild(element.lastChild);
37 function createButton(text, select_cb) {
38 var button = document.createElement('button');
40 button.appendChild(document.createTextNode(text));
41 button.onclick = function() {
48 ClientRenderer.prototype = {
49 audioStreamAdded: function(audioStreams, audioStreamAdded) {
50 this.redrawAudioStreamList_(audioStreams);
53 audioStreamUpdated: function(audioStreams, stream, key, value) {
54 if (stream === this.selectedStream) {
55 this.drawProperties_(stream);
59 audioStreamRemoved: function(audioStreams, audioStreamRemoved) {
60 this.redrawAudioStreamList_(audioStreams);
64 * Called when a player is added to the collection.
65 * @param players The entire map of id -> player.
66 * @param player_added The player that is added.
68 playerAdded: function(players, playerAdded) {
69 this.redrawPlayerList_(players);
73 * Called when a playre is removed from the collection.
74 * @param players The entire map of id -> player.
75 * @param player_added The player that was removed.
77 playerRemoved: function(players, playerRemoved) {
78 this.redrawPlayerList_(players);
82 * Called when a property on a player is changed.
83 * @param players The entire map of id -> player.
84 * @param player The player that had its property changed.
85 * @param key The name of the property that was changed.
86 * @param value The new value of the property.
88 playerUpdated: function(players, player, key, value) {
89 if (player === this.selectedPlayer) {
90 this.drawProperties_(player.properties);
94 if (key === 'name' || key === 'url') {
95 this.redrawPlayerList_(players);
99 redrawAudioStreamList_: function(streams) {
100 removeChildren(this.audioStreamListElement);
102 for (id in streams) {
103 var li = document.createElement('li');
104 li.appendChild(createButton(
105 id, this.selectAudioStream_.bind(this, streams[id])));
106 this.audioStreamListElement.appendChild(li);
110 selectAudioStream_: function(audioStream) {
111 this.selectedStream = audioStream;
112 this.selectedPlayer = null;
113 this.drawProperties_(audioStream);
114 removeChildren(this.logTable.querySelector('tbody'));
115 removeChildren(this.graphElement);
118 redrawPlayerList_: function(players) {
119 removeChildren(this.playerListElement);
121 for (id in players) {
122 var li = document.createElement('li');
123 var player = players[id];
124 var usableName = player.properties.name ||
125 player.properties.url ||
126 'player ' + player.id;
128 li.appendChild(createButton(
129 usableName, this.selectPlayer_.bind(this, player)));
130 this.playerListElement.appendChild(li);
134 selectPlayer_: function(player) {
135 this.selectedPlayer = player;
136 this.selectedPlayerLogIndex = 0;
137 this.selectedStream = null;
138 this.drawProperties_(player.properties);
140 removeChildren(this.logTable.querySelector('tbody'));
141 removeChildren(this.graphElement);
146 drawProperties_: function(propertyMap) {
147 removeChildren(this.propertiesTable);
149 for (key in propertyMap) {
150 var value = propertyMap[key];
152 var row = this.propertiesTable.insertRow(-1);
153 var keyCell = row.insertCell(-1);
154 var valueCell = row.insertCell(-1);
156 keyCell.appendChild(document.createTextNode(key));
157 valueCell.appendChild(document.createTextNode(value));
161 appendEventToLog_: function(event) {
162 if (this.filterFunction(event.key)) {
163 var row = this.logTable.querySelector('tbody').insertRow(-1);
165 row.insertCell(-1).appendChild(document.createTextNode(
166 util.millisecondsToString(event.time)));
167 row.insertCell(-1).appendChild(document.createTextNode(event.key));
168 row.insertCell(-1).appendChild(document.createTextNode(event.value));
172 drawLog_: function() {
173 var toDraw = this.selectedPlayer.allEvents.slice(
174 this.selectedPlayerLogIndex);
175 toDraw.forEach(this.appendEventToLog_.bind(this));
176 this.selectedPlayerLogIndex = this.selectedPlayer.allEvents.length;
179 drawGraphs_: function() {
180 function addToGraphs(name, graph, graphElement) {
181 var li = document.createElement('li');
182 li.appendChild(graph);
183 li.appendChild(document.createTextNode(name));
184 graphElement.appendChild(li);
187 var url = this.selectedPlayer.properties.url;
192 var cache = media.cacheForUrl(url);
194 var player = this.selectedPlayer;
195 var props = player.properties;
197 var cacheExists = false;
198 var bufferExists = false;
200 if (props['buffer_start'] !== undefined &&
201 props['buffer_current'] !== undefined &&
202 props['buffer_end'] !== undefined &&
203 props['total_bytes'] !== undefined) {
204 this.drawBufferGraph_(props['buffer_start'],
205 props['buffer_current'],
207 props['total_bytes']);
212 if (player.properties['total_bytes']) {
213 cache.size = Number(player.properties['total_bytes']);
215 cache.generateDetails();
220 if (!this.graphElement.hasChildNodes()) {
222 addToGraphs('buffer', this.bufferCanvas, this.graphElement);
225 addToGraphs('cache read', cache.readCanvas, this.graphElement);
226 addToGraphs('cache write', cache.writeCanvas, this.graphElement);
231 drawBufferGraph_: function(start, current, end, size) {
232 var ctx = this.bufferCanvas.getContext('2d');
233 var width = this.bufferCanvas.width;
234 var height = this.bufferCanvas.height;
235 ctx.fillStyle = '#aaa';
236 ctx.fillRect(0, 0, width, height);
238 var scale_factor = width / size;
239 var left = start * scale_factor;
240 var middle = current * scale_factor;
241 var right = end * scale_factor;
243 ctx.fillStyle = '#a0a';
244 ctx.fillRect(left, 0, middle - left, height);
245 ctx.fillStyle = '#aa0';
246 ctx.fillRect(middle, 0, right - middle, height);
249 copyToClipboard_: function() {
250 var properties = this.selectedStream ||
251 this.selectedPlayer.properties || false;
255 var stringBuffer = [];
257 for (var key in properties) {
258 var value = properties[key];
259 stringBuffer.push(key.toString());
260 stringBuffer.push(': ');
261 stringBuffer.push(value.toString());
262 stringBuffer.push('\n');
265 this.clipboardTextarea.value = stringBuffer.join('');
266 this.clipboardTextarea.classList.remove('hidden');
267 this.clipboardTextarea.focus();
268 this.clipboardTextarea.select();
270 // The act of copying anything from the textarea gets canceled
271 // if the element in question gets the class 'hidden' (which contains the
272 // css property display:none) before the event is finished. For this, it
273 // is necessary put the property setting on the event loop to be executed
274 // after the copy has taken place.
275 this.clipboardTextarea.oncopy = function(event) {
276 setTimeout(function(element) {
277 event.target.classList.add('hidden');
282 onTextChange_: function(event) {
283 var text = this.filterText.value.toLowerCase();
284 var parts = text.split(',').map(function(part) {
286 }).filter(function(part) {
287 return part.trim().length > 0;
290 this.filterFunction = function(text) {
291 text = text.toLowerCase();
292 return parts.length === 0 || parts.some(function(part) {
293 return text.indexOf(part) != -1;
297 if (this.selectedPlayer) {
298 removeChildren(this.logTable.querySelector('tbody'));
299 this.selectedPlayerLogIndex = 0;
305 return ClientRenderer;