Skip to content

Commit

Permalink
Choose live playback by type='dynamic'.
Browse files Browse the repository at this point in the history
Also avoid fetching manifest updates when minimumUpdatePeriod is
missing.  In the absence of minimumUpdatePeriod, just reprocess
the MPD we already downloaded.

Closes #69, #70.

Change-Id: I138bf136e5b0d691d9d625bbad02fb07ae21d32f
  • Loading branch information
joeyparrish committed May 22, 2015
1 parent 8d4136c commit cae93b3
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 50 deletions.
18 changes: 4 additions & 14 deletions lib/dash/mpd_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ shaka.dash.mpd.Mpd = function() {
/** @type {?string} */
this.id = null;

/** @type {?string} */
this.type = null;
/** @type {string} */
this.type = 'static';

/** @type {goog.Uri} */
this.baseUrl = null;
Expand All @@ -99,7 +99,7 @@ shaka.dash.mpd.Mpd = function() {

/**
* The interval, in seconds, to poll the media server for an updated
* MPD, or null if updates are not required. This value is never zero.
* MPD, or null if updates are not required.
* @type {?number}
*/
this.minUpdatePeriod = null;
Expand Down Expand Up @@ -821,7 +821,7 @@ shaka.dash.mpd.Mpd.prototype.parse = function(parent, elem) {

// Parse attributes.
this.id = mpd.parseAttr_(elem, 'id', mpd.parseString_);
this.type = mpd.parseAttr_(elem, 'type', mpd.parseString_);
this.type = mpd.parseAttr_(elem, 'type', mpd.parseString_) || 'static';
this.mediaPresentationDuration = mpd.parseAttr_(
elem, 'mediaPresentationDuration', mpd.parseDuration_);
this.minBufferTime =
Expand All @@ -836,16 +836,6 @@ shaka.dash.mpd.Mpd.prototype.parse = function(parent, elem) {
mpd.parseDuration_,
this.minUpdatePeriod);

// If minimumUpdatePeriod is set to 0, then we shouldn't refresh the manifest
// unless there is explicit signalling in the stream, according to:
// http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/
// There is no way to get the signal from within the stream in MSE as of now.
// So, if we see a minimumUpdatePeriod of zero, we merely set it to 10
// seconds.
if (this.minUpdatePeriod == 0) {
this.minUpdatePeriod = 10;
}

this.availabilityStartTime =
mpd.parseAttr_(elem,
'availabilityStartTime',
Expand Down
14 changes: 7 additions & 7 deletions lib/dash/mpd_processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ shaka.dash.MpdProcessor.prototype.calculateDurations_ = function(mpd) {
var isSet = function(x) { return x == 0 || !!x; };

// @mediaPresentationDuration should only be used if the MPD is static.
if (isSet(mpd.minUpdatePeriod)) {
if (mpd.type != 'static') {
mpd.mediaPresentationDuration = null;
}

Expand Down Expand Up @@ -314,7 +314,7 @@ shaka.dash.MpdProcessor.prototype.filterAdaptationSet_ = function(
* @private
*/
shaka.dash.MpdProcessor.prototype.createManifestInfo_ = function(mpd) {
this.manifestInfo.live = mpd.minUpdatePeriod != null;
this.manifestInfo.live = (mpd.type == 'dynamic');
this.manifestInfo.minBufferTime = mpd.minBufferTime ||
shaka.dash.MpdProcessor.DEFAULT_MIN_BUFFER_TIME;

Expand Down Expand Up @@ -394,7 +394,7 @@ shaka.dash.MpdProcessor.prototype.createManifestInfo_ = function(mpd) {
//
// TODO: Remove this hack once SourceBuffer synchronization is
// implemented.
if (mpd.minUpdatePeriod) {
if (mpd.type == 'dynamic') {
periodInfo.duration += 60 * 60 * 24 * 30;
}
}
Expand Down Expand Up @@ -874,7 +874,7 @@ shaka.dash.MpdProcessor.prototype.buildStreamInfoFromSegmentTimeline_ =
// complicated than the calculations in computeAvailableSegmentRange_() since
// the duration of each segment is variable here.
var earliestAvailableTimestamp = 0;
if (mpd.minUpdatePeriod && (timeline.length > 0)) {
if (mpd.type == 'dynamic' && timeline.length > 0) {
var index = Math.max(0, timeline.length - 2);
var timeShiftBufferDepth = mpd.timeShiftBufferDepth || 0;
earliestAvailableTimestamp =
Expand Down Expand Up @@ -949,7 +949,7 @@ shaka.dash.MpdProcessor.prototype.buildStreamInfoFromSegmentTimeline_ =
-1 * segmentTemplate.presentationTimeOffset / segmentTemplate.timescale;
}

if (mpd.minUpdatePeriod && (references.length > 0)) {
if (mpd.type == 'dynamic' && references.length > 0) {
var minBufferTime = this.manifestInfo.minBufferTime;
var bestAvailableTimestamp =
references[references.length - 1].startTime - minBufferTime;
Expand Down Expand Up @@ -1097,7 +1097,7 @@ shaka.dash.MpdProcessor.prototype.buildStreamInfoFromSegmentDuration_ =
// numbers are relative to the start of |period| unless marked otherwise.
var earliestSegmentNumber;
var currentSegmentNumber;
if (mpd.minUpdatePeriod) {
if (mpd.type == 'dynamic') {
var pair = this.computeAvailableSegmentRange_(mpd, period, segmentTemplate);
if (pair) {
// Build the SegmentIndex starting from the earliest available segment.
Expand Down Expand Up @@ -1195,7 +1195,7 @@ shaka.dash.MpdProcessor.prototype.buildStreamInfoFromSegmentDuration_ =
-1 * segmentTemplate.presentationTimeOffset / segmentTemplate.timescale;
}

if (mpd.minUpdatePeriod && (references.length > 0)) {
if (mpd.type == 'dynamic' && references.length > 0) {
shaka.asserts.assert(currentSegmentNumber);
var scaledSegmentDuration =
segmentTemplate.segmentDuration / segmentTemplate.timescale;
Expand Down
71 changes: 43 additions & 28 deletions lib/player/dash_video_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,20 @@ shaka.player.DashVideoSource =
/** @private {string} */
this.mpdUrl_ = mpdUrl;

/** @private {shaka.dash.mpd.Mpd} mpd */
this.mpd_ = null;

/** @private {?shaka.player.DashVideoSource.ContentProtectionCallback} */
this.interpretContentProtection_ = interpretContentProtection;

/** @private {?number} */
this.targetUpdateTime_ = null;

/**
* The last time an MPD was fetched, in wall-clock time.
* The last time the manifest was updated, in wall-clock time.
* @private {?number}
*/
this.lastMpdFetchTime_ = null;
this.lastUpdateTime_ = null;

/** @private {shaka.util.EWMA} */
this.latencyEstimator_ = new shaka.util.EWMA(5 /* half-life in samples */);
Expand Down Expand Up @@ -147,7 +150,7 @@ shaka.player.DashVideoSource.prototype.destroy = function() {

/** @override */
shaka.player.DashVideoSource.prototype.load = function(preferredLanguage) {
this.lastMpdFetchTime_ = Date.now() / 1000.0;
this.lastUpdateTime_ = Date.now() / 1000.0;
var mpdRequest = new shaka.dash.MpdRequest(this.mpdUrl_);

return mpdRequest.send().then(shaka.util.TypedBind(this,
Expand All @@ -157,6 +160,7 @@ shaka.player.DashVideoSource.prototype.load = function(preferredLanguage) {
new shaka.dash.MpdProcessor(this.interpretContentProtection_);
mpdProcessor.process(mpd);

this.mpd_ = mpd;
this.timeShiftBufferDepth_ = mpd.timeShiftBufferDepth || 0;
this.manifestInfo = mpdProcessor.manifestInfo;

Expand All @@ -169,7 +173,7 @@ shaka.player.DashVideoSource.prototype.load = function(preferredLanguage) {
this.sampleMpdLatency_();
// Set a timer to call onUpdate_() so we update the manifest at
// least every @minimumUpdatePeriod seconds.
this.setUpdateTimer_(mpd.minUpdatePeriod || 0);
this.setUpdateTimer_();
}));
}

Expand Down Expand Up @@ -315,33 +319,45 @@ shaka.player.DashVideoSource.prototype.onPlay_ = function(event) {
*/
shaka.player.DashVideoSource.prototype.onUpdate_ = function() {
shaka.asserts.assert(this.manifestInfo && this.manifestInfo.live);
shaka.asserts.assert(this.mpd_);

this.cancelUpdateTimer_();

var currentTime = Date.now() / 1000.0;
this.lastUpdateTime_ = currentTime;

var secondsSinceLastUpdate = currentTime - this.lastMpdFetchTime_;
shaka.log.debug(
'Requesting new MPD... last MPD was retrieved',
secondsSinceLastUpdate,
'seconds ago.');

this.lastMpdFetchTime_ = currentTime;
var mpdRequest = new shaka.dash.MpdRequest(this.mpdUrl_);
var p;
if (this.mpd_.minUpdatePeriod == null) {
// Do not fetch an updated MPD.
p = Promise.resolve();
} else {
// Fetch a new MPD.
var secondsSinceLastUpdate = currentTime - this.lastUpdateTime_;
shaka.log.debug(
'Requesting new MPD... last MPD was retrieved',
secondsSinceLastUpdate,
'seconds ago.');

var mpdRequest = new shaka.dash.MpdRequest(this.mpdUrl_);
p = mpdRequest.send().then(shaka.util.TypedBind(this,
/** @param {!shaka.dash.mpd.Mpd} mpd */
function(mpd) {
this.mpd_ = mpd;
}));
}

mpdRequest.send().then(shaka.util.TypedBind(this,
/** @param {!shaka.dash.mpd.Mpd} mpd */
function(mpd) {
p.then(shaka.util.TypedBind(this,
function() {
shaka.asserts.assert(this.mpd_);
var mpdProcessor =
new shaka.dash.MpdProcessor(this.interpretContentProtection_);
mpdProcessor.process(mpd);

this.timeShiftBufferDepth_ = mpd.timeShiftBufferDepth || 0;
mpdProcessor.process(/** @type {!shaka.dash.mpd.Mpd} */(this.mpd_));
this.timeShiftBufferDepth_ = this.mpd_.timeShiftBufferDepth || 0;
this.updateManifest(mpdProcessor.manifestInfo);
this.evictSegmentReferences_();
this.sampleMpdLatency_();

this.setUpdateTimer_(mpd.minUpdatePeriod || 0);
this.setUpdateTimer_();
this.setTargetUpdateTime_();
})
).catch(shaka.util.TypedBind(this,
Expand All @@ -351,7 +367,7 @@ shaka.player.DashVideoSource.prototype.onUpdate_ = function() {
this.dispatchEvent(event);

// In case the application wants to ignore errors, schedule a retry.
this.setUpdateTimer_(0);
this.setUpdateTimer_();
this.setTargetUpdateTime_();
})
);
Expand Down Expand Up @@ -382,16 +398,15 @@ shaka.player.DashVideoSource.prototype.evictSegmentReferences_ = function() {

/**
* Sets the update timer.
* @param {number} minUpdatePeriod
* @private
*/
shaka.player.DashVideoSource.prototype.setUpdateTimer_ = function(
minUpdatePeriod) {
shaka.player.DashVideoSource.prototype.setUpdateTimer_ = function() {
shaka.asserts.assert(this.manifestInfo && this.manifestInfo.live);
shaka.asserts.assert(this.updateTimer_ == null);
shaka.asserts.assert(this.mpd_ != null);

var updateInterval =
Math.max(minUpdatePeriod,
Math.max(this.mpd_.minUpdatePeriod || 0,
shaka.player.DashVideoSource.MIN_UPDATE_INTERVAL_);
shaka.log.debug('updateInterval', updateInterval);

Expand Down Expand Up @@ -489,9 +504,9 @@ shaka.player.DashVideoSource.prototype.onSeekRangeUpdate_ = function(
// is large, we might run out of indexed segments if we don't do this.
if (this.targetUpdateTime_ != null &&
(this.seekEndTime_ >= this.targetUpdateTime_)) {
var secondsSinceLastUpdate = (Date.now() / 1000.0) - this.lastMpdFetchTime_;
var secondsSinceLastUpdate = (Date.now() / 1000.0) - this.lastUpdateTime_;
// If we're not waiting for an update but we've hit our target update time,
// we must be fetching an MPD. Don't fetch another one.
// we must be processing an updating already. Don't start another one.
var waitingForUpdate = this.updateTimer_ != null;
var DashVideoSource = shaka.player.DashVideoSource;
if (waitingForUpdate &&
Expand Down Expand Up @@ -568,12 +583,12 @@ shaka.player.DashVideoSource.prototype.cancelSeekRangeUpdateTimer_ =
/**
* Measure the latency introduced by fetching, processing, and loading an MPD.
* Should be called after a manifest is loaded or updated. Assumes that
* |lastMpdFetchTime_| is always set when an MPD request begins.
* |lastUpdateTime_| is always set when an update begins.
* @private
*/
shaka.player.DashVideoSource.prototype.sampleMpdLatency_ = function() {
var currentTime = Date.now() / 1000.0;
var latencySample = currentTime - this.lastMpdFetchTime_;
var latencySample = currentTime - this.lastUpdateTime_;
this.latencyEstimator_.sample(1 /* weight */, latencySample);
};

3 changes: 2 additions & 1 deletion spec/mpd_processor_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,8 @@ describe('MpdProcessor', function() {
m.timeShiftBufferDepth = 0;
m.minBufferTime = 0;

// Set @minUpdatePeriod so that the MPD is treated as dynamic.
// Set @minUpdatePeriod and @type so that the MPD is treated as dynamic.
m.type = 'dynamic';
m.minUpdatePeriod = 30;

processor.createManifestInfo_(m);
Expand Down

0 comments on commit cae93b3

Please sign in to comment.