Skip to content

Commit

Permalink
Use segment info to adjust the pres. timeline.
Browse files Browse the repository at this point in the history
* Permit non-zero presentation start times for VOD: some
  presentations have segments which start too far from 0 to
  allow the video element to begin playback; now the player will
  start VOD presentations from the start of the first segment.
  However, segments of the 2nd, 3rd, 4th, etc., Period of a
  multi-Period presentation must still start close to 0 (the
  player will not jump any gaps in the presentation).
* Prohibit seeking to regions at the beginning of the segment
  availability window if segment information is missing from
  that region: sometimes live manifests do not contain all the
  segments in the segment availability window; now the player
  will prohibit seeking to these regions.
* Update definition of live in DashParser to match Player and
  Playhead.
* Simplify PresentationTimeline's constructor by just using setter
  functions.

Issue #341
Closes #348
Issue #357

Change-Id: I96c22774448476bea89ff4014f03b87bdb51ba07
  • Loading branch information
Timothy Drews committed Apr 26, 2016
1 parent a8f83e9 commit 1108700
Show file tree
Hide file tree
Showing 14 changed files with 297 additions and 150 deletions.
106 changes: 52 additions & 54 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,19 @@ shaka.dash.DashParser.InheritanceFrame;

/**
* @typedef {{
* presentationTimeline: !shaka.media.PresentationTimeline,
* period: ?shaka.dash.DashParser.InheritanceFrame,
* periodInfo: ?shaka.dash.DashParser.PeriodInfo,
* adaptationSet: ?shaka.dash.DashParser.InheritanceFrame,
* representation: ?shaka.dash.DashParser.InheritanceFrame,
* bandwidth: (number|undefined),
* maxSegmentDuration: number
* }}
*
* @description
* Contains context data for the streams.
*
* @property {!shaka.media.PresentationTimeline} presentationTimeline
* The PresentationTimeline.
* @property {?shaka.dash.DashParser.InheritanceFrame} period
* The inheritance from the Period element.
* @property {?shaka.dash.DashParser.PeriodInfo} periodInfo
Expand All @@ -180,8 +182,6 @@ shaka.dash.DashParser.InheritanceFrame;
* The inheritance from the Representation element.
* @property {(number|undefined)} bandwidth
* The bandwidth of the Representation.
* @property {number} maxSegmentDuration
* The largest segment duration among the streams which have been parsed.
*/
shaka.dash.DashParser.Context;

Expand Down Expand Up @@ -402,15 +402,6 @@ shaka.dash.DashParser.prototype.parseManifest_ =

var uris = XmlUtils.findChildren(mpd, 'BaseURL').map(XmlUtils.getContents);
var baseUris = shaka.dash.MpdUtils.resolveUris(manifestBaseUris, uris);
/** @type {shaka.dash.DashParser.Context} */
var context = {
period: null,
periodInfo: null,
adaptationSet: null,
representation: null,
bandwidth: undefined,
maxSegmentDuration: 1
};

