Skip to content

Commit

Permalink
Allow AbrManager to clear ahead of the playhead
Browse files Browse the repository at this point in the history
SimpleAbrManager will now leave 5s of video ahead of the playhead and
clear the rest when upgrading to a higher resolution.

Also smooths transitions for overlapping segments by scheduling an
abort() call to reset MediaSource internals after removing content or
changing the append window.

Related to issue #435

Change-Id: Ie036515388e1e7e4b3b8f3ab9922e3d5e7ed2627
  • Loading branch information
joeyparrish committed Jul 13, 2016
1 parent 28aac02 commit 6daa7f3
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 132 deletions.
18 changes: 16 additions & 2 deletions externs/shaka/abr_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,25 @@
shakaExtern.AbrManager = function() {};


/**
* @typedef {function(!Object.<string, !shakaExtern.Stream>, number=)}
* A callback which implementations call to switch streams.
*
* The first argument is a map of content types to chosen streams.
*
* The second argument is an optional number of seconds of content to leave in
* the buffer ahead of the playhead. Anything beyond that will be cleared.
* This is used to make a resolution change take effect sooner, at the cost of
* wasting previously downloaded segments. If undefined, nothing will be
* cleared.
*/
shakaExtern.AbrManager.SwitchCallback;


/**
* Initializes the AbrManager.
*
* @param {function(!Object.<string, !shakaExtern.Stream>)} switchCallback
* A callback which implementations call to switch streams.
* @param {shakaExtern.AbrManager.SwitchCallback} switchCallback
* @exportDoc
*/
shakaExtern.AbrManager.prototype.init = function(switchCallback) {};
Expand Down
22 changes: 19 additions & 3 deletions lib/abr/simple_abr_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ goog.require('shaka.log');
* @export
*/
shaka.abr.SimpleAbrManager = function() {
/** @private {?function(!Object.<string, !shakaExtern.Stream>)} */
/** @private {?shakaExtern.AbrManager.SwitchCallback} */
this.switch_ = null;

/** @private {boolean} */
Expand Down Expand Up @@ -105,6 +105,16 @@ shaka.abr.SimpleAbrManager.BANDWIDTH_UPGRADE_TARGET_ = 0.85;
shaka.abr.SimpleAbrManager.BANDWIDTH_DOWNGRADE_TARGET_ = 0.95;


/**
* The number of seconds of content to leave in buffer ahead of the playhead
* when upgrading video. This makes video upgrades visible sooner.
*
* @private
* @const {number}
*/
shaka.abr.SimpleAbrManager.UPGRADE_LEAVE_IN_BUFFER_ = 5;


/** @override */
shaka.abr.SimpleAbrManager.prototype.stop = function() {
this.switch_ = null;
Expand Down Expand Up @@ -206,12 +216,18 @@ shaka.abr.SimpleAbrManager.prototype.suggestStreams_ = function() {
var oldVideo = this.streamsByType_['video'];
var chosen = this.chooseStreams_();
if (chosen['audio'] != oldAudio || chosen['video'] != oldVideo) {
var opt_leaveInBuffer = undefined;
if (oldVideo && chosen.video &&
chosen.video.bandwidth > oldVideo.bandwidth) {
opt_leaveInBuffer = shaka.abr.SimpleAbrManager.UPGRADE_LEAVE_IN_BUFFER_;
}
var currentBandwidthKbps =
Math.round(this.bandwidthEstimator_.getBandwidthEstimate() / 1000.0);
shaka.log.debug(
'Calling switch_()...',
'bandwidth=' + currentBandwidthKbps + ' kbps');
this.switch_(chosen);
'bandwidth=' + currentBandwidthKbps + ' kbps',
'opt_leaveInBuffer=', opt_leaveInBuffer);
this.switch_(chosen, opt_leaveInBuffer);
}
};

Expand Down
68 changes: 62 additions & 6 deletions lib/media/media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,17 @@ shaka.media.MediaSourceEngine.prototype.remove =
if (contentType == 'text') {
return this.textEngine_.remove(startTime, endTime);
}
return this.enqueueOperation_(
contentType,
this.remove_.bind(this, contentType, startTime, endTime));
return Promise.all([
this.enqueueOperation_(
contentType,
this.remove_.bind(this, contentType, startTime, endTime)),
// Queue an abort() to help MSE splice together overlapping segments.
// We may have overlap if part of an already-decoded segment is removed
// and replaced by another representation, as happens during adaptation.
this.enqueueOperation_(
contentType,
this.abort_.bind(this, contentType))
]);
};


Expand Down Expand Up @@ -437,9 +445,17 @@ shaka.media.MediaSourceEngine.prototype.setAppendWindowEnd = function(
this.textEngine_.setAppendWindowEnd(appendWindowEnd);
return Promise.resolve();
}
return this.enqueueOperation_(
contentType,
this.setAppendWindowEnd_.bind(this, contentType, appendWindowEnd));
return Promise.all([
// Queue an abort() to help MSE splice together overlapping segments.
// We set appendWindowEnd when we change periods in DASH content, and the
// period transition may result in overlap.
this.enqueueOperation_(
contentType,
this.abort_.bind(this, contentType)),
this.enqueueOperation_(
contentType,
this.setAppendWindowEnd_.bind(this, contentType, appendWindowEnd))
]);
};


Expand Down Expand Up @@ -483,6 +499,16 @@ shaka.media.MediaSourceEngine.prototype.setDuration = function(duration) {
};


/**
* Get the current MediaSource duration.
*
* @return {number}
*/
shaka.media.MediaSourceEngine.prototype.getDuration = function() {
return this.mediaSource_.duration;
};


/**
* Append data to the SourceBuffer.
* @param {string} contentType
Expand All @@ -506,11 +532,41 @@ shaka.media.MediaSourceEngine.prototype.append_ =
*/
shaka.media.MediaSourceEngine.prototype.remove_ =
function(contentType, startTime, endTime) {
if (endTime <= startTime) {
// Ignore removal of inverted or empty ranges.
// Fake 'updateend' event to resolve the operation.
this.onUpdateEnd_(contentType);
return;
}

// This will trigger an 'updateend' event.
this.sourceBuffers_[contentType].remove(startTime, endTime);
};


/**
* Call abort() on the SourceBuffer.
* This resets MSE's last_decode_timestamp on all track buffers, which should
* trigger the splicing logic for overlapping segments.
* @param {string} contentType
* @private
*/
shaka.media.MediaSourceEngine.prototype.abort_ = function(contentType) {
// Save the append window end, which is reset on abort().
var appendWindowEnd = this.sourceBuffers_[contentType].appendWindowEnd;

// This will not trigger an 'updateend' event, since nothing is happening.
// This is only to reset MSE internals, not to abort an actual operation.
this.sourceBuffers_[contentType].abort();

// Restore the append window end.
this.sourceBuffers_[contentType].appendWindowEnd = appendWindowEnd;

// Fake 'updateend' event to resolve the operation.
this.onUpdateEnd_(contentType);
};


/**
* Set the SourceBuffer's timestamp offset.
* @param {string} contentType
Expand Down
Loading

0 comments on commit 6daa7f3

Please sign in to comment.