From fdcee73fb4ea3a244dbdef5404d3d8f4c0a016e2 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Mon, 24 Aug 2015 09:27:30 -0700 Subject: [PATCH] Added partial support to specify negative r values in SegmentTimeline Can now specify negative values for the r attribute in SegmentTimeline. Not supported with live when used on the last element. Issue #162 Change-Id: I7206e02f7af469a7daf1e4710befb2d102f4f979 --- lib/dash/list_segment_index_source.js | 3 +- lib/dash/mpd_parser.js | 14 +++- lib/dash/mpd_utils.js | 59 +++++++++------ lib/dash/timeline_segment_index_source.js | 3 +- spec/mpd_utils_spec.js | 87 +++++++++++++++++++++++ 5 files changed, 140 insertions(+), 26 deletions(-) diff --git a/lib/dash/list_segment_index_source.js b/lib/dash/list_segment_index_source.js index 826a8a140..8499a5558 100644 --- a/lib/dash/list_segment_index_source.js +++ b/lib/dash/list_segment_index_source.js @@ -103,7 +103,8 @@ shaka.dash.ListSegmentIndexSource.prototype.create = function() { var timeline = []; if (segmentList.timeline) { timeline = shaka.dash.MpdUtils.createTimeline( - segmentList.timeline, segmentList.timescale || 1); + segmentList.timeline, segmentList.timescale || 1, + this.period_.duration || 0); } // Calculate a value to be used as an initial start value. diff --git a/lib/dash/mpd_parser.js b/lib/dash/mpd_parser.js index 246de2950..07aa1998d 100644 --- a/lib/dash/mpd_parser.js +++ b/lib/dash/mpd_parser.js @@ -1442,7 +1442,7 @@ shaka.dash.mpd.SegmentTimePoint.prototype.parse = function(parent, elem) { // Parse attributes. this.startTime = mpd.parseAttr_(elem, 't', mpd.parseNonNegativeInt_); this.duration = mpd.parseAttr_(elem, 'd', mpd.parseNonNegativeInt_); - this.repeat = mpd.parseAttr_(elem, 'r', mpd.parseNonNegativeInt_); + this.repeat = mpd.parseAttr_(elem, 'r', mpd.parseInt_); }; @@ -1775,6 +1775,18 @@ shaka.dash.mpd.parseRange_ = function(rangeString) { }; +/** + * Parses an integer. + * @param {string} intString The integer string. + * @return {?number} The parsed integer on success; otherwise, return null. + * @private + */ +shaka.dash.mpd.parseInt_ = function(intString) { + var result = window.parseInt(intString, 10); + return (!isNaN(result) ? result : null); +}; + + /** * Parses a positive integer. * @param {string} intString The integer string. diff --git a/lib/dash/mpd_utils.js b/lib/dash/mpd_utils.js index a4deeae3a..c52633812 100644 --- a/lib/dash/mpd_utils.js +++ b/lib/dash/mpd_utils.js @@ -223,12 +223,15 @@ shaka.dash.MpdUtils.fillUrlTemplate = function( * * @param {shaka.dash.mpd.SegmentTimeline} segmentTimeline * @param {number} timescale + * @param {number} durationSeconds The duration of the period (in seconds). * @return {!Array.<{start: number, end: number}>} */ -shaka.dash.MpdUtils.createTimeline = function(segmentTimeline, timescale) { +shaka.dash.MpdUtils.createTimeline = function( + segmentTimeline, timescale, durationSeconds) { shaka.asserts.assert(segmentTimeline); var lastEndTime = 0; + var duration = durationSeconds * timescale; var timePoints = segmentTimeline.timePoints; /** @type {!Array.<{start: number, end: number}>} */ @@ -247,36 +250,46 @@ shaka.dash.MpdUtils.createTimeline = function(segmentTimeline, timescale) { var startTime = tpStart != null ? tpStart : lastEndTime; var repeat = timePoints[i].repeat || 0; - for (var j = 0; j <= repeat; ++j) { - var endTime = startTime + timePoints[i].duration; + if (repeat < 0) { + var d = timePoints[i].duration; + if (i + 1 === timePoints.length) { + var delta = timePoints[0].startTime + duration - startTime; + repeat = Math.ceil(delta / d) - 1; + } else { + var next = timePoints[i + 1].startTime; + repeat = Math.ceil((next - startTime) / d) - 1; + } + } - // The end of the last segment may end before the start of the current - // segment (a gap) or may end after the start of the current segment (an - // overlap). If there is a gap/overlap then stretch/compress the end of - // the last segment to the start of the current segment. - // - // Note: it is possible to move the start of the current segment to the - // end of the last segment, but this would complicate the computation of - // the $Time$ placeholder. - if ((timeline.length > 0) && (startTime != lastEndTime)) { - var delta = startTime - lastEndTime; + // The end of the last segment may end before the start of the current + // segment (a gap) or may end after the start of the current segment (an + // overlap). If there is a gap/overlap then stretch/compress the end of + // the last segment to the start of the current segment. + // + // Note: it is possible to move the start of the current segment to the + // end of the last segment, but this would complicate the computation of + // the $Time$ placeholder. + if ((timeline.length > 0) && (startTime != lastEndTime)) { + var delta = startTime - lastEndTime; - if (Math.abs(delta / timescale) >= - shaka.dash.MpdUtils.GAP_OVERLAP_WARN_THRESHOLD) { - shaka.log.warning( - 'SegmentTimeline contains a large gap/overlap.', - 'The content may have errors in it.', - timePoints[i]); - } - - timeline[timeline.length - 1].end = startTime; + if (Math.abs(delta / timescale) >= + shaka.dash.MpdUtils.GAP_OVERLAP_WARN_THRESHOLD) { + shaka.log.warning( + 'SegmentTimeline contains a large gap/overlap.', + 'The content may have errors in it.', + timePoints[i]); } + timeline[timeline.length - 1].end = startTime; + } + + for (var j = 0; j <= repeat; ++j) { + var endTime = startTime + timePoints[i].duration; timeline.push({start: startTime, end: endTime}); startTime = endTime; lastEndTime = endTime; - } // for j + } } return timeline; diff --git a/lib/dash/timeline_segment_index_source.js b/lib/dash/timeline_segment_index_source.js index 017cd305a..daa6fa043 100644 --- a/lib/dash/timeline_segment_index_source.js +++ b/lib/dash/timeline_segment_index_source.js @@ -95,7 +95,8 @@ shaka.dash.TimelineSegmentIndexSource.prototype.create = function() { var segmentTemplate = this.representation_.segmentTemplate; var timeline = shaka.dash.MpdUtils.createTimeline( - segmentTemplate.timeline, segmentTemplate.timescale || 1); + segmentTemplate.timeline, segmentTemplate.timescale || 1, + this.period_.duration || 0); /** @type {!Array.} */ var references = []; diff --git a/spec/mpd_utils_spec.js b/spec/mpd_utils_spec.js index 525bb29aa..f185b0acd 100644 --- a/spec/mpd_utils_spec.js +++ b/spec/mpd_utils_spec.js @@ -167,5 +167,92 @@ describe('MpdUtils', function() { 1, 2, 3, 4)).toBeNull(); }); }); + + // TODO: Create more unit tests for this. + describe('createTimeline', function() { + it('supports negative repetitions', function() { + var timepoints = [ + createTimepoint(0, 10, 0), + createTimepoint(10, 10, -1), + createTimepoint(40, 10, 0) + ]; + var result = [ + { start: 0, end: 10 }, + { start: 10, end: 20 }, + { start: 20, end: 30 }, + { start: 30, end: 40 }, + { start: 40, end: 50 } + ]; + checkTimepoints(timepoints, result, 1, 0); + }); + + it('supports negative repetitions with uneven border', function() { + var timepoints = [ + createTimepoint(0, 10, 0), + createTimepoint(10, 10, -1), + createTimepoint(45, 5, 0) + ]; + var result = [ + { start: 0, end: 10 }, + { start: 10, end: 20 }, + { start: 20, end: 30 }, + { start: 30, end: 40 }, + { start: 40, end: 45 }, + { start: 45, end: 50 } + ]; + checkTimepoints(timepoints, result, 1, 0); + }); + + it('supports negative repetitions at end', function() { + var timepoints = [ + createTimepoint(0, 10, 0), + createTimepoint(10, 5, -1) + ]; + var result = [ + { start: 0, end: 10 }, + { start: 10, end: 15 }, + { start: 15, end: 20 }, + { start: 20, end: 25 } + ]; + checkTimepoints(timepoints, result, 1, 25); + }); + + /** + * Creates a new timepoint. + * + * @param {number} start + * @param {number} dur + * @param {number} rep + */ + function createTimepoint(start, dur, rep) { + var ret = new shaka.dash.mpd.SegmentTimePoint(); + ret.startTime = start; + ret.duration = dur; + ret.repeat = rep; + return ret; + } + + /** + * Checks that the createTimeline works with the given timepoints and the + * given expected results. + * + * @param {!Array.} points + * @param {!Array.<{start: number, end: number}} expected + * @param {number} scale + * @param {number} duration + */ + function checkTimepoints(points, expected, scale, duration) { + var timeline = new shaka.dash.mpd.SegmentTimeline(); + timeline.timePoints = points; + + var data = MpdUtils.createTimeline(timeline, scale, duration); + expect(data).toBeTruthy(); + expect(data.length).toBe(expected.length); + for (var i = 0; i < expected.length; i++) { + expect(data[i].start).toBe(expected[i].start); + expect(data[i].end).toBe(expected[i].end); + } + } + }); });