var minBufferTime =
XmlUtils.parseAttr(mpd, 'minBufferTime', XmlUtils.parseDuration);
Expand All @@ -421,63 +412,70 @@ shaka.dash.DashParser.prototype.parseManifest_ =
mpd, 'availabilityStartTime', XmlUtils.parseDate);
var segmentAvailabilityDuration = XmlUtils.parseAttr(
mpd, 'timeShiftBufferDepth', XmlUtils.parseDuration);
var suggestedDelay = XmlUtils.parseAttr(
var suggestedPresentationDelay = XmlUtils.parseAttr(
mpd, 'suggestedPresentationDelay', XmlUtils.parseDuration);
var maxSegmentDuration = XmlUtils.parseAttr(
mpd, 'maxSegmentDuration', XmlUtils.parseDuration);

var periodsAndDuration = this.parsePeriods_(context, baseUris, mpd);
var duration = periodsAndDuration.duration;
var periods = periodsAndDuration.periods;

// Cannot return until we calculate the clock offset.
var timingElements = XmlUtils.findChildren(mpd, 'UTCTiming');
return this.parseUtcTiming_(timingElements).then(function(offset) {
// Detect calls to stop().
if (!this.networkingEngine_)
return;

// Determine |segmentAvailabilityDuration|.
if (presentationStartTime == null) {
// If this is not live, then ignore segment availability.
segmentAvailabilityDuration = null;
} else if (segmentAvailabilityDuration == null) {
// If there is no availability given and it's live, then the segments
// will always be available.
segmentAvailabilityDuration = Number.POSITIVE_INFINITY;
}

if (this.manifest_) {
this.manifest_.presentationTimeline.setSegmentAvailabilityDuration(
segmentAvailabilityDuration);
this.manifest_.presentationTimeline.setClockOffset(offset);
} else {
var presentationTimeline;
if (this.manifest_) {
presentationTimeline = this.manifest_.presentationTimeline;
} else {
if (segmentAvailabilityDuration) {
// If there's a segment availability window then the presentation is
// live.
if (presentationStartTime != null) {
// Offset the start time by @suggestedPresentationDelay. This has the
// effect of making the segments become available later than they
// actually are, so it causes a delay. Note that the DASH spec
// recommends that a default @suggestedPresentationDelay be used when
// one is not explicitly specified.
presentationStartTime +=
suggestedDelay != null ?
suggestedDelay :
suggestedPresentationDelay != null ?
suggestedPresentationDelay :
shaka.dash.DashParser.DEFAULT_SUGGESTED_PRESENTATION_DELAY_;
} else {
presentationStartTime = 0;
}
} else {
presentationStartTime = null;
}
presentationTimeline =
new shaka.media.PresentationTimeline(presentationStartTime);
}

/** @type {shaka.dash.DashParser.Context} */
var context = {
presentationTimeline: presentationTimeline,
period: null,
periodInfo: null,
adaptationSet: null,
representation: null,
bandwidth: undefined
};

shaka.log.v1('maxSegmentDuration:',
'explict=' + maxSegmentDuration,
'derived=' + context.maxSegmentDuration);
var periodsAndDuration = this.parsePeriods_(context, baseUris, mpd);
var duration = periodsAndDuration.duration;
var periods = periodsAndDuration.periods;

// Cannot return until we calculate the clock offset.
var timingElements = XmlUtils.findChildren(mpd, 'UTCTiming');
return this.parseUtcTiming_(timingElements).then(function(offset) {
// Detect calls to stop().
if (!this.networkingEngine_)
return;

var timeline = new shaka.media.PresentationTimeline(
duration || Number.POSITIVE_INFINITY,
presentationStartTime,
segmentAvailabilityDuration,
maxSegmentDuration || context.maxSegmentDuration,
offset);
presentationTimeline.setDuration(duration || Number.POSITIVE_INFINITY);
presentationTimeline.setClockOffset(offset);
presentationTimeline.setSegmentAvailabilityDuration(
segmentAvailabilityDuration);

if (!this.manifest_) {
// Use @maxSegmentDuration to override smaller, derived values.
presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
this.manifest_ = {
presentationTimeline: presentationTimeline,
periods: periods,
presentationTimeline: timeline,
minBufferTime: minBufferTime || 0
};
}
Expand Down Expand Up @@ -731,13 +729,13 @@ shaka.dash.DashParser.prototype.parseRepresentation_ = function(
var requestInitSegment = this.requestInitSegment_.bind(this);
if (context.representation.segmentBase) {
streamInfo = shaka.dash.SegmentBase.createStream(
context, requestInitSegment, this.segmentIndexMap_);
context, requestInitSegment);
} else if (context.representation.segmentList) {
streamInfo = shaka.dash.SegmentList.createStream(
context, this.segmentIndexMap_, this.manifest_);
context, this.segmentIndexMap_);
} else if (context.representation.segmentTemplate) {
streamInfo = shaka.dash.SegmentTemplate.createStream(
context, requestInitSegment, this.segmentIndexMap_, this.manifest_);
context, requestInitSegment, this.segmentIndexMap_, !!this.manifest_);
} else {
goog.asserts.assert(context.representation.contentType == 'text',
'Must have Segment* with non-text streams.');
Expand Down
2 changes: 2 additions & 0 deletions lib/dash/mpd_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ shaka.dash.MpdUtils.fitSegmentReferences = function(

var firstReference = references[0];
if (firstReference.startTime <= tolerance) {
// Note: if the segment actually starts past 0, the video element should
// automatically jump the gap since the gap is small.
references[0] =
new shaka.media.SegmentReference(
firstReference.position,
Expand Down
7 changes: 4 additions & 3 deletions lib/dash/segment_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ shaka.dash.SegmentBase.createInitSegment = function(context, callback) {
*
* @param {shaka.dash.DashParser.Context} context
* @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
* @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
* @throws shaka.util.Error When there is a parsing error.
* @return {shaka.dash.DashParser.StreamInfo}
*/
shaka.dash.SegmentBase.createStream = function(
context, requestInitSegment, segmentIndexMap) {
shaka.dash.SegmentBase.createStream = function(context, requestInitSegment) {
goog.asserts.assert(context.representation.segmentBase,
'Should only be called with SegmentBase');
// Since SegmentBase does not need updates, simply treat any call as
Expand Down Expand Up @@ -121,6 +119,8 @@ shaka.dash.SegmentBase.createStream = function(
shaka.dash.SegmentBase.createSegmentIndexFromUris = function(
context, requestInitSegment, init, uris,
startByte, endByte, containerType, presentationTimeOffset) {
var presentationTimeline = context.presentationTimeline;
var periodStartTime = context.periodInfo.start;
var periodDuration = context.periodInfo.duration;

// Create a local variable to bind to so we can set to null to help the GC.
Expand Down Expand Up @@ -152,6 +152,7 @@ shaka.dash.SegmentBase.createSegmentIndexFromUris = function(
}

shaka.dash.MpdUtils.fitSegmentReferences(periodDuration, references);
presentationTimeline.notifySegments(periodStartTime, references);

// Since containers are never updated, we don't need to store the
// segmentIndex in the map.
Expand Down
12 changes: 4 additions & 8 deletions lib/dash/segment_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ goog.require('shaka.util.XmlUtils');
*
* @param {shaka.dash.DashParser.Context} context
* @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
* @param {?shakaExtern.Manifest} manifest
* @return {shaka.dash.DashParser.StreamInfo}
*/
shaka.dash.SegmentList.createStream = function(
context, segmentIndexMap, manifest) {
shaka.dash.SegmentList.createStream = function(context, segmentIndexMap) {
goog.asserts.assert(context.representation.segmentList,
'Should only be called with SegmentList');
var SegmentList = shaka.dash.SegmentList;
Expand All @@ -68,14 +66,12 @@ shaka.dash.SegmentList.createStream = function(
shaka.dash.MpdUtils.fitSegmentReferences(
context.periodInfo.duration, references);
if (segmentIndex) {
goog.asserts.assert(manifest, 'This should be an update');
segmentIndex.merge(references);
segmentIndex.evict(
manifest.presentationTimeline.getSegmentAvailabilityStart());
context.presentationTimeline.getSegmentAvailabilityStart());
} else {
context.maxSegmentDuration = references.reduce(
function(max, r) { return Math.max(max, r.endTime - r.startTime); },
context.maxSegmentDuration);
context.presentationTimeline.notifySegments(
context.periodInfo.start, references);
segmentIndex = new shaka.media.SegmentIndex(references);
if (id)
segmentIndexMap[id] = segmentIndex;
Expand Down
18 changes: 9 additions & 9 deletions lib/dash/segment_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ goog.require('shaka.util.Error');
* @param {shaka.dash.DashParser.Context} context
* @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
* @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
* @param {?shakaExtern.Manifest} manifest
* @param {boolean} isUpdate True if the manifest is being updated.
* @throws shaka.util.Error When there is a parsing error.
* @return {shaka.dash.DashParser.StreamInfo}
*/
shaka.dash.SegmentTemplate.createStream = function(
context, requestInitSegment, segmentIndexMap, manifest) {
context, requestInitSegment, segmentIndexMap, isUpdate) {
goog.asserts.assert(context.representation.segmentTemplate,
'Should only be called with SegmentTemplate');
var SegmentTemplate = shaka.dash.SegmentTemplate;
Expand All @@ -58,8 +58,10 @@ shaka.dash.SegmentTemplate.createStream = function(
segmentIndexFunctions = SegmentTemplate.createFromIndexTemplate_(
context, requestInitSegment, init, info);
} else if (info.segmentDuration) {
context.maxSegmentDuration =
Math.max(context.maxSegmentDuration, info.segmentDuration);
if (!isUpdate) {
context.presentationTimeline.notifyMaxSegmentDuration(
info.segmentDuration);
}
segmentIndexFunctions = SegmentTemplate.createFromDuration_(context, info);
} else {
/** @type {shaka.media.SegmentIndex} */
Expand All @@ -75,14 +77,12 @@ shaka.dash.SegmentTemplate.createStream = function(
shaka.dash.MpdUtils.fitSegmentReferences(
context.periodInfo.duration, references);
if (segmentIndex) {
goog.asserts.assert(manifest, 'This should be an update');
segmentIndex.merge(references);
segmentIndex.evict(
manifest.presentationTimeline.getSegmentAvailabilityStart());
context.presentationTimeline.getSegmentAvailabilityStart());
} else {
context.maxSegmentDuration = references.reduce(
function(max, r) { return Math.max(max, r.endTime - r.startTime); },
context.maxSegmentDuration);
context.presentationTimeline.notifySegments(
context.periodInfo.start, references);
segmentIndex = new shaka.media.SegmentIndex(references);
if (id)
segmentIndexMap[id] = segmentIndex;
Expand Down
8 changes: 4 additions & 4 deletions lib/media/playhead.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,13 @@ shaka.media.Playhead.prototype.getStartTime_ = function() {
if (this.timeline_.getDuration() < Number.POSITIVE_INFINITY) {
// If the presentation is VOD, or if the presentation is live but has
// finished broadcasting, then start from the beginning.
startTime = this.timeline_.getSegmentAvailabilityStart();
startTime = this.timeline_.getEarliestStart();
} else {
// Otherwise, start near the live-edge, but ensure that the startup
// buffering goal can be met
startTime = Math.max(
this.timeline_.getSegmentAvailabilityEnd() - this.rebufferingGoal_,
this.timeline_.getSegmentAvailabilityStart());
this.timeline_.getEarliestStart());
}
return startTime;
};
Expand Down Expand Up @@ -330,7 +330,7 @@ shaka.media.Playhead.prototype.onSeeking_ = function() {
*/
shaka.media.Playhead.prototype.reposition_ = function(currentTime) {
var availabilityDuration = this.timeline_.getSegmentAvailabilityDuration();
var start = this.timeline_.getSegmentAvailabilityStart();
var start = this.timeline_.getEarliestStart();
var end = this.timeline_.getSegmentAvailabilityEnd();

if (availabilityDuration == null ||
Expand Down Expand Up @@ -390,7 +390,7 @@ shaka.media.Playhead.prototype.reposition_ = function(currentTime) {
* @private
*/
shaka.media.Playhead.prototype.clampTime_ = function(time) {
var start = this.timeline_.getSegmentAvailabilityStart();
var start = this.timeline_.getEarliestStart();
if (time < start) return start;

var end = this.timeline_.getSegmentAvailabilityEnd();
Expand Down
Loading

0 comments on commit 1108700

Please sign in to comment.