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} */ 8var remoting = remoting || {}; 9 10/** 11 * @param {HTMLMediaElement} videoTag <video> tag to render to. 12 * @constructor 13 */ 14remoting.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 */ 35remoting.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 */ 62remoting.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 */ 74remoting.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 */ 107remoting.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