Skip to content

Commit

Permalink
In-progress recording (IPR) support, phase 1
Browse files Browse the repository at this point in the history
This change creates a new model which divides DASH streams into VOD,
IPR, and live.  It is possible to create manifests which do not fit
into any of the three categories according to our model, so we now
assert that our input fits cleanly into one of the three.

Inline manifests used in our tests had to be updated to conform to
the new model.  All external test assets have been verified to fit
into these categories.

This is phase 1 of IPR support.  There should be no behavior change
in this CL.

In phase 2, we will make various other parts of the library aware of
IPR so that IPR-specific behaviors can be achieved.

Issue #477

Change-Id: I395d3a0c8c9825a3cd2efde263b8493ce0920ed9
  • Loading branch information
joeyparrish committed Aug 29, 2016
1 parent 6351183 commit c7e73e0
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 207 deletions.
3 changes: 3 additions & 0 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ shaka.dash.DashParser.prototype.parseManifest_ =
mpd, 'suggestedPresentationDelay', XmlUtils.parseDuration);
var maxSegmentDuration = XmlUtils.parseAttr(
mpd, 'maxSegmentDuration', XmlUtils.parseDuration);
var mpdType = mpd.getAttribute('type') || 'static';

var presentationTimeline;
if (this.manifest_) {
Expand All @@ -498,13 +499,15 @@ shaka.dash.DashParser.prototype.parseManifest_ =
var duration = periodsAndDuration.duration;
var periods = periodsAndDuration.periods;

presentationTimeline.setStatic(mpdType == 'static');
presentationTimeline.setDuration(duration || Number.POSITIVE_INFINITY);
presentationTimeline.setSegmentAvailabilityDuration(
segmentAvailabilityDuration != null ?
segmentAvailabilityDuration :
Number.POSITIVE_INFINITY);
// Use @maxSegmentDuration to override smaller, derived values.
presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
if (!COMPILED) presentationTimeline.assertIsValid();

if (this.manifest_) {
// This is a manifest update, so we're done.
Expand Down
74 changes: 64 additions & 10 deletions lib/media/presentation_timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ shaka.media.PresentationTimeline = function(
this.presentationDelay_ = presentationDelay;

/** @private {number} */
this.duration_ = Number.POSITIVE_INFINITY;
this.duration_ = Infinity;

/** @private {number} */
this.segmentAvailabilityDuration_ = Number.POSITIVE_INFINITY;
this.segmentAvailabilityDuration_ = Infinity;

/** @private {?number} */
this.maxSegmentDuration_ = 1;
Expand All @@ -58,12 +58,15 @@ shaka.media.PresentationTimeline = function(

/** @private {number} */
this.clockOffset_ = 0;

/** @private {boolean} */
this.static_ = true;
};


/**
* @return {number} The presentation's duration in seconds.
* POSITIVE_INFINITY indicates that the presentation continues indefinitely.
* Infinity indicates that the presentation continues indefinitely.
* @export
*/
shaka.media.PresentationTimeline.prototype.getDuration = function() {
Expand All @@ -75,7 +78,7 @@ shaka.media.PresentationTimeline.prototype.getDuration = function() {
* Sets the presentation's duration.
*
* @param {number} duration The presentation's duration in seconds.
* POSITIVE_INFINITY indicates that the presentation continues indefinitely.
* Infinity indicates that the presentation continues indefinitely.
* @export
*/
shaka.media.PresentationTimeline.prototype.setDuration = function(duration) {
Expand All @@ -97,11 +100,24 @@ shaka.media.PresentationTimeline.prototype.setClockOffset = function(offset) {
};


/**
* Sets the presentation's static flag.
*
* @param {boolean} isStatic If true, the presentation is static, meaning all
* segments are available at once.
* @export
*/
shaka.media.PresentationTimeline.prototype.setStatic = function(isStatic) {
// NOTE: the argument name is not "static" because that's a keyword in ES6
this.static_ = isStatic;
};


/**
* Gets the presentation's segment availability duration, which is the amount
* of time, in seconds, that the start of a segment remains available after the
* live-edge moves past the end of that segment. POSITIVE_INFINITY indicates
* that segments remain available indefinitely. For example, if your live
* live-edge moves past the end of that segment. Infinity indicates that
* segments remain available indefinitely. For example, if your live
* presentation has a 5 minute DVR window and your segments are 10 seconds long
* then the segment availability duration should be 4 minutes and 50 seconds.
*
Expand Down Expand Up @@ -186,8 +202,19 @@ shaka.media.PresentationTimeline.prototype.notifyMaxSegmentDuration = function(
* @export
*/
shaka.media.PresentationTimeline.prototype.isLive = function() {
return this.duration_ == Number.POSITIVE_INFINITY ||
this.segmentAvailabilityDuration_ < Number.POSITIVE_INFINITY;
return this.duration_ == Infinity &&
!this.static_;
};


/**
* @return {boolean} True if the presentation is in progress (meaning not live,
* but also not completely available); otherwise, return false.
* @export
*/
shaka.media.PresentationTimeline.prototype.isInProgress = function() {
return this.duration_ != Infinity &&
!this.static_;
};


Expand Down Expand Up @@ -219,7 +246,7 @@ shaka.media.PresentationTimeline.prototype.getEarliestStart = function() {
*/
shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityStart =
function() {
if (this.segmentAvailabilityDuration_ == Number.POSITIVE_INFINITY)
if (this.segmentAvailabilityDuration_ == Infinity)
return 0;

var start = this.getSegmentAvailabilityEnd() -
Expand All @@ -239,7 +266,7 @@ shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityStart =
*/
shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityEnd =
function() {
if (this.presentationStartTime_ == null || !this.isLive())
if (!this.isLive())
return this.duration_;

return Math.min(this.getLiveEdge_(), this.duration_);
Expand Down Expand Up @@ -270,3 +297,30 @@ shaka.media.PresentationTimeline.prototype.getLiveEdge_ = function() {
0, now - this.maxSegmentDuration_ - this.presentationStartTime_);
};


if (!COMPILED) {
/**
* Debug only: assert that the timeline parameters make sense for the type of
* presentation (VOD, IPR, live).
*/
shaka.media.PresentationTimeline.prototype.assertIsValid = function() {
if (this.isLive()) {
// Implied by isLive(): infinite and dynamic.
// Live streams should have a start time.
goog.asserts.assert(this.presentationStartTime_ != null,
'Detected as live stream, but does not match our model of live!');
} else if (this.isInProgress()) {
// Implied by isInProgress(): finite and dynamic.
// IPR segments should not expire.
goog.asserts.assert(this.segmentAvailabilityDuration_ == Infinity,
'Detected as IPR stream, but does not match our model of IPR!');
} else { // VOD
// VOD segments should not expire and the presentation should be finite
// and static.
goog.asserts.assert(this.segmentAvailabilityDuration_ == Infinity &&
this.duration_ != Infinity &&
this.static_,
'Detected as VOD stream, but does not match our model of VOD!');
}
};
}
15 changes: 13 additions & 2 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,8 +668,7 @@ shaka.Player.prototype.getManifestUri = function() {


/**
* @return {boolean} True if the current stream is live. False if the stream is
* VOD or if there is no active stream.
* @return {boolean} True if the current stream is live. False otherwise.
* @export
*/
shaka.Player.prototype.isLive = function() {
Expand All @@ -679,6 +678,18 @@ shaka.Player.prototype.isLive = function() {
};


/**
* @return {boolean} True if the current stream is in-progress VOD.
* False otherwise.
* @export
*/
shaka.Player.prototype.isInProgress = function() {
return this.manifest_ ?
this.manifest_.presentationTimeline.isInProgress() :
false;
};


/**
* Get the seekable range for the current stream.
* @return {{start: number, end: number}}
Expand Down
23 changes: 14 additions & 9 deletions test/dash/dash_parser_live_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ describe('DashParser.Live', function() {
* Makes a simple live manifest with the given representation contents.
*
* @param {!Array.<string>} lines
* @param {number} updatePeriod
* @param {number} updateTime
* @param {number=} opt_duration
* @return {string}
*/
function makeSimpleLiveManifestText(lines, updatePeriod, opt_duration) {
function makeSimpleLiveManifestText(lines, updateTime, opt_duration) {
var attr = opt_duration ? 'duration="PT' + opt_duration + 'S"' : '';
var template = [
'<MPD type="dynamic" minimumUpdatePeriod="PT%(updatePeriod)dS">',
'<MPD type="dynamic" minimumUpdatePeriod="PT%(updateTime)dS"',
' availabilityStartTime="1970-01-01T00:00:00Z">',
' <Period id="1" %(attr)s>',
' <AdaptationSet mimeType="video/mp4">',
' <Representation id="3" bandwidth="500">',
Expand All @@ -91,7 +92,7 @@ describe('DashParser.Live', function() {
var text = sprintf(template, {
attr: attr,
contents: lines.join('\n'),
updatePeriod: updatePeriod
updateTime: updateTime
});
return text;
}
Expand Down Expand Up @@ -319,7 +320,8 @@ describe('DashParser.Live', function() {
'<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />'
];
var template = [
'<MPD type="dynamic" minimumUpdatePeriod="PT%(updateTime)dS">',
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
' minimumUpdatePeriod="PT%(updateTime)dS">',
' <Period id="4">',
' <AdaptationSet mimeType="video/mp4">',
' <Representation id="6" bandwidth="500">',
Expand Down Expand Up @@ -352,7 +354,8 @@ describe('DashParser.Live', function() {

it('uses redirect URL for manifest BaseURL', function(done) {
var template = [
'<MPD type="dynamic" minimumUpdatePeriod="PT%(updatePeriod)dS">',
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
' minimumUpdatePeriod="PT%(updateTime)dS">',
' <Period id="1" duration="PT30S">',
' <AdaptationSet mimeType="video/mp4">',
' <Representation id="3" bandwidth="500">',
Expand All @@ -368,7 +371,7 @@ describe('DashParser.Live', function() {
' </Period>',
'</MPD>'
].join('\n');
var manifestText = sprintf(template, {updatePeriod: updateTime});
var manifestText = sprintf(template, {updateTime: updateTime});
var manifestData = shaka.util.StringUtils.toUTF8(manifestText);
var originalUri = 'http://example.com/';
var redirectedUri = 'http://redirected.com/';
Expand Down Expand Up @@ -447,7 +450,8 @@ describe('DashParser.Live', function() {

it('uses Mpd.Location', function(done) {
var manifest = [
'<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S">',
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
' minimumUpdatePeriod="PT' + updateTime + 'S">',
' <Location>http://foobar</Location>',
' <Location>http://foobar2</Location>',
' <Period id="1" duration="PT10S">',
Expand Down Expand Up @@ -648,7 +652,8 @@ describe('DashParser.Live', function() {

beforeEach(function() {
var manifest = [
'<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S">',
'<MPD type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z"',
' minimumUpdatePeriod="PT' + updateTime + 'S">',
' <UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014"',
' value="http://foo.bar/date" />',
' <UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014"',
Expand Down
12 changes: 6 additions & 6 deletions test/dash/dash_parser_manifest_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ describe('DashParser.Manifest', function() {
' </AdaptationSet>'
].join('\n');
var template = [
'<MPD minBufferTime="PT75S">',
'<MPD mediaPresentationDuration="PT75S">',
' <Period id="1">',
'%(periodContents)s',
' </Period>',
Expand Down Expand Up @@ -483,12 +483,12 @@ describe('DashParser.Manifest', function() {
function makeManifest(lines) {
var template = [
'<MPD type="dynamic"',
' availabilityStartTime="1970-01-01T00:00:00Z"',
' timeShiftBufferDepth="PT60S"',
' maxSegmentDuration="PT5S"',
' suggestedPresentationDelay="PT0S">',
' availabilityStartTime="1970-01-01T00:00:00Z"',
' timeShiftBufferDepth="PT60S"',
' maxSegmentDuration="PT5S"',
' suggestedPresentationDelay="PT0S">',
' %s',
' <Period duration="PT30M">',
' <Period>',
' <AdaptationSet mimeType="video/mp4">',
' <Representation bandwidth="500">',
' <BaseURL>http://example.com</BaseURL>',
Expand Down
18 changes: 9 additions & 9 deletions test/dash/dash_parser_segment_base_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('DashParser.SegmentBase', function() {

it('requests init data for WebM', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <AdaptationSet mimeType="video/webm">',
' <Representation>',
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('DashParser.SegmentBase', function() {

it('inherits from Period', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <BaseURL>http://example.com</BaseURL>',
' <SegmentBase indexRange="100-200" timescale="9000">',
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('DashParser.SegmentBase', function() {

it('inherits from AdaptationSet', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <AdaptationSet mimeType="video/mp4">',
' <BaseURL>http://example.com</BaseURL>',
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('DashParser.SegmentBase', function() {

it('does not require sourceURL in Initialization', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <AdaptationSet mimeType="video/mp4">',
' <Representation>',
Expand Down Expand Up @@ -166,7 +166,7 @@ describe('DashParser.SegmentBase', function() {

it('merges across levels', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <BaseURL>http://example.com</BaseURL>',
' <SegmentBase timescale="9000">',
Expand Down Expand Up @@ -206,7 +206,7 @@ describe('DashParser.SegmentBase', function() {

it('merges and overrides across levels', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <BaseURL>http://example.com</BaseURL>',
' <SegmentBase indexRange="0-10" timescale="9000">',
Expand Down Expand Up @@ -244,7 +244,7 @@ describe('DashParser.SegmentBase', function() {
describe('fails for', function() {
it('unsupported container', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <BaseURL>http://example.com</BaseURL>',
' <AdaptationSet mimeType="video/cat">',
Expand All @@ -263,7 +263,7 @@ describe('DashParser.SegmentBase', function() {

it('missing init segment for WebM', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <BaseURL>http://example.com</BaseURL>',
' <AdaptationSet mimeType="video/webm">',
Expand All @@ -282,7 +282,7 @@ describe('DashParser.SegmentBase', function() {

it('no @indexRange nor RepresentationIndex', function(done) {
var source = [
'<MPD>',
'<MPD mediaPresentationDuration="PT75S">',
' <Period>',
' <BaseURL>http://example.com</BaseURL>',
' <AdaptationSet mimeType="video/webm">',
Expand Down

0 comments on commit c7e73e0

Please sign in to comment.