// 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';
// Stores information on open audio streams, referenced by id.
var audioStreams = new media.ItemStore;
// Active media players, indexed by 'render_id:player_id'.
var mediaPlayers = {};
// Cached files indexed by key and source id.
var cacheEntriesByKey = {};
var cacheEntries = {};
// Map of event source -> url.
var requestURLs = {};
// Constants passed to us from Chrome.
var eventTypes = {};
var eventPhases = {};
// The
s on the page in which to display information.
var audioStreamDiv;
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.
*/
function initialize() {
audioStreamDiv = document.getElementById('audio-streams');
cacheDiv = document.getElementById('cache-entries');
// Get information about all currently active media.
chrome.send('getEverything');
}
/**
* Write the set of audio streams to the DOM.
*/
function printAudioStreams() {
/**
* Render a single stream as a
.
* @param {Object} stream The stream to render.
* @return {HTMLElement} A containing the stream information.
*/
function printStream(stream) {
var out = document.createElement('li');
out.id = stream.id;
out.className = 'audio-stream';
out.setAttribute('status', stream.status);
out.textContent += 'Audio stream ' + stream.id.split('.')[1];
out.textContent += ' is ' + (stream.playing ? 'playing' : 'paused');
out.textContent += ' at ' + (stream.volume * 100).toFixed(0);
out.textContent += '% volume.';
return out;
}
var out = document.createElement('ul');
audioStreams.map(printStream).forEach(function(s) {
out.appendChild(s)
});
audioStreamDiv.textContent = '';
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.
*/
function printSparseCacheEntries() {
var out = document.createElement('ul');
for (var key in cacheEntriesByKey) {
if (cacheEntriesByKey[key].sparse)
out.appendChild(cacheEntriesByKey[key].toListItem());
}
cacheDiv.textContent = '';
cacheDiv.appendChild(out);
}
/**
* Receiving data for an audio stream.
* Add it to audioStreams and update the page.
* @param {Object} stream JSON representation of an audio stream.
*/
function addAudioStream(stream) {
audioStreams.addItem(stream);
printAudioStreams();
}
/**
* Receiving all data.
* Add it all to the appropriate stores and update the page.
* @param {Object} stuff JSON containing lists of data.
* @param {Object} stuff.audio_streams A dictionary of audio streams.
*/
function onReceiveEverything(stuff) {
audioStreams.addItems(stuff.audio_streams);
printAudioStreams();
}
/**
* Removing an item from the appropriate store.
* @param {string} id The id of the item to be removed, in the format
* "item_type.identifying_info".
*/
function onItemDeleted(id) {
var type = id.split('.')[0];
switch (type) {
case 'audio_streams':
audioStreams.removeItem(id);
printAudioStreams();
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.
* Update cache information and update that section of the page.
* @param {Array} updates A list of net events that have occurred.
*/
function onNetUpdate(updates) {
updates.forEach(function(update) {
var id = update.source.id;
if (!cacheEntries[id])
cacheEntries[id] = new media.CacheEntry;
switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) {
case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL':
var key = update.params.key;
// Merge this source with anything we already know about this key.
if (cacheEntriesByKey[key]) {
cacheEntriesByKey[key].merge(cacheEntries[id]);
cacheEntries[id] = cacheEntriesByKey[key];
} else {
cacheEntriesByKey[key] = cacheEntries[id];
}
cacheEntriesByKey[key].key = key;
break;
case 'PHASE_BEGIN.SPARSE_READ':
cacheEntries[id].readBytes(update.params.offset,
update.params.buff_len);
cacheEntries[id].sparse = true;
break;
case 'PHASE_BEGIN.SPARSE_WRITE':
cacheEntries[id].writeBytes(update.params.offset,
update.params.buff_len);
cacheEntries[id].sparse = true;
break;
case 'PHASE_BEGIN.URL_REQUEST_START_JOB':
requestURLs[update.source.id] = update.params.url;
break;
case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS':
// Record the total size of the file if this was a range request.
var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec(
update.params.headers);
var key = requestURLs[update.source.id];
delete requestURLs[update.source.id];
if (range && key) {
if (!cacheEntriesByKey[key]) {
cacheEntriesByKey[key] = new media.CacheEntry;
cacheEntriesByKey[key].key = key;
}
cacheEntriesByKey[key].size = range[1];
}
break;
}
});
printSparseCacheEntries();
}
/**
* Receiving values for constants. Store them for later use.
* @param {Object} constants A dictionary of constants.
* @param {Object} constants.eventTypes A dictionary of event name -> int.
* @param {Object} constants.eventPhases A dictionary of event phase -> int.
*/
function onReceiveConstants(constants) {
var events = constants.eventTypes;
for (var e in events) {
eventTypes[events[e]] = e;
}
var phases = constants.eventPhases;
for (var p in phases) {
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 {
initialize: initialize,
addAudioStream: addAudioStream,
cacheEntriesByKey: cacheEntriesByKey,
onReceiveEverything: onReceiveEverything,
onItemDeleted: onItemDeleted,
onRendererTerminated: onRendererTerminated,
onNetUpdate: onNetUpdate,
onReceiveConstants: onReceiveConstants,
onMediaEvent: onMediaEvent
};
});
/**
* Initialize everything once we have access to the DOM.
*/
document.addEventListener('DOMContentLoaded', function() {
media.initialize();
});