diff --git a/lib/media/segment_index.js b/lib/media/segment_index.js index ec9298b3501..761cecc0a53 100644 --- a/lib/media/segment_index.js +++ b/lib/media/segment_index.js @@ -436,10 +436,11 @@ shaka.media.SegmentIndex = class { * * @param {number} time * @param {boolean=} allowNonIndepedent + * @param {boolean=} reverse * @return {?shaka.media.SegmentIterator} * @export */ - getIteratorForTime(time, allowNonIndepedent = false) { + getIteratorForTime(time, allowNonIndepedent = false, reverse = false) { let index = this.find(time); if (index == null) { return null; @@ -476,7 +477,8 @@ shaka.media.SegmentIndex = class { } } } - return new shaka.media.SegmentIterator(this, index, partialSegmentIndex); + return new shaka.media.SegmentIterator( + this, index, partialSegmentIndex, reverse); } /** @@ -552,8 +554,9 @@ shaka.media.SegmentIterator = class { * @param {shaka.media.SegmentIndex} segmentIndex * @param {number} index * @param {number} partialSegmentIndex + * @param {boolean} reverse */ - constructor(segmentIndex, index, partialSegmentIndex) { + constructor(segmentIndex, index, partialSegmentIndex, reverse) { /** @private {shaka.media.SegmentIndex} */ this.segmentIndex_ = segmentIndex; @@ -562,6 +565,17 @@ shaka.media.SegmentIterator = class { /** @private {number} */ this.currentPartialPosition_ = partialSegmentIndex; + + /** @private {boolean} */ + this.reverse = reverse; + } + + /** + * @param {boolean} reverse + * @export + */ + setReverse(reverse) { + this.reverse = reverse; } /** @@ -606,25 +620,47 @@ shaka.media.SegmentIterator = class { next() { const ref = this.segmentIndex_.get(this.currentPosition_); - if (ref && ref.hasPartialSegments()) { - // If the regular segment contains partial segments, move to the next - // partial SegmentReference. - this.currentPartialPosition_++; - // If the current regular segment has been published completely, and - // we've reached the end of its partial segments list, move to the next - // regular segment. - // If the Partial Segments list is still on the fly, do not move to - // the next regular segment. - if (ref.hasAllPartialSegments() && - this.currentPartialPosition_ == ref.partialReferences.length) { + if (!this.reverse) { + if (ref && ref.hasPartialSegments()) { + // If the regular segment contains partial segments, move to the next + // partial SegmentReference. + this.currentPartialPosition_++; + // If the current regular segment has been published completely, and + // we've reached the end of its partial segments list, move to the next + // regular segment. + // If the Partial Segments list is still on the fly, do not move to + // the next regular segment. + if (ref.hasAllPartialSegments() && + this.currentPartialPosition_ == ref.partialReferences.length) { + this.currentPosition_++; + this.currentPartialPosition_ = 0; + } + } else { + // If the regular segment doesn't contain partial segments, move to the + // next regular segment. this.currentPosition_++; this.currentPartialPosition_ = 0; } } else { - // If the regular segment doesn't contain partial segments, move to the - // next regular segment. - this.currentPosition_++; - this.currentPartialPosition_ = 0; + if (ref && ref.hasPartialSegments()) { + // If the regular segment contains partial segments, move to the + // previous partial SegmentReference. + this.currentPartialPosition_--; + if (this.currentPartialPosition_ < 0) { + this.currentPosition_--; + const prevRef = this.segmentIndex_.get(this.currentPosition_); + if (prevRef && prevRef.hasPartialSegments()) { + this.currentPartialPosition_ = prevRef.partialReferences.length - 1; + } else { + this.currentPartialPosition_ = 0; + } + } + } else { + // If the regular segment doesn't contain partial segments, move to the + // previous regular segment. + this.currentPosition_--; + this.currentPartialPosition_ = 0; + } } const res = this.current(); diff --git a/test/media/segment_index_unit.js b/test/media/segment_index_unit.js index d6010c65f81..73e865b3d6e 100644 --- a/test/media/segment_index_unit.js +++ b/test/media/segment_index_unit.js @@ -742,6 +742,40 @@ describe('SegmentIndex', /** @suppress {accessControls} */ () => { expect(iterator.current()).toBe(null); }); + it('reverse iteration', () => { + const refs = inputRefs.slice(); + const index = new shaka.media.SegmentIndex(refs); + + // This simulates the pattern of calls in StreamingEngine when we buffer + // to the edge of a live stream. + const iterator = index[Symbol.iterator](); + iterator.next(); + expect(iterator.current()).toBe(inputRefs[0]); + + iterator.next(); + expect(iterator.current()).toBe(inputRefs[1]); + + iterator.next(); + expect(iterator.current()).toBe(inputRefs[2]); + + iterator.next(); + expect(iterator.current()).toBe(null); + + iterator.setReverse(true); + + iterator.next(); + expect(iterator.current()).toBe(inputRefs[2]); + + iterator.next(); + expect(iterator.current()).toBe(inputRefs[1]); + + iterator.next(); + expect(iterator.current()).toBe(inputRefs[0]); + + iterator.next(); + expect(iterator.current()).toBe(null); + }); + describe('getIteratorForTime', () => { it('begins with an independent partial segment', () => { // This test contains its own segment refs, which are manipulated to