Home | History | Annotate | Download | only in webapp
      1 // Copyright 2014 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 'use strict';
      6 
      7 /** @suppress {duplicate} */
      8 var remoting = remoting || {};
      9 
     10 /**
     11  * @param {HTMLMediaElement} videoTag <video> tag to render to.
     12  * @constructor
     13  */
     14 remoting.MediaSourceRenderer = function(videoTag) {
     15   /** @type {HTMLMediaElement} */
     16   this.video_ = videoTag;
     17 
     18   /** @type {MediaSource} */
     19   this.mediaSource_ = null;
     20 
     21   /** @type {SourceBuffer} */
     22   this.sourceBuffer_ = null;
     23 
     24   /** @type {!Array.<ArrayBuffer>} Queue of pending buffers that haven't been
     25    * processed. A null element indicates that the SourceBuffer can be reset
     26    * because the following buffer contains a keyframe. */
     27   this.buffers_ = [];
     28 
     29   this.lastKeyFramePos_ = 0;
     30 }
     31 
     32 /**
     33  * @param {string} format Format of the stream.
     34  */
     35 remoting.MediaSourceRenderer.prototype.reset = function(format) {
     36   // Reset the queue.
     37   this.buffers_ = [];
     38 
     39   // Create a new MediaSource instance.
     40   this.sourceBuffer_ = null;
     41   this.mediaSource_ = new MediaSource();
     42   this.mediaSource_.addEventListener('sourceopen',
     43                                      this.onSourceOpen_.bind(this, format));
     44   this.mediaSource_.addEventListener('sourceclose', function(e) {
     45     console.error("MediaSource closed unexpectedly.");
     46   });
     47   this.mediaSource_.addEventListener('sourceended', function(e) {
     48     console.error("MediaSource ended unexpectedly.");
     49   });
     50 
     51   // Start playback from new MediaSource.
     52   this.video_.src =
     53       /** @type {string} */(
     54           window.URL.createObjectURL(/** @type {!Blob} */(this.mediaSource_)));
     55   this.video_.play();
     56 }
     57 
     58 /**
     59  * @param {string} format
     60  * @private
     61  */
     62 remoting.MediaSourceRenderer.prototype.onSourceOpen_ = function(format) {
     63   this.sourceBuffer_ =
     64       this.mediaSource_.addSourceBuffer(format);
     65 
     66   this.sourceBuffer_.addEventListener(
     67       'updateend', this.processPendingData_.bind(this));
     68   this.processPendingData_();
     69 }
     70 
     71 /**
     72  * @private
     73  */
     74 remoting.MediaSourceRenderer.prototype.processPendingData_ = function() {
     75   if (this.sourceBuffer_) {
     76     while (this.buffers_.length > 0 && !this.sourceBuffer_.updating) {
     77       var buffer = /** @type {ArrayBuffer} */ this.buffers_.shift();
     78       if (buffer == null) {
     79         // Remove data from the SourceBuffer from the beginning to the previous
     80         // key frame. By default Chrome buffers up to 150MB of data. We never
     81         // need to seek the stream, so it doesn't make sense to keep any of that
     82         // data.
     83         if (this.sourceBuffer_.buffered.length > 0) {
     84           // TODO(sergeyu): Check currentTime to make sure that the current
     85           // playback position is not being removed. crbug.com/398290 .
     86           if (this.lastKeyFramePos_ > this.sourceBuffer_.buffered.start(0)) {
     87             this.sourceBuffer_.remove(this.sourceBuffer_.buffered.start(0),
     88                                       this.lastKeyFramePos_);
     89           }
     90 
     91           this.lastKeyFramePos_ = this.sourceBuffer_.buffered.end(
     92               this.sourceBuffer_.buffered.length - 1);
     93         }
     94       } else {
     95         // TODO(sergeyu): Figure out the way to determine when a frame is
     96         // rendered and use it to report performance statistics.
     97         this.sourceBuffer_.appendBuffer(buffer);
     98       }
     99     }
    100   }
    101 }
    102 
    103 /**
    104  * @param {ArrayBuffer} data
    105  * @param {boolean} keyframe
    106  */
    107 remoting.MediaSourceRenderer.prototype.onIncomingData =
    108     function(data, keyframe) {
    109   if (keyframe) {
    110     // Queue SourceBuffer reset request.
    111     this.buffers_.push(null);
    112   }
    113   this.buffers_.push(data);
    114   this.processPendingData_();
    115 }
    116 
    117