diff --git a/lib/dash/mpd_parser.js b/lib/dash/mpd_parser.js index 8cd94efea..2f1776248 100644 --- a/lib/dash/mpd_parser.js +++ b/lib/dash/mpd_parser.js @@ -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; @@ -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; @@ -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 = @@ -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', diff --git a/lib/dash/mpd_processor.js b/lib/dash/mpd_processor.js index 782df73fb..6d8c483f2 100644 --- a/lib/dash/mpd_processor.js +++ b/lib/dash/mpd_processor.js @@ -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; } @@ -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; @@ -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; } } @@ -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 = @@ -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; @@ -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. @@ -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; diff --git a/lib/player/dash_video_source.js b/lib/player/dash_video_source.js index f946292cc..3a510243c 100644 --- a/lib/player/dash_video_source.js +++ b/lib/player/dash_video_source.js @@ -69,6 +69,9 @@ 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; @@ -76,10 +79,10 @@ shaka.player.DashVideoSource = 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 */); @@ -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, @@ -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; @@ -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_(); })); } @@ -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.'); + 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.'); - this.lastMpdFetchTime_ = currentTime; - var mpdRequest = new shaka.dash.MpdRequest(this.mpdUrl_); + 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, @@ -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_(); }) ); @@ -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); @@ -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 && @@ -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); }; diff --git a/spec/mpd_processor_spec.js b/spec/mpd_processor_spec.js index bcac619d3..95be6d1df 100644 --- a/spec/mpd_processor_spec.js +++ b/spec/mpd_processor_spec.js @@ -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);