diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index f976d5ddd3..b5274d9388 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -199,7 +199,8 @@ shaka.dash.DashParser.Context; * @typedef {{ * start: number, * duration: ?number, - * node: !Element + * node: !Element, + * isLastPeriod: boolean * }} * * @description @@ -212,6 +213,8 @@ shaka.dash.DashParser.Context; * will be non-null for all periods except the last. * @property {!Element} node * The XML Node for the Period. + * @property {boolean} isLastPeriod + * Whether this Period is the last one in the manifest. */ shaka.dash.DashParser.PeriodInfo; @@ -465,6 +468,7 @@ shaka.dash.DashParser.prototype.parseManifest_ = /** @type {shaka.dash.DashParser.Context} */ var context = { + // Don't base on updatePeriod_ since emsg boxes can cause manifest updates. dynamic: mpdType != 'static', presentationTimeline: presentationTimeline, period: null, @@ -567,7 +571,8 @@ shaka.dash.DashParser.prototype.parsePeriods_ = function( var info = { start: start, duration: periodDuration, - node: elem + node: elem, + isLastPeriod: periodDuration == null || i == periodNodes.length - 1 }; var period = this.parsePeriod_(context, baseUris, info); periods.push(period); @@ -662,7 +667,7 @@ shaka.dash.DashParser.prototype.parsePeriod_ = function( .map(function(as) { return as.representationIds; }) .reduce(Functional.collapseArrays, []); var uniqueRepIds = representationIds.filter(Functional.isNotDuplicate); - if (representationIds.length != uniqueRepIds.length) { + if (context.dynamic && representationIds.length != uniqueRepIds.length) { throw new shaka.util.Error( shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.DASH_DUPLICATE_REPRESENTATION_ID); diff --git a/lib/dash/mpd_utils.js b/lib/dash/mpd_utils.js index a0d50a7f48..3c2e6529db 100644 --- a/lib/dash/mpd_utils.js +++ b/lib/dash/mpd_utils.js @@ -297,12 +297,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 {boolean} fitLast * @param {?number} periodDuration * @param {!Array.} references */ shaka.dash.MpdUtils.fitSegmentReferences = function( - dynamic, periodDuration, references) { + fitLast, periodDuration, references) { if (references.length == 0) return; @@ -321,7 +321,7 @@ shaka.dash.MpdUtils.fitSegmentReferences = function( firstReference.startByte, firstReference.endByte); } - if (dynamic) + if (!fitLast) return; goog.asserts.assert(periodDuration != null, 'Period duration must be known for static content!'); diff --git a/lib/dash/segment_base.js b/lib/dash/segment_base.js index cbe93bae3d..9b929f3979 100644 --- a/lib/dash/segment_base.js +++ b/lib/dash/segment_base.js @@ -123,6 +123,7 @@ shaka.dash.SegmentBase.createSegmentIndexFromUris = function( context, requestInitSegment, init, uris, startByte, endByte, containerType, presentationTimeOffset) { var presentationTimeline = context.presentationTimeline; + var fitLast = !context.dynamic || !context.periodInfo.isLastPeriod; var periodStartTime = context.periodInfo.start; var periodDuration = context.periodInfo.duration; @@ -154,7 +155,7 @@ shaka.dash.SegmentBase.createSegmentIndexFromUris = function( } shaka.dash.MpdUtils.fitSegmentReferences( - context.dynamic, periodDuration, references); + fitLast, periodDuration, references); presentationTimeline.notifySegments(periodStartTime, references); // Since containers are never updated, we don't need to store the diff --git a/lib/dash/segment_list.js b/lib/dash/segment_list.js index 0c241032f9..32cd1cf2a3 100644 --- a/lib/dash/segment_list.js +++ b/lib/dash/segment_list.js @@ -65,7 +65,8 @@ shaka.dash.SegmentList.createStream = function(context, segmentIndexMap) { context.periodInfo.duration, info.startNumber, context.representation.baseUris, info); shaka.dash.MpdUtils.fitSegmentReferences( - context.dynamic, context.periodInfo.duration, references); + !context.dynamic || !context.periodInfo.isLastPeriod, + context.periodInfo.duration, references); if (segmentIndex) { segmentIndex.merge(references); var start = context.presentationTimeline.getSegmentAvailabilityStart(); @@ -74,7 +75,7 @@ shaka.dash.SegmentList.createStream = function(context, segmentIndexMap) { context.presentationTimeline.notifySegments( context.periodInfo.start, references); segmentIndex = new shaka.media.SegmentIndex(references); - if (id) + if (id && context.dynamic) segmentIndexMap[id] = segmentIndex; } diff --git a/lib/dash/segment_template.js b/lib/dash/segment_template.js index 7d81c411da..ad57d5e0bf 100644 --- a/lib/dash/segment_template.js +++ b/lib/dash/segment_template.js @@ -78,7 +78,8 @@ shaka.dash.SegmentTemplate.createStream = function( var references = SegmentTemplate.createFromTimeline_(context, info); shaka.dash.MpdUtils.fitSegmentReferences( - context.dynamic, context.periodInfo.duration, references); + !context.dynamic || !context.periodInfo.isLastPeriod, + context.periodInfo.duration, references); if (segmentIndex) { segmentIndex.merge(references); var start = context.presentationTimeline.getSegmentAvailabilityStart(); @@ -87,7 +88,7 @@ shaka.dash.SegmentTemplate.createStream = function( context.presentationTimeline.notifySegments( context.periodInfo.start, references); segmentIndex = new shaka.media.SegmentIndex(references); - if (id) + if (id && context.dynamic) segmentIndexMap[id] = segmentIndex; } diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js index b4e985d9a4..fc9af225e9 100644 --- a/test/dash/dash_parser_manifest_unit.js +++ b/test/dash/dash_parser_manifest_unit.js @@ -675,9 +675,9 @@ describe('DashParser Manifest', function() { Dash.testFails(done, source, error); }); - it('duplicate Representation ids', function(done) { + it('duplicate Representation ids with live', function(done) { var source = [ - '', + '', ' ', ' ', ' ', @@ -817,6 +817,56 @@ describe('DashParser Manifest', function() { .then(done); }); + it('ignores duplicate Representation IDs for VOD', function(done) { + var source = [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '' + ].join('\n'); + + // See https://goo.gl/BAM3mi + // The old error was that with SegmentTimeline, duplicate Representation IDs + // would use the same segment index, so they would have the same references. + // This test proves that duplicate Representation IDs are allowed for VOD + // and that error doesn't occur. + fakeNetEngine.setResponseMapAsText({'dummy://foo': source}); + parser.start('dummy://foo', playerInterface) + .then(function(manifest) { + expect(manifest.periods.length).toBe(1); + expect(manifest.periods[0].variants.length).toBe(2); + + var variant1 = manifest.periods[0].variants[0]; + var variant2 = manifest.periods[0].variants[1]; + expect(variant1.video).toBeTruthy(); + expect(variant2.video).toBeTruthy(); + expect(variant1.video.getSegmentReference(1).getUris()) + .toEqual(['dummy://foo/1.mp4']); + expect(variant2.video.getSegmentReference(1).getUris()) + .toEqual(['dummy://foo/2.mp4']); + }) + .catch(fail) + .then(done); + }); + /** * @param {string} manifestText * @param {Uint8Array} emsgUpdate