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 |