Skip to content

Commit

Permalink
In-progress recording (IPR) support, phase 2
Browse files Browse the repository at this point in the history
Behavior for IPR streams:
 * offline storage disallowed
 * segment references will not be stretched to the period
 * seek range starts at 0
 * seek range end is calculated like the live edge
 * seek bar is from 0 to duration, not the seek range

Closes #477

Change-Id: Ia36874bb7208c2473c79cb817395ce03925b8c95
  • Loading branch information
joeyparrish committed Aug 29, 2016
1 parent eafe954 commit 3cad924
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 30 deletions.
11 changes: 8 additions & 3 deletions demo/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,10 +547,11 @@ ShakaControls.prototype.updateTimeAndSeekRange_ = function() {
var bufferedEnd = bufferedLength ? this.video_.buffered.end(0) : 0;
var seekRange = this.player_.seekRange();

this.seekBar_.min = seekRange.start;
this.seekBar_.max = seekRange.end;

if (this.player_.isLive()) {
// For live, the seek bar size is the seek range.
this.seekBar_.min = seekRange.start;
this.seekBar_.max = seekRange.end;

// The amount of time we are behind the live edge.
var behindLive = Math.floor(seekRange.end - displayTime);
displayTime = Math.max(0, behindLive);
Expand All @@ -572,6 +573,10 @@ ShakaControls.prototype.updateTimeAndSeekRange_ = function() {
this.seekBar_.value = seekRange.end - displayTime;
}
} else {
// For VOD and IPR, the seek bar size is from 0 to duration.
this.seekBar_.min = 0;
this.seekBar_.max = duration;

var showHour = duration >= 3600;
this.currentTime_.textContent =
this.buildTimeString_(displayTime, showHour);
Expand Down
4 changes: 4 additions & 0 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ shaka.dash.DashParser.InheritanceFrame;

/**
* @typedef {{
* dynamic: boolean,
* presentationTimeline: !shaka.media.PresentationTimeline,
* period: ?shaka.dash.DashParser.InheritanceFrame,
* periodInfo: ?shaka.dash.DashParser.PeriodInfo,
Expand All @@ -181,6 +182,8 @@ shaka.dash.DashParser.InheritanceFrame;
* @description
* Contains context data for the streams.
*
* @property {boolean} dynamic
* True if the MPD is dynamic (not all segments available at once)
* @property {!shaka.media.PresentationTimeline} presentationTimeline
* The PresentationTimeline.
* @property {?shaka.dash.DashParser.InheritanceFrame} period
Expand Down Expand Up @@ -487,6 +490,7 @@ shaka.dash.DashParser.prototype.parseManifest_ =

/** @type {shaka.dash.DashParser.Context} */
var context = {
dynamic: mpdType != 'static',
presentationTimeline: presentationTimeline,
period: null,
periodInfo: null,
Expand Down
9 changes: 7 additions & 2 deletions lib/dash/mpd_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,12 @@ shaka.dash.MpdUtils.createTimeline = function(
* contracts the last SegmentReference so it ends at the end of its Period for
* VOD presentations.
*
* @param {boolean} dynamic
* @param {?number} periodDuration
* @param {!Array.<!shaka.media.SegmentReference>} references
*/
shaka.dash.MpdUtils.fitSegmentReferences = function(
periodDuration, references) {
dynamic, periodDuration, references) {
if (references.length == 0)
return;

Expand All @@ -311,8 +312,12 @@ shaka.dash.MpdUtils.fitSegmentReferences = function(
firstReference.startByte, firstReference.endByte);
}

if (periodDuration == null || periodDuration == Infinity)
if (dynamic)
return;
goog.asserts.assert(periodDuration != null,
'Period duration must be known for static content!');
goog.asserts.assert(periodDuration != Infinity,
'Period duration must be finite for static content!');

var lastReference = references[references.length - 1];

Expand Down
3 changes: 2 additions & 1 deletion lib/dash/segment_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ shaka.dash.SegmentBase.createSegmentIndexFromUris = function(
presentationTimeOffset);
}

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

// Since containers are never updated, we don't need to store the
Expand Down
2 changes: 1 addition & 1 deletion lib/dash/segment_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ shaka.dash.SegmentList.createStream = function(context, segmentIndexMap) {
context.periodInfo.duration, info.startNumber,
context.representation.baseUris, info);
shaka.dash.MpdUtils.fitSegmentReferences(
context.periodInfo.duration, references);
context.dynamic, context.periodInfo.duration, references);
if (segmentIndex) {
segmentIndex.merge(references);
var start = context.presentationTimeline.getSegmentAvailabilityStart();
Expand Down
2 changes: 1 addition & 1 deletion lib/dash/segment_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ shaka.dash.SegmentTemplate.createStream = function(

var references = SegmentTemplate.createFromTimeline_(context, info);
shaka.dash.MpdUtils.fitSegmentReferences(
context.periodInfo.duration, references);
context.dynamic, context.periodInfo.duration, references);
if (segmentIndex) {
segmentIndex.merge(references);
var start = context.presentationTimeline.getSegmentAvailabilityStart();
Expand Down
7 changes: 4 additions & 3 deletions lib/media/presentation_timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityStart =
*/
shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityEnd =
function() {
if (!this.isLive())
if (!this.isLive() && !this.isInProgress())
return this.duration_;

return Math.min(this.getLiveEdge_(), this.duration_);
Expand Down Expand Up @@ -311,8 +311,9 @@ if (!COMPILED) {
'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,
// IPR streams should have a start time, and segments should not expire.
goog.asserts.assert(this.presentationStartTime_ != null &&
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
Expand Down
3 changes: 2 additions & 1 deletion lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ shaka.offline.Storage.prototype.store = function(
this.manifest_ = data.manifest;
this.drmEngine_ = data.drmEngine;

if (this.manifest_.presentationTimeline.isLive()) {
if (this.manifest_.presentationTimeline.isLive() ||
this.manifest_.presentationTimeline.isInProgress()) {
throw new shaka.util.Error(
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.CANNOT_STORE_LIVE_OFFLINE, manifestUri);
Expand Down
3 changes: 2 additions & 1 deletion lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,8 @@ shaka.util.Error.Code = {
'MALFORMED_OFFLINE_URI': 9004,

/**
* The specified manifest is live. Live manifests cannot be stored offline.
* The specified content is live or in-progress.
* Live and in-progress streams cannot be stored offline.
* <br> error.data[0] is the URI.
*/
'CANNOT_STORE_LIVE_OFFLINE': 9005,
Expand Down
60 changes: 43 additions & 17 deletions test/media/presentation_timeline_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ describe('PresentationTimeline', function() {
* @return {shaka.media.PresentationTimeline}
*/
function makeIprTimeline(duration) {
var now = Date.now() / 1000;
var timeline = makePresentationTimeline(
/* static */ false, duration, /* start time */ null,
/* static */ false, duration, /* start time */ now,
/* availability */ Infinity, /* max seg dur */ 10,
/* clock offset */ 0);
expect(timeline.isLive()).toBe(false);
Expand Down Expand Up @@ -170,17 +171,36 @@ describe('PresentationTimeline', function() {
});

describe('getSegmentAvailabilityEnd', function() {
it('returns duration for VOD and IPR', function() {
var timeline1 = makeVodTimeline(/* duration */ 60);
var timeline2 = makeIprTimeline(/* duration */ 60);
it('returns duration for VOD', function() {
var timeline = makeVodTimeline(/* duration */ 60);

setElapsed(0);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(60);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(60);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);

setElapsed(100);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(60);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(60);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
});

it('calculates time for IPR', function() {
var timeline = makeIprTimeline(/* duration */ 60);

setElapsed(0);
expect(timeline.getSegmentAvailabilityEnd()).toBe(0);

setElapsed(10);
expect(timeline.getSegmentAvailabilityEnd()).toBe(0);

setElapsed(11);
expect(timeline.getSegmentAvailabilityEnd()).toBe(1);

setElapsed(69);
expect(timeline.getSegmentAvailabilityEnd()).toBe(59);

setElapsed(70);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);

setElapsed(100);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
});

it('calculates time for live', function() {
Expand Down Expand Up @@ -228,17 +248,23 @@ describe('PresentationTimeline', function() {
});

describe('setDuration', function() {
it('affects availability end for VOD and IPR', function() {
it('affects availability end for VOD', function() {
setElapsed(0);
var timeline1 = makeVodTimeline(/* duration */ 60);
var timeline2 = makeIprTimeline(/* duration */ 60);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(60);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(60);
var timeline = makeVodTimeline(/* duration */ 60);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);

timeline.setDuration(90);
expect(timeline.getSegmentAvailabilityEnd()).toBe(90);
});

it('affects availability end for IPR', function() {
var timeline = makeIprTimeline(/* duration */ 60);

setElapsed(85);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);

timeline1.setDuration(90);
timeline2.setDuration(90);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(90);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(90);
timeline.setDuration(90);
expect(timeline.getSegmentAvailabilityEnd()).toBe(75);
});
});

Expand Down

0 comments on commit 3cad924

Please sign in to comment.