Commit 294d9e01 authored by scherkus@chromium.org's avatar scherkus@chromium.org

Display active media players on chrome://media-internals.

Patch by scottfr@chromium.org.

Review URL: http://codereview.chromium.org/7972028

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107146 0039d316-1c4b-4281-b951-d872f2087c98
parent 86c6b9e3
...@@ -9,11 +9,14 @@ found in the LICENSE file. ...@@ -9,11 +9,14 @@ found in the LICENSE file.
<link rel="stylesheet" href="webui.css" /> <link rel="stylesheet" href="webui.css" />
<link rel="stylesheet" href="media_internals/media_internals.css" /> <link rel="stylesheet" href="media_internals/media_internals.css" />
<script src="chrome://resources/js/cr.js"></script> <script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/cr/ui.js"></script>
<script src="chrome://media-internals/media_internals.js"></script> <script src="chrome://media-internals/media_internals.js"></script>
<script src="chrome://media-internals/strings.js"></script> <script src="chrome://media-internals/strings.js"></script>
<title>Media Internals</title> <title>Media Internals</title>
</head> </head>
<body> <body>
<h2>Active media players:</h2>
<ul id="media-players"></ul>
<h2>Active audio streams:</h2> <h2>Active audio streams:</h2>
<div id="audio-streams"></div> <div id="audio-streams"></div>
<h2>Cached resources:</h2> <h2>Cached resources:</h2>
......
...@@ -3,12 +3,7 @@ ...@@ -3,12 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
cr.define('media', function() { cr.define('media', function() {
'use strict';
/**
* The width and height of a CacheEntry canvas in pixels.
*/
var CANVAS_WIDTH = 500;
var CANVAS_HEIGHT = 31;
/** /**
* This class represents a file cached by net. * This class represents a file cached by net.
...@@ -50,19 +45,25 @@ cr.define('media', function() { ...@@ -50,19 +45,25 @@ cr.define('media', function() {
clearControl.textContent = '(clear entry)'; clearControl.textContent = '(clear entry)';
controls.appendChild(clearControl); controls.appendChild(clearControl);
// The canvas upon which to draw the cached sections of a file.
this.canvas_ = document.createElement('canvas');
this.canvas_.width = CANVAS_WIDTH;
this.canvas_.height = CANVAS_HEIGHT;
summary.appendChild(this.canvas_);
this.details_.appendChild(summary); this.details_.appendChild(summary);
// The canvas for drawing cache writes.
this.writeCanvas = document.createElement('canvas');
this.writeCanvas.width = media.BAR_WIDTH;
this.writeCanvas.height = media.BAR_HEIGHT;
this.details_.appendChild(this.writeCanvas);
// The canvas for drawing cache reads.
this.readCanvas = document.createElement('canvas');
this.readCanvas.width = media.BAR_WIDTH;
this.readCanvas.height = media.BAR_HEIGHT;
this.details_.appendChild(this.readCanvas);
// A tabular representation of the data in the above canvas. // A tabular representation of the data in the above canvas.
this.detailTable_ = document.createElement('table'); this.detailTable_ = document.createElement('table');
this.detailTable_.className = 'cache-table'; this.detailTable_.className = 'cache-table';
this.details_.appendChild(this.detailTable_); this.details_.appendChild(this.detailTable_);
}; }
CacheEntry.prototype = { CacheEntry.prototype = {
/** /**
...@@ -115,57 +116,59 @@ cr.define('media', function() { ...@@ -115,57 +116,59 @@ cr.define('media', function() {
}, },
/** /**
* Redraw this.canvas_. * Helper for drawCacheReadsToCanvas() and drawCacheWritesToCanvas().
* It should consist of two horizontal bars with highlighted sections to *
* represent which parts of a file have been read from (top) and written to * Accepts the entries to draw, a canvas fill style, and the canvas to
* (bottom) the cache. * draw on.
* e.g. |xxxxxx----------x|
* |-----xxxxx-------|
*/ */
generateCanvas: function() { drawCacheEntriesToCanvas: function(entries, fillStyle, canvas) {
var context = this.canvas_.getContext('2d'); // Don't bother drawing anything if we don't know the total size.
context.textAlign = 'center'; if (!this.size) {
context.textBaseline = 'middle'; return;
}
var width = canvas.width;
var height = canvas.height;
var context = canvas.getContext('2d');
var fileSize = this.size;
context.fillStyle = '#aaa'; context.fillStyle = '#aaa';
context.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); context.fillRect(0, 0, width, height);
if (this.size) { function drawRange(start, end) {
var fileSize = this.size; var left = start / fileSize * width;
drawRange = function(start, end, top) { var right = end / fileSize * width;
var left = start / fileSize * CANVAS_WIDTH; context.fillRect(left, 0, right - left, height);
var right = end / fileSize * CANVAS_WIDTH;
context.fillRect(left, top, right - left, top + CANVAS_HEIGHT / 2);
};
context.fillStyle = '#0a0';
this.read_.map(function(start, end) {
drawRange(start, end, 0);
});
context.fillStyle = '#00a';
this.written_.map(function(start, end) {
drawRange(start, end, CANVAS_HEIGHT / 2);
});
// Overlay a description of each bar.
context.fillStyle = '#fff';
context.fillText('Read from cache.', CANVAS_WIDTH / 2,
CANVAS_HEIGHT / 4 - 0.5);
context.fillText('Written to cache.', CANVAS_WIDTH / 2,
CANVAS_HEIGHT * 3 / 4 + 0.5);
// Add a 1px separator line.
context.moveTo(0, CANVAS_HEIGHT / 2);
context.lineTo(CANVAS_WIDTH, CANVAS_HEIGHT / 2);
context.strokeStyle = '#fff';
context.stroke();
} else {
// We can't draw bars if we don't know how big the file is.
context.fillStyle = '#fff';
context.fillText('Unknown file size. See details below.',
CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2);
} }
context.fillStyle = fillStyle;
entries.map(function(start, end) {
drawRange(start, end);
});
},
/**
* Draw cache writes to the given canvas.
*
* It should consist of a horizontal bar with highlighted sections to
* represent which parts of a file have been written to the cache.
*
* e.g. |xxxxxx----------x|
*/
drawCacheWritesToCanvas: function(canvas) {
this.drawCacheEntriesToCanvas(this.written_, '#00a', canvas);
},
/**
* Draw cache reads to the given canvas.
*
* It should consist of a horizontal bar with highlighted sections to
* represent which parts of a file have been read from the cache.
*
* e.g. |xxxxxx----------x|
*/
drawCacheReadsToCanvas: function(canvas) {
this.drawCacheEntriesToCanvas(this.read_, '#0a0', canvas);
}, },
/** /**
...@@ -173,12 +176,6 @@ cr.define('media', function() { ...@@ -173,12 +176,6 @@ cr.define('media', function() {
* this file. * this file.
*/ */
generateDetails: function() { generateDetails: function() {
function makeElement(type, content) {
var element = document.createElement(type);
element.textContent = content;
return element;
};
this.details_.id = this.key; this.details_.id = this.key;
this.summaryText_.textContent = this.key || 'Unknown File'; this.summaryText_.textContent = this.key || 'Unknown File';
...@@ -191,8 +188,8 @@ cr.define('media', function() { ...@@ -191,8 +188,8 @@ cr.define('media', function() {
this.detailTable_.appendChild(body); this.detailTable_.appendChild(body);
var headerRow = document.createElement('tr'); var headerRow = document.createElement('tr');
headerRow.appendChild(makeElement('th', 'Read From Cache')); headerRow.appendChild(media.makeElement('th', 'Read From Cache'));
headerRow.appendChild(makeElement('th', 'Written To Cache')); headerRow.appendChild(media.makeElement('th', 'Written To Cache'));
header.appendChild(headerRow); header.appendChild(headerRow);
var footerRow = document.createElement('tr'); var footerRow = document.createElement('tr');
...@@ -212,12 +209,13 @@ cr.define('media', function() { ...@@ -212,12 +209,13 @@ cr.define('media', function() {
var length = Math.max(read.length, written.length); var length = Math.max(read.length, written.length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var row = document.createElement('tr'); var row = document.createElement('tr');
row.appendChild(makeElement('td', read[i] || '')); row.appendChild(media.makeElement('td', read[i] || ''));
row.appendChild(makeElement('td', written[i] || '')); row.appendChild(media.makeElement('td', written[i] || ''));
body.appendChild(row); body.appendChild(row);
} }
this.generateCanvas(); this.drawCacheWritesToCanvas(this.writeCanvas);
this.drawCacheReadsToCanvas(this.readCanvas);
}, },
/** /**
......
...@@ -18,7 +18,7 @@ cr.define('media', function() { ...@@ -18,7 +18,7 @@ cr.define('media', function() {
*/ */
function DisjointRangeSet() { function DisjointRangeSet() {
this.ranges_ = {}; this.ranges_ = {};
}; }
DisjointRangeSet.prototype = { DisjointRangeSet.prototype = {
/** /**
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('media', function() {
'use strict';
/**
* This class holds a list of MediaLogEvents.
* It inherits from <li> and contains a tabular list of said events,
* the time at which they occurred, and their parameters.
*/
var EventList = cr.ui.define('li');
EventList.prototype = {
__proto__: HTMLLIElement.prototype,
startTime_: null,
/**
* Decorate this list item as an EventList.
*/
decorate: function() {
this.table_ = document.createElement('table');
var details = document.createElement('details');
var summary = media.makeElement('summary', 'Log:');
details.appendChild(summary);
details.appendChild(this.table_);
this.appendChild(details);
var hRow = document.createElement('tr');
hRow.appendChild(media.makeElement('th', 'Time:'));
hRow.appendChild(media.makeElement('th', 'Event:'));
hRow.appendChild(media.makeElement('th', 'Parameters:'));
var header = document.createElement('thead');
header.appendChild(hRow);
this.table_.appendChild(header);
},
/**
* Add an event to the list. It is stored as a new row in this.table_.
* @param {Object} event The MediaLogEvent that has occurred.
*/
addEvent: function(event) {
var timeInMs = event.time * 1000; // Work with milliseconds.
this.startTime_ = this.startTime_ || timeInMs;
timeInMs -= this.startTime_;
var row = document.createElement('tr');
row.appendChild(media.makeElement('td', timeInMs.toFixed(1)));
row.appendChild(media.makeElement('td', event.type));
var params = [];
for (var i = 0; i < event.params.length; ++i) {
params.push(i + ': ' + event.params[i]);
}
row.appendChild(media.makeElement('td', params.join(', ')));
this.table_.appendChild(row);
}
};
return {
EventList: EventList
};
});
...@@ -11,7 +11,7 @@ cr.define('media', function() { ...@@ -11,7 +11,7 @@ cr.define('media', function() {
*/ */
function ItemStore() { function ItemStore() {
this.items_ = {}; this.items_ = {};
}; }
ItemStore.prototype = { ItemStore.prototype = {
/** /**
......
...@@ -3,12 +3,21 @@ ...@@ -3,12 +3,21 @@
* found in the LICENSE file. * found in the LICENSE file.
*/ */
body {
font-family: sans-serif;
}
h2 { h2 {
margin: 15px 0px 5px 0px; margin: 15px 0 5px 0;
}
ul, p, canvas {
margin: 0;
} }
ul, p { #media-players td,
margin: 0px; #media-players th {
padding: 0 10px;
} }
.audio-stream[status='created'] { .audio-stream[status='created'] {
...@@ -23,31 +32,46 @@ ul, p { ...@@ -23,31 +32,46 @@ ul, p {
color: red; color: red;
} }
#cache-entries ul { #cache-entries ul,
#media-players ul {
list-style-type: none; list-style-type: none;
} }
.cache-entry { .cache-entry {
margin: 0px 0px 5px 0px; margin: 0 0 5px 0;
} }
.cache-entry-controls { .cache-entry-controls {
font-size: smaller; font-size: smaller;
} }
.cache-entry summary {
text-indent: -1em;
}
.cache-table { .cache-table {
table-layout: fixed; table-layout: fixed;
width: 500px; width: 500px;
} }
.cache-table thead { thead {
text-align: left; text-align: left;
} }
.cache-table tfoot { tfoot {
text-align: right; text-align: right;
} }
.buffered {
display: table;
}
.buffered > div {
display: table-row;
}
.buffered > div > div {
display: table-cell;
vertical-align: bottom;
}
.buffered > div > div:first-child {
font-weight: bold;
padding-right: 2px;
}
...@@ -3,14 +3,22 @@ ...@@ -3,14 +3,22 @@
// found in the LICENSE file. // found in the LICENSE file.
<include src="cache_entry.js"/> <include src="cache_entry.js"/>
<include src="item_store.js"/>
<include src="disjoint_range_set.js"/> <include src="disjoint_range_set.js"/>
<include src="event_list.js"/>
<include src="item_store.js"/>
<include src="media_player.js"/>
<include src="metrics.js"/>
<include src="util.js"/>
cr.define('media', function() { cr.define('media', function() {
'use strict';
// Stores information on open audio streams, referenced by id. // Stores information on open audio streams, referenced by id.
var audioStreams = new media.ItemStore; var audioStreams = new media.ItemStore;
// Active media players, indexed by 'render_id:player_id'.
var mediaPlayers = {};
// Cached files indexed by key and source id. // Cached files indexed by key and source id.
var cacheEntriesByKey = {}; var cacheEntriesByKey = {};
var cacheEntries = {}; var cacheEntries = {};
...@@ -26,27 +34,31 @@ cr.define('media', function() { ...@@ -26,27 +34,31 @@ cr.define('media', function() {
var audioStreamDiv; var audioStreamDiv;
var cacheDiv; var cacheDiv;
// A timer used to limit the rate of redrawing the Media Players section.
var redrawTimer = null;
/** /**
* Initialize variables and ask MediaInternals for all its data. * Initialize variables and ask MediaInternals for all its data.
*/ */
initialize = function() { function initialize() {
audioStreamDiv = document.getElementById('audio-streams'); audioStreamDiv = document.getElementById('audio-streams');
cacheDiv = document.getElementById('cache-entries'); cacheDiv = document.getElementById('cache-entries');
// Get information about all currently active media. // Get information about all currently active media.
chrome.send('getEverything'); chrome.send('getEverything');
}; }
/** /**
* Write the set of audio streams to the DOM. * Write the set of audio streams to the DOM.
*/ */
printAudioStreams = function() { function printAudioStreams() {
/** /**
* Render a single stream as a <li>. * Render a single stream as a <li>.
* @param {Object} stream The stream to render. * @param {Object} stream The stream to render.
* @return {HTMLElement} A <li> containing the stream information. * @return {HTMLElement} A <li> containing the stream information.
*/ */
printStream = function(stream) { function printStream(stream) {
var out = document.createElement('li'); var out = document.createElement('li');
out.id = stream.id; out.id = stream.id;
out.className = 'audio-stream'; out.className = 'audio-stream';
...@@ -54,9 +66,10 @@ cr.define('media', function() { ...@@ -54,9 +66,10 @@ cr.define('media', function() {
out.textContent += 'Audio stream ' + stream.id.split('.')[1]; out.textContent += 'Audio stream ' + stream.id.split('.')[1];
out.textContent += ' is ' + (stream.playing ? 'playing' : 'paused'); out.textContent += ' is ' + (stream.playing ? 'playing' : 'paused');
out.textContent += ' at ' + Math.round(stream.volume * 100) + '% volume.'; out.textContent += ' at ' + (stream.volume * 100).toFixed(0);
out.textContent += '% volume.';
return out; return out;
}; }
var out = document.createElement('ul'); var out = document.createElement('ul');
audioStreams.map(printStream).forEach(function(s) { audioStreams.map(printStream).forEach(function(s) {
...@@ -65,12 +78,22 @@ cr.define('media', function() { ...@@ -65,12 +78,22 @@ cr.define('media', function() {
audioStreamDiv.textContent = ''; audioStreamDiv.textContent = '';
audioStreamDiv.appendChild(out); audioStreamDiv.appendChild(out);
}; }
/**
* Redraw each MediaPlayer.
*/
function printMediaPlayers() {
for (var key in mediaPlayers) {
mediaPlayers[key].redraw();
}
redrawTimer = null;
}
/** /**
* Write the set of sparse CacheEntries to the DOM. * Write the set of sparse CacheEntries to the DOM.
*/ */
printSparseCacheEntries = function() { function printSparseCacheEntries() {
var out = document.createElement('ul'); var out = document.createElement('ul');
for (var key in cacheEntriesByKey) { for (var key in cacheEntriesByKey) {
if (cacheEntriesByKey[key].sparse) if (cacheEntriesByKey[key].sparse)
...@@ -79,17 +102,17 @@ cr.define('media', function() { ...@@ -79,17 +102,17 @@ cr.define('media', function() {
cacheDiv.textContent = ''; cacheDiv.textContent = '';
cacheDiv.appendChild(out); cacheDiv.appendChild(out);
}; }
/** /**
* Receiving data for an audio stream. * Receiving data for an audio stream.
* Add it to audioStreams and update the page. * Add it to audioStreams and update the page.
* @param {Object} stream JSON representation of an audio stream. * @param {Object} stream JSON representation of an audio stream.
*/ */
addAudioStream = function(stream) { function addAudioStream(stream) {
audioStreams.addItem(stream); audioStreams.addItem(stream);
printAudioStreams(); printAudioStreams();
}; }
/** /**
* Receiving all data. * Receiving all data.
...@@ -97,32 +120,47 @@ cr.define('media', function() { ...@@ -97,32 +120,47 @@ cr.define('media', function() {
* @param {Object} stuff JSON containing lists of data. * @param {Object} stuff JSON containing lists of data.
* @param {Object} stuff.audio_streams A dictionary of audio streams. * @param {Object} stuff.audio_streams A dictionary of audio streams.
*/ */
onReceiveEverything = function(stuff) { function onReceiveEverything(stuff) {
audioStreams.addItems(stuff.audio_streams); audioStreams.addItems(stuff.audio_streams);
printAudioStreams(); printAudioStreams();
}; }
/** /**
* Removing an item from the appropriate store. * Removing an item from the appropriate store.
* @param {string} id The id of the item to be removed, in the format * @param {string} id The id of the item to be removed, in the format
* "item_type.identifying_info". * "item_type.identifying_info".
*/ */
onItemDeleted = function(id) { function onItemDeleted(id) {
var type = id.split('.')[0]; var type = id.split('.')[0];
switch (type) { switch (type) {
case 'audio_streams': case 'audio_streams':
audioStreams.removeItem(id); audioStreams.removeItem(id);
printAudioStreams(); printAudioStreams();
break; break;
} }
}; }
/**
* A render process has ended, delete any media players associated with it.
* @param {number} renderer The id of the render process.
*/
function onRendererTerminated(renderer) {
for (var key in mediaPlayers) {
if (mediaPlayers[key].renderer == renderer) {
document.getElementById('media-players').removeChild(mediaPlayers[key]);
delete mediaPlayers[key];
break;
}
}
printMediaPlayers();
}
/** /**
* Receiving net events. * Receiving net events.
* Update cache information and update that section of the page. * Update cache information and update that section of the page.
* @param {Array} updates A list of net events that have occurred. * @param {Array} updates A list of net events that have occurred.
*/ */
onNetUpdate = function(updates) { function onNetUpdate(updates) {
updates.forEach(function(update) { updates.forEach(function(update) {
var id = update.source.id; var id = update.source.id;
if (!cacheEntries[id]) if (!cacheEntries[id])
...@@ -166,7 +204,7 @@ cr.define('media', function() { ...@@ -166,7 +204,7 @@ cr.define('media', function() {
delete requestURLs[update.source.id]; delete requestURLs[update.source.id];
if (range && key) { if (range && key) {
if (!cacheEntriesByKey[key]) { if (!cacheEntriesByKey[key]) {
cacheEntriesByKey[key] = new CacheEntry; cacheEntriesByKey[key] = new media.CacheEntry;
cacheEntriesByKey[key].key = key; cacheEntriesByKey[key].key = key;
} }
cacheEntriesByKey[key].size = range[1]; cacheEntriesByKey[key].size = range[1];
...@@ -176,7 +214,7 @@ cr.define('media', function() { ...@@ -176,7 +214,7 @@ cr.define('media', function() {
}); });
printSparseCacheEntries(); printSparseCacheEntries();
}; }
/** /**
* Receiving values for constants. Store them for later use. * Receiving values for constants. Store them for later use.
...@@ -184,29 +222,58 @@ cr.define('media', function() { ...@@ -184,29 +222,58 @@ cr.define('media', function() {
* @param {Object} constants.eventTypes A dictionary of event name -> int. * @param {Object} constants.eventTypes A dictionary of event name -> int.
* @param {Object} constants.eventPhases A dictionary of event phase -> int. * @param {Object} constants.eventPhases A dictionary of event phase -> int.
*/ */
onReceiveConstants = function(constants) { function onReceiveConstants(constants) {
var events = constants.eventTypes; var events = constants.eventTypes;
for (var e in events) for (var e in events) {
eventTypes[events[e]] = e; eventTypes[events[e]] = e;
}
var phases = constants.eventPhases; var phases = constants.eventPhases;
for (var p in phases) for (var p in phases) {
eventPhases[phases[p]] = p; eventPhases[phases[p]] = p;
}; }
}
/**
* Receiving notification of a media event.
* @param {Object} event The json representation of a MediaLogEvent.
*/
function onMediaEvent(event) {
var source = event.renderer + ':' + event.player;
var item = mediaPlayers[source] ||
new media.MediaPlayer({id: source, renderer: event.renderer});
mediaPlayers[source] = item;
item.addEvent(event);
// Both media and net events could provide the size of the file.
// Media takes priority, but keep the size in both places synchronized.
if (cacheEntriesByKey[item.properties.url]) {
item.properties.total_bytes = item.properties.total_bytes ||
cacheEntriesByKey[item.properties.url].size;
cacheEntriesByKey[item.properties.url].size = item.properties.total_bytes;
}
// Events tend to arrive in groups; don't redraw the page too often.
if (!redrawTimer)
redrawTimer = setTimeout(printMediaPlayers, 50);
}
return { return {
initialize: initialize, initialize: initialize,
addAudioStream: addAudioStream, addAudioStream: addAudioStream,
cacheEntriesByKey: cacheEntriesByKey,
onReceiveEverything: onReceiveEverything, onReceiveEverything: onReceiveEverything,
onItemDeleted: onItemDeleted, onItemDeleted: onItemDeleted,
onRendererTerminated: onRendererTerminated,
onNetUpdate: onNetUpdate, onNetUpdate: onNetUpdate,
onReceiveConstants: onReceiveConstants, onReceiveConstants: onReceiveConstants,
onMediaEvent: onMediaEvent
}; };
}); });
/** /**
* Initialize everything once we have access to the DOM. * Initialize everything once we have access to the DOM.
*/ */
window.onload = function() { document.addEventListener('DOMContentLoaded', function() {
media.initialize(); media.initialize();
}; });
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('media', function() {
'use strict';
/**
* This class inherits from <li> and is designed to store and display
* information about an open media player.
*/
var MediaPlayer = cr.ui.define('li');
MediaPlayer.prototype = {
__proto__: HTMLLIElement.prototype,
renderer: null,
id: null,
/**
* Decorate this <li> as a MediaPlayer.
*/
decorate: function() {
this.properties = {};
this.details_ = document.createElement('details');
this.summary_ = document.createElement('summary');
this.details_.appendChild(this.summary_);
this.propertyTable_ = document.createElement('table');
this.events_ = new media.EventList;
this.metrics_ = new media.Metrics;
var properties = media.createDetailsLi();
properties.summary.textContent = 'Properties:';
properties.details.appendChild(this.propertyTable_);
var ul = document.createElement('ul');
ul.appendChild(properties);
ul.appendChild(this.metrics_);
ul.appendChild(this.events_);
this.details_.appendChild(ul);
this.appendChild(this.details_);
document.getElementById('media-players').appendChild(this);
},
/**
* Record an event and update statistics etc.
* @param {Object} event The event that occurred.
*/
addEvent: function(event) {
for (var key in event.params) {
this.properties[key] = event.params[key];
}
if (event.type == 'BUFFERED_EXTENTS_CHANGED')
return;
this.events_.addEvent(event);
this.metrics_.addEvent(event);
},
/**
* Update the summary line and properties table and redraw the canvas.
* @return {HTMLElement} A <li> representing this MediaPlayer.
*/
redraw: function() {
media.appendDictionaryToTable(this.properties, this.propertyTable_);
this.setAttribute('status', this.properties.state);
this.summary_.textContent = '';
this.summary_.appendChild(document.createTextNode(
this.id + ' (' + this.properties.url + '):'));
this.summary_.appendChild(document.createElement('br'));
// Don't bother drawing anything if we don't know the total size.
var size = this.properties.total_bytes;
if (!size) {
return;
}
var bufferedDiv = document.createElement('div');
bufferedDiv.className = 'buffered';
this.summary_.appendChild(bufferedDiv);
function addEntry(label, canvas) {
var labelDiv = document.createElement('div');
labelDiv.textContent = label;
var canvasDiv = document.createElement('div');
canvasDiv.appendChild(canvas);
var entryDiv = document.createElement('div');
entryDiv.appendChild(labelDiv);
entryDiv.appendChild(canvasDiv);
bufferedDiv.appendChild(entryDiv);
}
// Draw the state of BufferedResourceLoader.
var canvas = document.createElement('canvas');
canvas.width = media.BAR_WIDTH;
canvas.height = media.BAR_HEIGHT;
var context = canvas.getContext('2d');
context.fillStyle = '#aaa';
context.fillRect(0, 0, canvas.width, canvas.height);
var left = this.properties.buffer_start / size * canvas.width;
var middle = this.properties.buffer_current / size * canvas.width;
var right = this.properties.buffer_end / size * canvas.width;
context.fillStyle = '#a0a';
context.fillRect(left, 0, middle - left, canvas.height);
context.fillStyle = '#aa0';
context.fillRect(middle, 0, right - middle, canvas.height);
addEntry('Buffered', canvas);
// Only show cached file information if we have something.
var cacheEntry = media.cacheEntriesByKey[this.properties.url];
if (!cacheEntry) {
return;
}
// Draw cache reads.
canvas = document.createElement('canvas');
canvas.width = media.BAR_WIDTH;
canvas.height = media.BAR_HEIGHT;
cacheEntry.drawCacheReadsToCanvas(canvas);
addEntry('Cache Reads', canvas);
// Draw cache writes.
canvas = document.createElement('canvas');
canvas.width = media.BAR_WIDTH;
canvas.height = media.BAR_HEIGHT;
cacheEntry.drawCacheWritesToCanvas(canvas);
addEntry('Cache Writes', canvas);
},
};
return {
MediaPlayer: MediaPlayer
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('media', function() {
'use strict';
// A set of parameter names. An entry of 'abc' allows metrics to specify
// events with specific values of 'abc'.
var metricProperties = {
'pipeline_state': true,
};
// A set of metrics to measure. The user will see the most recent and average
// measurement of the time between each metric's start and end events.
var metrics = {
'seek': {
'start': 'SEEK',
'end': 'pipeline_state=started'
},
'first frame': {
'start': 'WEBMEDIAPLAYER_CREATED',
'end': 'pipeline_state=started'
},
};
/**
* This class measures times between the events specified above. It inherits
* <li> and contains a table that displays the measurements.
*/
var Metrics = cr.ui.define('li');
Metrics.prototype = {
__proto__: HTMLLIElement.prototype,
/**
* Decorate this <li> as a Metrics.
*/
decorate: function() {
this.table_ = document.createElement('table');
var details = document.createElement('details');
var summary = media.makeElement('summary', 'Metrics:');
details.appendChild(summary);
details.appendChild(this.table_);
this.appendChild(details);
var hRow = document.createElement('tr');
hRow.appendChild(media.makeElement('th', 'Metric:'));
hRow.appendChild(media.makeElement('th', 'Last Measure:'));
hRow.appendChild(media.makeElement('th', 'Average:'));
var header = document.createElement('thead');
header.appendChild(hRow);
this.table_.appendChild(header);
for (var metric in metrics) {
var last = document.createElement('td');
var avg = document.createElement('td');
this[metric] = {
count: 0,
total: 0,
start: null,
last: last,
avg: avg
};
var row = document.createElement('tr');
row.appendChild(media.makeElement('td', metric + ':'));
row.appendChild(last);
row.appendChild(avg);
this.table_.appendChild(row);
}
},
/**
* An event has occurred. Update any metrics that refer to this type
* of event. Can be called multiple times by addEvent below if the metrics
* refer to specific parameters.
* @param {Object} event The MediaLogEvent that has occurred.
* @param {string} type The type of event.
*/
addEventInternal: function(event, type) {
var timeInMs = event.time * 1000; // Work with milliseconds.
for (var metric in metrics) {
var m = this[metric];
if (type == metrics[metric].start && !m.start) {
m.start = timeInMs;
} else if (type == metrics[metric].end && m.start != null) {
var last = timeInMs - m.start;
m.last.textContent = last.toFixed(1);
m.total += last;
m.count++;
if (m.count > 1)
m.avg.textContent = (m.total / m.count).toFixed(1);
m.start = null;
}
}
},
/**
* An event has occurred. Update any metrics that refer to events of this
* type or with this event's parameters.
* @param {Object} event The MediaLogEvent that has occurred.
*/
addEvent: function(event) {
this.addEventInternal(event, event.type);
for (var p in event.params) {
if (p in metricProperties) {
var type = p + '=' + event.params[p];
this.addEventInternal(event, type);
}
}
},
};
return {
Metrics: Metrics,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('media', function() {
'use strict';
/**
* The width and height of a bar drawn on a file canvas in pixels.
*/
var BAR_WIDTH = 500;
var BAR_HEIGHT = 16;
/**
* Draws a 1px white horizontal line across |context|.
*/
function drawLine(context, top) {
context.moveTo(0, top);
context.lineTo(BAR_WIDTH, top);
context.strokeStyle = '#fff';
context.stroke();
}
/**
* Creates an HTMLElement of type |type| with textContent |content|.
* @param {string} type The type of element to create.
* @param {string} content The content to place in the element.
* @return {HTMLElement} A newly initialized element.
*/
function makeElement(type, content) {
var element = document.createElement(type);
element.textContent = content;
return element;
}
/**
* Creates a new <li> containing a <details> with a <summary> and sets
* properties to reference them.
* @return {Object} The new <li>.
*/
function createDetailsLi() {
var li = document.createElement('li');
li.details = document.createElement('details');
li.summary = document.createElement('summary');
li.appendChild(li.details);
li.details.appendChild(li.summary);
return li
}
/**
* Appends each key-value pair in a dictionary to a row in a table.
* @param {Object} dict The dictionary to append.
* @param {HTMLElement} table The <table> element to append to.
*/
function appendDictionaryToTable(dict, table) {
table.textContent = '';
for (var key in dict) {
var tr = document.createElement('tr');
tr.appendChild(makeElement('td', key + ':'));
tr.appendChild(makeElement('td', dict[key]));
table.appendChild(tr);
}
return table;
}
return {
BAR_WIDTH: BAR_WIDTH,
BAR_HEIGHT: BAR_HEIGHT,
drawLine: drawLine,
makeElement: makeElement,
createDetailsLi: createDetailsLi,
appendDictionaryToTable: appendDictionaryToTable
};
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment