Home | History | Annotate | Download | only in media
      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