| OLD | NEW |
| (Empty) |
| 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 }); | |
| OLD | NEW |