From 0a6baab9db415918a77b003ef607aa5fbf2d9c12 Mon Sep 17 00:00:00 2001 From: Timothy Drews Date: Tue, 18 Aug 2015 13:39:46 -0700 Subject: [PATCH] Improve Stream docs; rm minBufferTime from switch. * Make IStream more precise by adding more documentation. * Make misc. docs related to Stream more consistent. * Remove |minBufferTime| from IStream; use a configuration option instead. Issue #147 Change-Id: Ie6261ced49a8f815d270e0ee94af5dc40acff63f --- lib/media/i_stream.js | 160 +++++++++++++++++++++++++----- lib/media/simple_abr_manager.js | 3 +- lib/media/stream.js | 114 ++++++++++++++------- lib/media/text_stream.js | 39 +++++--- lib/player/i_video_source.js | 4 +- lib/player/player.js | 10 +- lib/player/stream_video_source.js | 51 ++++++---- 7 files changed, 277 insertions(+), 104 deletions(-) diff --git a/lib/media/i_stream.js b/lib/media/i_stream.js index 4dbca69c71..3902bef32b 100644 --- a/lib/media/i_stream.js +++ b/lib/media/i_stream.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @fileoverview A generic media stream interface. + * @fileoverview A generic stream interface. */ goog.provide('shaka.media.IStream'); @@ -23,7 +23,68 @@ goog.require('shaka.media.StreamInfo'); /** - * An IStream is a generic media stream interface. + *

+ * IStream provides an interface to present streams. A stream is data, which + * may be fragmented into multiple segments, that can be presented to the user + * (e.g., aurally or visually). A StreamInfo object describes an individual + * stream. IStream does not dictate a presentation medium or mechanism; Stream + * implementations may present streams however they wish (e.g., via an + * HTMLMediaElement). + *

+ * + *

+ * Stream callers (i.e., clients) can use a single Stream object to present one + * or more streams, but a Stream object can only present one stream at one time + * (note that multiple Stream objects can present multiple streams in + * parallel). Stream implementations should support stream switching, although + * this may not be possible for all stream types. Furthermore, Streams may + * support just one stream type (e.g., text) or multiple stream types (e.g., + * audio and video). + *

+ * + *

