| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| /** @suppress {duplicate} */ |
| var remoting = remoting || {}; |
| |
| /** |
| * @param {HTMLMediaElement} videoTag <video> tag to render to. |
| * @constructor |
| */ |
| remoting.MediaSourceRenderer = function(videoTag) { |
| /** @type {HTMLMediaElement} */ |
| this.video_ = videoTag; |
| |
| /** @type {MediaSource} */ |
| this.mediaSource_ = null; |
| |
| /** @type {SourceBuffer} */ |
| this.sourceBuffer_ = null; |
| |
| /** @type {!Array.<ArrayBuffer>} Queue of pending buffers that haven't been |
| * processed. A null element indicates that the SourceBuffer can be reset |
| * because the following buffer contains a keyframe. */ |
| this.buffers_ = []; |
| |
| this.lastKeyFramePos_ = 0; |
| } |
| |
| /** |
| * @param {string} format Format of the stream. |
| */ |
| remoting.MediaSourceRenderer.prototype.reset = function(format) { |
| // Reset the queue. |
| this.buffers_ = []; |
| |
| // Create a new MediaSource instance. |
| this.sourceBuffer_ = null; |
| this.mediaSource_ = new MediaSource(); |
| this.mediaSource_.addEventListener('sourceopen', |
| this.onSourceOpen_.bind(this, format)); |
| this.mediaSource_.addEventListener('sourceclose', function(e) { |
| console.error("MediaSource closed unexpectedly."); |
| }); |
| this.mediaSource_.addEventListener('sourceended', function(e) { |
| console.error("MediaSource ended unexpectedly."); |
| }); |
| |
| // Start playback from new MediaSource. |
| this.video_.src = |
| /** @type {string} */( |
| window.URL.createObjectURL(/** @type {!Blob} */(this.mediaSource_))); |
| this.video_.play(); |
| } |
| |
| /** |
| * @param {string} format |
| * @private |
| */ |
| remoting.MediaSourceRenderer.prototype.onSourceOpen_ = function(format) { |
| this.sourceBuffer_ = |
| this.mediaSource_.addSourceBuffer(format); |
| |
| this.sourceBuffer_.addEventListener( |
| 'updateend', this.processPendingData_.bind(this)); |
| this.processPendingData_(); |
| } |
| |
| /** |
| * @private |
| */ |
| remoting.MediaSourceRenderer.prototype.processPendingData_ = function() { |
| if (this.sourceBuffer_) { |
| while (this.buffers_.length > 0 && !this.sourceBuffer_.updating) { |
| var buffer = /** @type {ArrayBuffer} */ this.buffers_.shift(); |
| if (buffer == null) { |
| // Remove data from the SourceBuffer from the beginning to the previous |
| // key frame. By default Chrome buffers up to 150MB of data. We never |
| // need to seek the stream, so it doesn't make sense to keep any of that |
| // data. |
| if (this.sourceBuffer_.buffered.length > 0) { |
| // TODO(sergeyu): Check currentTime to make sure that the current |
| // playback position is not being removed. crbug.com/398290 . |
| if (this.lastKeyFramePos_ > this.sourceBuffer_.buffered.start(0)) { |
| this.sourceBuffer_.remove(this.sourceBuffer_.buffered.start(0), |
| this.lastKeyFramePos_); |
| } |
| |
| this.lastKeyFramePos_ = this.sourceBuffer_.buffered.end( |
| this.sourceBuffer_.buffered.length - 1); |
| } |
| } else { |
| // TODO(sergeyu): Figure out the way to determine when a frame is |
| // rendered and use it to report performance statistics. |
| this.sourceBuffer_.appendBuffer(buffer); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param {ArrayBuffer} data |
| * @param {boolean} keyframe |
| */ |
| remoting.MediaSourceRenderer.prototype.onIncomingData = |
| function(data, keyframe) { |
| if (keyframe) { |
| // Queue SourceBuffer reset request. |
| this.buffers_.push(null); |
| } |
| this.buffers_.push(data); |
| this.processPendingData_(); |
| } |
| |