Choose live playback by type='dynamic'.

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
This commit is contained in:
Joey Parrish
2015-04-25 12:11:31 -07:00
parent 0b003d3819
commit 7dc3c1b0a9
4 changed files with 55 additions and 49 deletions
+4 -14
View File
@@ -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',
+7 -7
View File
@@ -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;
+42 -27
View File
@@ -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);
};
+2 -1
View File
@@ -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);