+ * Stream implementations must implement the IStream state model, which enables + * callers to deduce which state the Stream is in based upon its interaction + * with the Stream. The IStream state model consists of the following states + *

    + *
  1. + * idle
    + * The caller has created the Stream but has not called switch(). + * + *
  2. + * startup
    + * The caller has called switch(), and the Stream is performing its + * initialization sequence (see {@link shaka.media.IStream#started}). If the + * Stream encounters an error during startup then it must reject its + * started() Promise. Stream implementations may treat errors during startup + * as either recoverable or unrecoverable and may provide their own recovery + * mechanism if they so choose. + * + *
  3. + * waiting
    + * The Stream has completed startup, but the caller has not signalled the + * Stream to proceed (see {@link shaka.media.IStream#started}). + * + *
  4. + * streaming
    + * The caller has signalled the Stream to proceed, and the Stream is + * processing and presenting data. If the Stream encounters an error while + * streaming then it should attempt to recover, fire an error event, or do + * both. + * + *
  5. + * ended
    + * The Stream has no more data available but may still be presenting data. + *
+ * + * And state transitions + *
+ * idle --> startup --> waiting --> streaming --> ended --+
+ *                                      ^                 |
+ *                                      |                 |
+ *                                      +-----------------+
+ * 
+ *

* * @interface * @extends {EventTarget} @@ -33,15 +94,19 @@ shaka.media.IStream = function() {}; /** * @event shaka.media.IStream.AdaptationEvent - * @description Fired when video, audio, or text tracks change. - * Bubbles up through the Player. + * @description Fired when an audio, video, or text track has changed, or more + * specifically, when the Stream has buffered at least one segment of a + * new stream. Bubbles up through the Player. * @property {string} type 'adaptation' * @property {boolean} bubbles true - * @property {string} contentType 'video', 'audio', or 'text' - * @property {?{width: number, height: number}} size The resolution chosen, if - * the stream is a video stream. - * @property {number} bandwidth The stream's bandwidth requirement in bits per - * second. + * @property {string} contentType The new stream's content type, e.g., 'audio', + * 'video', or 'text'. + * @property {?{width: number, height: number}} size The new stream's + * resolution, if applicable. Note: the new stream may not start presenting + * immediately (see {@link shaka.media.IStream#switch}), so the user may not + * see the resolution change immediately. + * @property {number} bandwidth The new stream's bandwidth requirement in + * bits per second. * @export */ @@ -65,11 +130,26 @@ shaka.media.IStream.prototype.configure = function(config) {}; shaka.media.IStream.prototype.destroy = function() {}; -/** @return {shaka.media.StreamInfo} */ +/** + * Gets the StreamInfo that corresponds to the stream that is currently being + * processed or presented. Note that this StreamInfo may be different than the + * last StreamInfo that was passed to switch() + * (see {@link shaka.media.IStream#switch}). + * + * @return {shaka.media.StreamInfo} + */ shaka.media.IStream.prototype.getStreamInfo = function() {}; -/** @return {shaka.media.SegmentIndex} */ +/** + * Gets the SegmentIndex of the StreamInfo that corresponds to the stream that + * is currently being processed or presented. Note that this SegmentIndex may + * be different than the SegmentIndex of the last StreamInfo that was passed to + * switch() + * (see {@link shaka.media.IStream#switch}). + * + * @return {shaka.media.SegmentIndex} + */ shaka.media.IStream.prototype.getSegmentIndex = function() {}; @@ -78,14 +158,15 @@ shaka.media.IStream.prototype.getSegmentIndex = function() {}; * (i.e., the Stream's initialization sequence) completes. Stream * implementations may implement startup as they wish but startup should entail * acquiring some initial resources. Implementations must resolve the returned - * Promise if startup completes and reject the Promise if startup fails. + * Promise if startup completes and reject the returned Promise if startup + * fails. * * This function can only be called once. * * @param {!Promise} proceed A Promise that the caller must resolve after - * startup completes to signal the Stream that it can proceed. Stream - * implementations must idle after startup completes and before the caller - * resolves |proceed|. Callers must never reject |proceed|. + * startup completes to signal the Stream that it can proceed. The Stream + * will idle while in the 'waiting' state. Callers must never reject + * |proceed|. * @return {!Promise.} A promise to a timestamp correction, which is * the number of seconds that the media timeline (the sequence of * timestamps in the stream's media segments) is offset from the Stream's @@ -103,8 +184,7 @@ shaka.media.IStream.prototype.started = function(proceed) {}; /** * Returns true if the stream has ended; otherwise, returns false. The Stream - * can only end after it has been signalled to proceed after startup completes. - * (see {@link shaka.media.IStream#started}). + * can only end while it's in the 'streaming' state. * * @return {boolean} True if the stream has ended; otherwise, return false. */ @@ -112,30 +192,56 @@ shaka.media.IStream.prototype.hasEnded = function() {}; /** - * Start or switch the stream to the given |streamInfo|. + * Starts presenting the specified stream. Stream implementations may implement + * stream switching asynchronously, in which case, they must implement the + * switch state model, which consists of the following states + *
    + *
  1. + * acquiring-metadata
    + * The caller has called switch(), and the Stream is acquiring the new + * stream's metadata, but the stream itself is not being processed; + * getStreamInfo() and getSegmentIndex() must not return the new StreamInfo + * and SegmentIndex. + * + *
  2. + * processing
    + * The Stream is processing the new stream's content, but the stream's + * content is not buffered yet; getStreamInfo() and getSegmentIndex() + * must return the new StreamInfo and SegmentIndex. + * + *
  3. + * buffered
    + * The Stream has buffered some of the new stream's content (e.g., at least + * one segment), but the Stream may or may not be presenting the new stream's + * content, i.e., the Stream's current position may or may not be within the + * new stream's buffered range at this time. + *
+ * + * Stream implementations must fire an AdaptationEvent when/after transitioning + * from the 'processing' state to the 'buffered' state. * * @param {!shaka.media.StreamInfo} streamInfo - * @param {number} minBufferTime The amount of content to buffer, in seconds, - * when the stream starts for the first time. * @param {boolean} clearBuffer If true, removes the previous stream's content * before switching to the new stream. * @param {number=} opt_clearBufferOffset if |clearBuffer| and - * |opt_clearBufferOffset| - * are truthy, clear the stream buffer from the offset (in front of video - * currentTime) to the end of the stream. + * |opt_clearBufferOffset| are truthy, clear the stream buffer from the + * given offset (relative to the Stream's current position) to the end of + * the stream. */ shaka.media.IStream.prototype.switch = function( - streamInfo, minBufferTime, clearBuffer, opt_clearBufferOffset) {}; + streamInfo, clearBuffer, opt_clearBufferOffset) {}; /** - * Resync the stream with the video's currentTime. Called on seeking. + * Resynchronizes the Stream's current position, e.g., to the video's playhead, + * or does nothing if the Stream does not require manual resynchronization. */ shaka.media.IStream.prototype.resync = function() {}; /** - * Enable or disable the stream. Not supported for all stream types. + * Enables or disables stream presentation or does nothing if the Stream cannot + * disable stream presentation. * * @param {boolean} enabled */ @@ -143,7 +249,7 @@ shaka.media.IStream.prototype.setEnabled = function(enabled) {}; /** - * @return {boolean} true if the stream is enabled. + * @return {boolean} True if the stream is enabled; otherwise, return false. */ shaka.media.IStream.prototype.getEnabled = function() {}; diff --git a/lib/media/simple_abr_manager.js b/lib/media/simple_abr_manager.js index ba3f9a5c52..3900c71c22 100644 --- a/lib/media/simple_abr_manager.js +++ b/lib/media/simple_abr_manager.js @@ -166,7 +166,8 @@ shaka.media.SimpleAbrManager.prototype.getInitialVideoTrackId = function() { * before switching to the new stream. * @param {number=} opt_clearBufferOffset if |clearBuffer| and * |opt_clearBufferOffset| are truthy, clear the stream buffer from the - * offset (in front of video currentTime) to the end of the stream. + * given offset (relative to the video's current time) to the end of the + * stream. * * @protected * @expose diff --git a/lib/media/stream.js b/lib/media/stream.js index 270cf768b5..1ea146433b 100644 --- a/lib/media/stream.js +++ b/lib/media/stream.js @@ -42,15 +42,16 @@ goog.require('shaka.util.TypedBind'); /** - * Creates a Stream. + * Creates a Stream, which presents audio and video streams via an + * HTMLVideoElement. * * @param {!shaka.util.FakeEventTarget} parent The parent for event bubbling. - * @param {!HTMLVideoElement} video The video element. + * @param {!HTMLVideoElement} video * @param {!MediaSource} mediaSource The SourceBuffer's parent MediaSource. - * @param {!SourceBuffer} sourceBuffer The SourceBuffer. It's assumed that - * |sourceBuffer| has the same mime type as |streamInfo_|. + * @param {!SourceBuffer} sourceBuffer The SourceBuffer, whose content type + * sets the Stream's type. * @param {!shaka.util.IBandwidthEstimator} estimator A bandwidth estimator to - * attach to all data requests. + * attach to all segment requests. * * @fires shaka.media.IStream.AdaptationEvent * @fires shaka.media.Stream.EndedEvent @@ -61,8 +62,8 @@ goog.require('shaka.util.TypedBind'); * @implements {shaka.media.IStream} * @extends {shaka.util.FakeEventTarget} */ -shaka.media.Stream = - function(parent, video, mediaSource, sourceBuffer, estimator) { +shaka.media.Stream = function( + parent, video, mediaSource, sourceBuffer, estimator) { shaka.util.FakeEventTarget.call(this, parent); /** @private {!HTMLVideoElement} */ @@ -84,9 +85,6 @@ shaka.media.Stream = /** @private {ArrayBuffer} */ this.initData_ = null; - /** @private {number} */ - this.minBufferTime_ = 0; - /** @private {boolean} */ this.switched_ = false; @@ -111,6 +109,9 @@ shaka.media.Stream = /** @private {boolean} */ this.ended_ = false; + /** @private {number} */ + this.initialBufferSizeSeconds_ = 0; + /** @private {number} */ this.bufferSizeSeconds_ = shaka.player.Defaults.STREAM_BUFFER_SIZE; @@ -147,14 +148,25 @@ shaka.media.Stream.NUDGE_ = 0.001; /** * Configures the Stream options. Options are set via key-value pairs. + *
* * The following configuration options are supported: - * streamBufferSize: number - * Sets the amount of content that the stream will buffer, in seconds, after - * startup. Where startup consists of waiting until the stream has buffered - * some minimum amount of content, which is determined via switch(). - * segmentRequestTimeout: number - * Sets the segment request timeout in seconds. + * * * @example * stream.configure({'streamBufferSize': 20}); @@ -165,6 +177,10 @@ shaka.media.Stream.NUDGE_ = 0.001; * @override */ shaka.media.Stream.prototype.configure = function(config) { + if (config['initialStreamBufferSize'] != null) { + this.initialBufferSizeSeconds_ = Number(config['initialStreamBufferSize']); + } + if (config['streamBufferSize'] != null) { this.bufferSizeSeconds_ = Number(config['streamBufferSize']); } @@ -209,11 +225,16 @@ shaka.media.Stream.prototype.getSegmentIndex = function() { /** - * The Stream will resolve the returned Promise after it buffers N seconds - * of content, where N equals |minBufferTime| from the last call to switch(). + *

+ * Returns a promise that the Stream will resolve after it has buffered + * 'initialStreamBufferSize' seconds of content + * (see {@link shaka.media.Stream#configure}). + *

* - * The Stream will not modify its underlying SourceBuffer after startup - * completes and before the caller resolves |proceed|. + *

+ * The Stream will not modify its underlying SourceBuffer while it's in the + * 'waiting' state (see {@link shaka.media.IStream}). + *

* * @override */ @@ -237,8 +258,14 @@ shaka.media.Stream.prototype.started = function(proceed) { /** - * The Stream will not modify its underlying SourceBuffer if the stream has - * ended. + *

+ * Returns true if the stream has ended; otherwise, returns false. + *

+ * + *

+ * The Stream will not modify its underlying SourceBuffer while it's in the + * 'ended' state. + *

* * @override */ @@ -247,9 +274,15 @@ shaka.media.Stream.prototype.hasEnded = function() { }; -/** @override */ +/** + * Starts presenting the specified stream asynchronously. The stream's type + * must be equivalent to the underlying SourceBuffer's type. + * Note: |opt_clearBufferOffset| is relative to the video's playhead. + * + * @override + */ shaka.media.Stream.prototype.switch = function( - streamInfo, minBufferTime, clearBuffer, opt_clearBufferOffset) { + streamInfo, clearBuffer, opt_clearBufferOffset) { if (streamInfo == this.streamInfo_) { shaka.log.debug(this.logPrefix_(), 'already using stream', streamInfo); return; @@ -273,7 +306,6 @@ shaka.media.Stream.prototype.switch = function( this.streamInfo_ = streamInfo; this.segmentIndex_ = results[0]; this.initData_ = results[1]; - this.minBufferTime_ = minBufferTime; this.switched_ = true; if (this.resyncing_) { @@ -310,7 +342,12 @@ shaka.media.Stream.prototype.switch = function( }; -/** @override */ +/** + * Resynchronizes the Stream's current position to the video's playhead. + * This must be called when the user seeks. + * + * @override + */ shaka.media.Stream.prototype.resync = function() { shaka.log.debug(this.logPrefix_(), 'resync'); return this.resync_(false /* forceClear */); @@ -318,13 +355,11 @@ shaka.media.Stream.prototype.resync = function() { /** - * Resync the stream. - * * @param {boolean} forceClear * @param {number=} opt_clearBufferOffset if |forceClear| and * |opt_clearBufferOffset| are truthy, clear the stream buffer from the - * offset (in front of video currentTime) to the end of the stream. - * + * given offset (relative to the video's current time) to the end of the + * stream. * @private */ shaka.media.Stream.prototype.resync_ = function( @@ -399,13 +434,19 @@ shaka.media.Stream.prototype.resync_ = function( }; -/** @override */ -shaka.media.Stream.prototype.setEnabled = function(enabled) { - // NOP, not supported for audio and video streams. -}; +/** + * Does nothing since audio and video streams are always enabled. + * + * @override + */ +shaka.media.Stream.prototype.setEnabled = function(enabled) {}; -/** @override */ +/** + * Always returns true since audio and video streams are always enabled. + * + * @override + */ shaka.media.Stream.prototype.getEnabled = function() { return true; }; @@ -413,6 +454,7 @@ shaka.media.Stream.prototype.getEnabled = function() { /** * Update callback. + * * @private */ shaka.media.Stream.prototype.onUpdate_ = function() { @@ -578,7 +620,7 @@ shaka.media.Stream.prototype.getBufferingGoal_ = function() { // TODO: Consider a different buffering goal when re-buffering. return this.started_ ? this.bufferSizeSeconds_ : - Math.min(this.minBufferTime_, this.bufferSizeSeconds_) + + Math.min(this.initialBufferSizeSeconds_, this.bufferSizeSeconds_) + (this.timestampCorrection_ || 0); }; diff --git a/lib/media/text_stream.js b/lib/media/text_stream.js index 1a6870a439..bf8ecc571b 100644 --- a/lib/media/text_stream.js +++ b/lib/media/text_stream.js @@ -28,11 +28,14 @@ goog.require('shaka.util.PublicPromise'); /** - * Creates a TextStream. A TextStream is a Stream work-alike for - * text tracks. + * Creates a TextStream, which presents subtitles via an HTMLVideoElement's + * built-in subtitles support. * * @param {!shaka.util.FakeEventTarget} parent The parent for event bubbling. * @param {!HTMLVideoElement} video The video element. + * + * @fires shaka.media.IStream.AdaptationEvent + * * @struct * @constructor * @implements {shaka.media.IStream} @@ -63,9 +66,7 @@ goog.inherits(shaka.media.TextStream, shaka.util.FakeEventTarget); /** @override */ -shaka.media.TextStream.prototype.configure = function(config) { - // nop -}; +shaka.media.TextStream.prototype.configure = function(config) {}; /** @@ -98,7 +99,8 @@ shaka.media.TextStream.prototype.getSegmentIndex = function() { /** - * The stream will never reject the returned Promise. + * Returns a Promise that the Stream will resolve after it has begun presenting + * its first text stream. The Stream will never reject the returned Promise. * * @override */ @@ -107,15 +109,24 @@ shaka.media.TextStream.prototype.started = function(proceed) { }; -/** @override */ +/** + * Always returns true since text streams are not segmented. + * + * @override + */ shaka.media.TextStream.prototype.hasEnded = function() { return true; }; -/** @override */ +/** + * Starts presenting the specified stream asynchronously. + * Note: |clearBuffer| and |opt_clearBufferOffset| are ignored. + * + * @override + */ shaka.media.TextStream.prototype.switch = function( - streamInfo, minBufferTime, clearBuffer, opt_clearBufferOffset) { + streamInfo, clearBuffer, opt_clearBufferOffset) { shaka.log.info('Switching stream to', streamInfo); streamInfo.segmentIndexSource.create().then(shaka.util.TypedBind(this, @@ -173,10 +184,12 @@ shaka.media.TextStream.prototype.switch = function( }; -/** @override */ -shaka.media.TextStream.prototype.resync = function() { - // NOP -}; +/** + * Does nothing since text streams do not require manual resynchronization. + * + * @override + */ +shaka.media.TextStream.prototype.resync = function() {}; /** @override */ diff --git a/lib/player/i_video_source.js b/lib/player/i_video_source.js index 90907e4c38..834ae66adf 100644 --- a/lib/player/i_video_source.js +++ b/lib/player/i_video_source.js @@ -144,7 +144,8 @@ shaka.player.IVideoSource.prototype.selectConfigurations = * before switching to the new stream. * @param {number=} opt_clearBufferOffset if |clearBuffer| and * |opt_clearBufferOffset| are truthy, clear the stream buffer from the - * offset (in front of video currentTime) to the end of the stream. + * given offset (relative to the video's current time) to the end of the + * stream. * * @return {boolean} True on success; otherwise, return false if the specified * VideoTrack does not exist or if a video stream does not exist. @@ -224,3 +225,4 @@ shaka.player.IVideoSource.prototype.isLive = function() {}; */ shaka.player.IVideoSource.prototype.setPlaybackStartTime = function(startTime) {}; + diff --git a/lib/player/player.js b/lib/player/player.js index b38b207d45..997fdb6e55 100644 --- a/lib/player/player.js +++ b/lib/player/player.js @@ -745,11 +745,11 @@ shaka.player.Player.prototype.isLive = function() { * *
  • * streamBufferSize: number
    - * Sets the amount of content that streams will buffer, in seconds, after - * startup. Where startup consists of waiting until the streams have buffered - * some minimum amount of content, which is determined by the VideoSource - * implementation; for DASH content, the minimum amount of content is equal - * to the 'minBufferTime' attribute from the MPD. + * Sets the amount of content that audio and video streams will buffer ahead + * of the playhead, in seconds, after stream startup. Where stream startup + * entails acquiring some initial resources for each stream. For DASH audio + * and video streams, startup completes when each stream buffers N seconds of + * content, where N equals the 'minBufferTime' attribute from the MPD. * *
  • * licenseRequestTimeout: number
    diff --git a/lib/player/stream_video_source.js b/lib/player/stream_video_source.js index 95815266ed..85228f619d 100644 --- a/lib/player/stream_video_source.js +++ b/lib/player/stream_video_source.js @@ -218,10 +218,7 @@ shaka.player.StreamVideoSource.prototype.configure = function(config) { config['segmentRequestTimeout']; } - for (var type in this.streamsByType_) { - var stream = this.streamsByType_[type]; - stream.configure(this.streamConfig_); - } + this.configureStreams_(); if (config['enableAdaptation'] != null) { this.abrManager_.enable(Boolean(config['enableAdaptation'])); @@ -357,6 +354,12 @@ shaka.player.StreamVideoSource.prototype.load = function() { } this.loaded_ = true; + + // Set the Streams' initial buffer sizes. + this.streamConfig_['initialStreamBufferSize'] = + this.manifestInfo.minBufferTime; + this.configureStreams_(); + this.applyRestrictions_(); return Promise.resolve(); @@ -405,6 +408,11 @@ shaka.player.StreamVideoSource.prototype.onUpdateManifest_ = function() { this.removeStream_(removedStreamInfos[i]); } + // Reconfigure the Streams because |minBufferTime| may have changed. + this.streamConfig_['initialStreamBufferSize'] = + this.manifestInfo.minBufferTime; + this.configureStreams_(); + this.applyRestrictions_(); if (shaka.util.MapUtils.empty(this.streamsByType_)) { @@ -489,10 +497,7 @@ shaka.player.StreamVideoSource.prototype.removeStream_ = function(streamInfo) { } // Just ignore |canSwitch_| and switch right now. - stream.switch( - newStreamInfos[0], - this.manifestInfo.minBufferTime, - true /* clearBuffer */); + stream.switch(newStreamInfos[0], true /* clearBuffer */); streamInfo.destroy(); } @@ -891,10 +896,7 @@ shaka.player.StreamVideoSource.prototype.selectTrack_ = this.stats_.logStreamChange(streamInfo); this.streamsByType_[type].switch( - streamInfo, - this.manifestInfo.minBufferTime, - clearBuffer, - opt_clearBufferOffset); + streamInfo, clearBuffer, opt_clearBufferOffset); return true; } } @@ -1264,10 +1266,7 @@ shaka.player.StreamVideoSource.prototype.startStreams_ = function( var streamInfo = streamInfosByType[type]; this.stats_.logStreamChange(streamInfo); - stream.switch( - streamInfo, - this.manifestInfo.minBufferTime, - false /* clearBuffer */); + stream.switch(streamInfo, false /* clearBuffer */); } Promise.all(async).then( @@ -1386,11 +1385,7 @@ shaka.player.StreamVideoSource.prototype.processDeferredSwitches_ = function() { shaka.asserts.assert(this.stats_); this.stats_.logStreamChange(tuple.streamInfo); - stream.switch( - tuple.streamInfo, - this.manifestInfo.minBufferTime, - tuple.clearBuffer, - tuple.clearBufferOffset); + stream.switch(tuple.streamInfo, tuple.clearBuffer, tuple.clearBufferOffset); } this.deferredSwitches_ = {}; @@ -1952,3 +1947,17 @@ shaka.player.StreamVideoSource.prototype.cancelSeekRangeTimer_ = function() { this.seekRangeTimer_ = null; } }; + + +/** + * Configures each Stream with |streamConfig_| + * + * @private + */ +shaka.player.StreamVideoSource.prototype.configureStreams_ = function() { + for (var type in this.streamsByType_) { + var stream = this.streamsByType_[type]; + stream.configure(this.streamConfig_); + } +}; +