1 // Copyright (c) 2012 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 5 <include src="cache_entry.js"/> 6 <include src="disjoint_range_set.js"/> 7 <include src="event_list.js"/> 8 <include src="item_store.js"/> 9 <include src="media_player.js"/> 10 <include src="metrics.js"/> 11 <include src="util.js"/> 12 13 cr.define('media', function() { 14 'use strict'; 15 16 // Stores information on open audio streams, referenced by id. 17 var audioStreams = new media.ItemStore; 18 19 // Active media players, indexed by 'render_id:player_id'. 20 var mediaPlayers = {}; 21 22 // Cached files indexed by key and source id. 23 var cacheEntriesByKey = {}; 24 var cacheEntries = {}; 25 26 // Map of event source -> url. 27 var requestURLs = {}; 28 29 // Constants passed to us from Chrome. 30 var eventTypes = {}; 31 var eventPhases = {}; 32 33 // The <div>s on the page in which to display information. 34 var audioStreamDiv; 35 var cacheDiv; 36 37 // A timer used to limit the rate of redrawing the Media Players section. 38 var redrawTimer = null; 39 40 /** 41 * Initialize variables and ask MediaInternals for all its data. 42 */ 43 function initialize() { 44 audioStreamDiv = $('audio-streams'); 45 cacheDiv = $('cache-entries'); 46 47 // Get information about all currently active media. 48 chrome.send('getEverything'); 49 } 50 51 /** 52 * Write the set of audio streams to the DOM. 53 */ 54 function printAudioStreams() { 55 56 /** 57 * Render a single stream as a <li>. 58 * @param {Object} stream The stream to render. 59 * @return {HTMLElement} A <li> containing the stream information. 60 */ 61 function printStream(stream) { 62 var out = document.createElement('li'); 63 out.id = stream.id; 64 out.className = 'audio-stream'; 65 out.setAttribute('status', stream.status); 66 67 out.textContent += 'Audio stream ' + stream.id.split('.')[1]; 68 out.textContent += ' is ' + (stream.playing ? 'playing' : 'paused'); 69 if (typeof stream.volume != 'undefined') { 70 out.textContent += ' at ' + (stream.volume * 100).toFixed(0); 71 out.textContent += '% volume.'; 72 } 73 return out; 74 } 75 76 var out = document.createElement('ul'); 77 audioStreams.map(printStream).forEach(function(s) { 78 out.appendChild(s); 79 }); 80 81 audioStreamDiv.textContent = ''; 82 audioStreamDiv.appendChild(out); 83 } 84 85 /** 86 * Redraw each MediaPlayer. 87 */ 88 function printMediaPlayers() { 89 for (var key in mediaPlayers) { 90 mediaPlayers[key].redraw(); 91 } 92 redrawTimer = null; 93 } 94 95 /** 96 * Write the set of sparse CacheEntries to the DOM. 97 */ 98 function printSparseCacheEntries() { 99 var out = document.createElement('ul'); 100 for (var key in cacheEntriesByKey) { 101 if (cacheEntriesByKey[key].sparse) 102 out.appendChild(cacheEntriesByKey[key].toListItem()); 103 } 104 105 cacheDiv.textContent = ''; 106 cacheDiv.appendChild(out); 107 } 108 109 /** 110 * Receiving data for an audio stream. 111 * Add it to audioStreams and update the page. 112 * @param {Object} stream JSON representation of an audio stream. 113 */ 114 function addAudioStream(stream) { 115 audioStreams.addItem(stream); 116 printAudioStreams(); 117 } 118 119 /** 120 * Receiving all data. 121 * Add it all to the appropriate stores and update the page. 122 * @param {Object} stuff JSON containing lists of data. 123 * @param {Object} stuff.audio_streams A dictionary of audio streams. 124 */ 125 function onReceiveEverything(stuff) { 126 audioStreams.addItems(stuff.audio_streams); 127 printAudioStreams(); 128 } 129 130 /** 131 * Removing an item from the appropriate store. 132 * @param {string} id The id of the item to be removed, in the format 133 * "item_type.identifying_info". 134 */ 135 function onItemDeleted(id) { 136 var type = id.split('.')[0]; 137 switch (type) { 138 case 'audio_streams': 139 audioStreams.removeItem(id); 140 printAudioStreams(); 141 break; 142 } 143 } 144 145 /** 146 * A render process has ended, delete any media players associated with it. 147 * @param {number} renderer The id of the render process. 148 */ 149 function onRendererTerminated(renderer) { 150 for (var key in mediaPlayers) { 151 if (mediaPlayers[key].renderer == renderer) { 152 $('media-players').removeChild(mediaPlayers[key]); 153 delete mediaPlayers[key]; 154 break; 155 } 156 } 157 printMediaPlayers(); 158 } 159 160 /** 161 * Receiving net events. 162 * Update cache information and update that section of the page. 163 * @param {Array} updates A list of net events that have occurred. 164 */ 165 function onNetUpdate(updates) { 166 updates.forEach(function(update) { 167 var id = update.source.id; 168 if (!cacheEntries[id]) 169 cacheEntries[id] = new media.CacheEntry; 170 171 switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) { 172 case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL': 173 var key = update.params.key; 174 175 // Merge this source with anything we already know about this key. 176 if (cacheEntriesByKey[key]) { 177 cacheEntriesByKey[key].merge(cacheEntries[id]); 178 cacheEntries[id] = cacheEntriesByKey[key]; 179 } else { 180 cacheEntriesByKey[key] = cacheEntries[id]; 181 } 182 cacheEntriesByKey[key].key = key; 183 break; 184 185 case 'PHASE_BEGIN.SPARSE_READ': 186 cacheEntries[id].readBytes(update.params.offset, 187 update.params.buff_len); 188 cacheEntries[id].sparse = true; 189 break; 190 191 case 'PHASE_BEGIN.SPARSE_WRITE': 192 cacheEntries[id].writeBytes(update.params.offset, 193 update.params.buff_len); 194 cacheEntries[id].sparse = true; 195 break; 196 197 case 'PHASE_BEGIN.URL_REQUEST_START_JOB': 198 requestURLs[update.source.id] = update.params.url; 199 break; 200 201 case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS': 202 // Record the total size of the file if this was a range request. 203 var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec( 204 update.params.headers); 205 var key = requestURLs[update.source.id]; 206 delete requestURLs[update.source.id]; 207 if (range && key) { 208 if (!cacheEntriesByKey[key]) { 209 cacheEntriesByKey[key] = new media.CacheEntry; 210 cacheEntriesByKey[key].key = key; 211 } 212 cacheEntriesByKey[key].size = range[1]; 213 } 214 break; 215 } 216 }); 217 218 printSparseCacheEntries(); 219 } 220 221 /** 222 * Receiving values for constants. Store them for later use. 223 * @param {Object} constants A dictionary of constants. 224 * @param {Object} constants.eventTypes A dictionary of event name -> int. 225 * @param {Object} constants.eventPhases A dictionary of event phase -> int. 226 */ 227 function onReceiveConstants(constants) { 228 var events = constants.eventTypes; 229 for (var e in events) { 230 eventTypes[events[e]] = e; 231 } 232 233 var phases = constants.eventPhases; 234 for (var p in phases) { 235 eventPhases[phases[p]] = p; 236 } 237 } 238 239 /** 240 * Receiving notification of a media event. 241 * @param {Object} event The json representation of a MediaLogEvent. 242 */ 243 function onMediaEvent(event) { 244 var source = event.renderer + ':' + event.player; 245 var item = mediaPlayers[source] || 246 new media.MediaPlayer({id: source, renderer: event.renderer}); 247 mediaPlayers[source] = item; 248 item.addEvent(event); 249 250 // Both media and net events could provide the size of the file. 251 // Media takes priority, but keep the size in both places synchronized. 252 if (cacheEntriesByKey[item.properties.url]) { 253 item.properties.total_bytes = item.properties.total_bytes || 254 cacheEntriesByKey[item.properties.url].size; 255 cacheEntriesByKey[item.properties.url].size = item.properties.total_bytes; 256 } 257 258 // Events tend to arrive in groups; don't redraw the page too often. 259 if (!redrawTimer) 260 redrawTimer = setTimeout(printMediaPlayers, 50); 261 } 262 263 return { 264 initialize: initialize, 265 addAudioStream: addAudioStream, 266 cacheEntriesByKey: cacheEntriesByKey, 267 onReceiveEverything: onReceiveEverything, 268 onItemDeleted: onItemDeleted, 269 onRendererTerminated: onRendererTerminated, 270 onNetUpdate: onNetUpdate, 271 onReceiveConstants: onReceiveConstants, 272 onMediaEvent: onMediaEvent 273 }; 274 }); 275 276 /** 277 * Initialize everything once we have access to the DOM. 278 */ 279 document.addEventListener('DOMContentLoaded', function() { 280 media.initialize(); 281 }); 282