diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 399a94d282..c9bd36674d 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -174,7 +174,6 @@ shaka.media.StreamingEngine = function( * stream: shakaExtern.Stream, * lastStream: ?shakaExtern.Stream, * lastSegmentReference: shaka.media.SegmentReference, - * drift: ?number, * needInitSegment: boolean, * needRebuffering: boolean, * needPeriodIndex: number, @@ -198,12 +197,6 @@ shaka.media.StreamingEngine = function( * The Stream of the last segment that was appended. * @property {shaka.media.SegmentReference} lastSegmentReference * The SegmentReference of the last segment that was appended. - * @property {?number} drift - * The number of seconds that the segments' timestamps are offset from the - * SegmentReferences' timestamps. For example, a positive value indicates - * that the segments are ahead of the SegmentReferences. Note that the - * segments' timestamps are the true values; however, the drift should - * never be very large for valid content. * @property {boolean} needInitSegment * True indicates that |stream|'s init segment must be inserted before the * next media segment is appended. @@ -555,7 +548,6 @@ shaka.media.StreamingEngine.prototype.initStreams_ = function(streamsByType) { type: type, lastStream: null, lastSegmentReference: null, - drift: null, needInitSegment: true, needRebuffering: false, needPeriodIndex: needPeriodIndex, @@ -794,14 +786,10 @@ shaka.media.StreamingEngine.prototype.update_ = function(mediaState) { var bufferEnd = this.mediaSourceEngine_.bufferEnd(mediaState.type); var timeNeeded = this.getTimeNeeded_( mediaState, playheadTime, bufferedAhead, bufferEnd); - if (timeNeeded == null) - return null; shaka.log.v2(logPrefix, 'timeNeeded=' + timeNeeded); - var timeline = this.manifest_.presentationTimeline; - // Check if we've buffered to the end of the presentation. - if (timeNeeded >= timeline.getDuration()) { + if (timeNeeded >= this.manifest_.presentationTimeline.getDuration()) { // We shouldn't rebuffer if the playhead is close to the end of the // presentation. shaka.log.debug(logPrefix, 'buffered to end of presentation'); @@ -838,27 +826,17 @@ shaka.media.StreamingEngine.prototype.update_ = function(mediaState) { return null; } - // Check segment availability. - var availabilityStart = timeline.getSegmentAvailabilityStart(); - var availabilityEnd = timeline.getSegmentAvailabilityEnd(); - if ((timeNeeded < availabilityStart) || (timeNeeded > availabilityEnd)) { - // The next segment is not available. In the usual case, this occurs when - // we've buffered to the live-edge of a live presentation; in the - // degenerate case, this occurs if the playhead is forced outside the - // segment availability window; either way try another update in a second. - shaka.log.v1(logPrefix, - 'next segment is outside segment availability window:', - 'playheadTime=' + playheadTime, - 'timeNeeded=' + timeNeeded, - 'availabilityStart=' + availabilityStart, - 'availabilityEnd=' + availabilityEnd); + var reference = this.getSegmentReferenceNeeded_( + mediaState, playheadTime, currentPeriodIndex); + if (!reference) { + // The segment could not be found, does not exist, or is not available. In + // any case just try again... if the manifest is incomplete or is not being + // updated then we'll idle forever; otherwise, we'll end up getting a + // SegmentReference eventually. return 1; } - var reference = this.getSegmentReference_( - mediaState, playheadTime, currentPeriodIndex); this.fetchAndAppend_(mediaState, playheadTime, currentPeriodIndex, reference); - return null; }; @@ -872,8 +850,7 @@ shaka.media.StreamingEngine.prototype.update_ = function(mediaState) { * @param {number} playheadTime * @param {number} bufferedAhead * @param {?number} bufferEnd - * @return {?number} The next timestamp needed or null if the playhead is is in - * an unbuffered region behind the buffer. + * @return {number} The next timestamp needed. * @throws {!shaka.util.Error} if the buffer is inconsistent with our * expectations. * @private @@ -886,8 +863,9 @@ shaka.media.StreamingEngine.prototype.getTimeNeeded_ = function( // to determine this and not the actual buffer for two reasons: // 1. actual segments end slightly before their advertised end times, so // the next timestamp we need is actually larger than |bufferEnd|; and - // 2. there may be drift, but we need drift free times when comparing times - // against presentation and Period boundaries. + // 2. there may be drift (the timestamps in the segments are ahead/behind + // of the timestamps in the manifest), but we need drift free times when + // comparing times against presentation and Period boundaries. if (bufferedAhead == 0) { // The playhead is in an unbuffered region. @@ -903,24 +881,29 @@ shaka.media.StreamingEngine.prototype.getTimeNeeded_ = function( } return playheadTime; } else if (bufferEnd > playheadTime) { - // The user agent seeked backwards but seeked() was not called or has not - // been called yet (because it's a race). Assume seeked() will be called. + // We may find ourseleves in this state for two reasons: + // 1. there is a significant amount of positive drift; or + // 2. the user agent seeked backwards but seeked() was not called or has + // not been called yet (because it's a race). + // For case 1 we'll idle forever, and for case 2 we'll end up buffering a + // segment, removing it, and then buffering it again (note that this case + // should be rare). shaka.log.debug(logPrefix, 'playhead in unbuffered region (behind buffer):', 'playheadTime=' + playheadTime, 'bufferEnd=' + bufferEnd); - return null; } else { - // We may find ourseleves in this state for three reasons: + // We may find ourseleves in this state for four reasons: // 1. the playhead is exactly at the end of the buffer; // 2. the browser allowed the playhead to proceed past the end of - // the buffer (either under normal or accelerated playback rates); or - // 3. the user agent seeked forwards but seeked() was not called or has + // the buffer (either under normal or accelerated playback rates); + // 3. there is a significant amount of negative drift; or + // 4. the user agent seeked forwards but seeked() was not called or has // not been called yet (because it's a race). // For cases 1 and 2 we'll end up buffering the next segment we want - // anyways, and for case 3 we'll end up buffering the next segment and - // then just removing it and buffering it again (note that this case - // should be rare). + // anyways, for case 3 we'll end up buffering behind the playhead until + // we catch up, and for case 4 we'll proceed as in case 2 of the previous + // block. shaka.log.debug(logPrefix, 'playhead in unbuffered region (ahead of buffer):', 'playheadTime=' + playheadTime, @@ -951,102 +934,142 @@ shaka.media.StreamingEngine.prototype.getTimeNeeded_ = function( * @param {shaka.media.StreamingEngine.MediaState_} mediaState * @param {number} playheadTime * @param {number} currentPeriodIndex - * @return {!shaka.media.SegmentReference} The SegmentReference of the - * next segment needed. - * @throws {!shaka.util.Error} If the next segment does not exist. + * @return {shaka.media.SegmentReference} The SegmentReference of the + * next segment needed, or null if a segment could not be found, does not + * exist, or is not available. * @private */ -shaka.media.StreamingEngine.prototype.getSegmentReference_ = function( +shaka.media.StreamingEngine.prototype.getSegmentReferenceNeeded_ = function( mediaState, playheadTime, currentPeriodIndex) { var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); + if (mediaState.lastSegmentReference && + mediaState.stream == mediaState.lastStream) { + // Something is buffered from the same Stream. + var position = mediaState.lastSegmentReference.position + 1; + shaka.log.v2(logPrefix, 'next position known:', 'position=' + position); + + return this.getSegmentReferenceIfAvailable_( + mediaState, currentPeriodIndex, position); + } + var position; - if (!mediaState.lastSegmentReference) { + + if (mediaState.lastSegmentReference) { + // Something is buffered from another Stream. + goog.asserts.assert(mediaState.lastStream, 'lastStream should not be null'); + shaka.log.v1(logPrefix, 'next position unknown: another Stream buffered'); + var lastPeriodIndex = + this.findPeriodContainingStream_(mediaState.lastStream); + var lastPeriod = this.manifest_.periods[lastPeriodIndex]; + position = this.lookupSegmentPosition_( + mediaState, + lastPeriod.startTime + mediaState.lastSegmentReference.endTime, + currentPeriodIndex); + } else { + // Nothing is buffered. goog.asserts.assert(!mediaState.lastStream, 'lastStream should be null'); shaka.log.v1(logPrefix, 'next position unknown: nothing buffered'); position = this.lookupSegmentPosition_( mediaState, playheadTime, currentPeriodIndex); - } else { - goog.asserts.assert(mediaState.lastStream, 'lastStream should not be null'); - if (mediaState.stream == mediaState.lastStream) { - // Something is buffered from the same Stream. - position = mediaState.lastSegmentReference.position + 1; - shaka.log.v2(logPrefix, 'using next position:', 'position=' + position); - } else { - // Something is buffered from another Stream. - shaka.log.v1(logPrefix, 'next position unknown: another Stream buffered'); - var lastPeriodIndex = - this.findPeriodContainingStream_(mediaState.lastStream); - var lastPeriod = this.manifest_.periods[lastPeriodIndex]; - position = this.lookupSegmentPosition_( - mediaState, - lastPeriod.startTime + mediaState.lastSegmentReference.endTime, - currentPeriodIndex); - } } - var reference = mediaState.stream.getSegmentReference(position); - if (!reference) { - shaka.log.error(logPrefix, - 'invalid segment index: SegmentReference does not exist:', - 'currentPeriodIndex=' + currentPeriodIndex, - 'position=' + position); - throw new shaka.util.Error( - shaka.util.Error.Category.STREAMING, - shaka.util.Error.Code.INVALID_SEGMENT_INDEX, - mediaState.type, - currentPeriodIndex, - position); - } + if (position == null) + return null; + // If there's positive drift then we need to get the previous segment; + // however, we don't actually know how much drift there is, so we must + // unconditionally get the previous segment. If it turns out that there's + // non-positive drift then we'll just end up buffering beind the playhead a + // little more than we needed. + var optimalPosition = Math.max(0, position - 1); + var reference = + this.getSegmentReferenceIfAvailable_( + mediaState, currentPeriodIndex, optimalPosition) || + this.getSegmentReferenceIfAvailable_( + mediaState, currentPeriodIndex, position); return reference; }; /** - * Looks up the position of the next segment needed. + * Looks up the position of the segment containing the given timestamp. * * @param {shaka.media.StreamingEngine.MediaState_} mediaState - * @param {number} time relative to presentation timeline + * @param {number} presentationTime The timestamp needed, relative to the + * start of the presentation. * @param {number} currentPeriodIndex - * @return {number} - * @throws {!shaka.util.Error} If the next segment does not exist. + * @return {?number} A segment position, or null if a segment was not be found. * @private */ shaka.media.StreamingEngine.prototype.lookupSegmentPosition_ = function( - mediaState, time, currentPeriodIndex) { + mediaState, presentationTime, currentPeriodIndex) { var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); var currentPeriod = this.manifest_.periods[currentPeriodIndex]; - shaka.log.v1(logPrefix, - 'looking up next position:', - 'time=' + time, - 'currentPeriod.startTime=' + currentPeriod.startTime, - 'mediaState.drift=' + mediaState.drift); + shaka.log.debug(logPrefix, + 'looking up segment:', + 'presentationTime=' + presentationTime, + 'currentPeriod.startTime=' + currentPeriod.startTime); - var lookupTime = time - currentPeriod.startTime - mediaState.drift; - lookupTime = Math.max(lookupTime, 0); + var lookupTime = Math.max(0, presentationTime - currentPeriod.startTime); var position = mediaState.stream.findSegmentPosition(lookupTime); if (position == null) { shaka.log.warning(logPrefix, - 'next segment does not exist:', - 'lookupTime=' + lookupTime, - 'time=' + time, + 'cannot find segment:', 'currentPeriod.startTime=' + currentPeriod.startTime, - 'mediaState.drift=' + mediaState.drift); - throw new shaka.util.Error( - shaka.util.Error.Category.STREAMING, - shaka.util.Error.Code.SEGMENT_DOES_NOT_EXIST, - mediaState.type, - currentPeriodIndex, - time); + 'lookupTime=' + lookupTime); } return position; }; +/** + * Gets the SegmentReference at the given position if it's available. + * + * @param {shaka.media.StreamingEngine.MediaState_} mediaState + * @param {number} currentPeriodIndex + * @param {number} position + * @return {shaka.media.SegmentReference} + * + * @private + */ +shaka.media.StreamingEngine.prototype.getSegmentReferenceIfAvailable_ = + function(mediaState, currentPeriodIndex, position) { + var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); + var currentPeriod = this.manifest_.periods[currentPeriodIndex]; + + var reference = mediaState.stream.getSegmentReference(position); + if (!reference) { + shaka.log.v1(logPrefix, + 'segment does not exist:', + 'currentPeriod.startTime=' + currentPeriod.startTime, + 'position=' + position); + return null; + } + + var timeline = this.manifest_.presentationTimeline; + var availabilityStart = timeline.getSegmentAvailabilityStart(); + var availabilityEnd = timeline.getSegmentAvailabilityEnd(); + + if ((currentPeriod.startTime + reference.endTime < availabilityStart) || + (currentPeriod.startTime + reference.startTime > availabilityEnd)) { + shaka.log.v2(logPrefix, + 'segment is not available:', + 'currentPeriod.startTime=' + currentPeriod.startTime, + 'reference.startTime=' + reference.startTime, + 'reference.endTime=' + reference.endTime, + 'availabilityStart=' + availabilityStart, + 'availabilityEnd=' + availabilityEnd); + return null; + } + + return reference; +}; + + /** * Fetches and appends the given segment; sets up the given MediaState's * associated SourceBuffer and evicts segments if either are required @@ -1067,7 +1090,6 @@ shaka.media.StreamingEngine.prototype.fetchAndAppend_ = function( 'fetchAndAppend_:', 'playheadTime=' + playheadTime, 'currentPeriod.startTime=' + currentPeriod.startTime, - 'mediaState.drift=' + mediaState.drift, 'reference.position=' + reference.position, 'reference.startTime=' + reference.startTime, 'reference.endTime=' + reference.endTime); @@ -1110,12 +1132,6 @@ shaka.media.StreamingEngine.prototype.fetchAndAppend_ = function( stream, reference, results[1]); - }.bind(this)).then(function() { - if (this.destroyed_) return; - return this.handleDrift_(mediaState, - playheadTime, - currentPeriodIndex, - reference); }.bind(this)).then(function() { mediaState.performingUpdate = false; @@ -1277,54 +1293,6 @@ shaka.media.StreamingEngine.prototype.evict_ = function( }; -/** - * Handles drift. - * - * @param {!shaka.media.StreamingEngine.MediaState_} mediaState - * @param {number} playheadTime - * @param {number} currentPeriodIndex - * @param {!shaka.media.SegmentReference} reference - * @return {!Promise} - * @private - */ -shaka.media.StreamingEngine.prototype.handleDrift_ = function( - mediaState, playheadTime, currentPeriodIndex, reference) { - if (mediaState.drift != null) - return Promise.resolve(); - - var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); - var currentPeriod = this.manifest_.periods[currentPeriodIndex]; - - var bufferStart = this.mediaSourceEngine_.bufferStart(mediaState.type); - if (bufferStart == null) { - // The segment did not contain any actual media content. - shaka.log.error(logPrefix, 'bad segment'); - return Promise.reject(new shaka.util.Error( - shaka.util.Error.Category.STREAMING, - shaka.util.Error.Code.BAD_SEGMENT, - mediaState.type)); - } - - mediaState.drift = - bufferStart - reference.startTime - currentPeriod.startTime; - shaka.log.debug(logPrefix, 'drift=', mediaState.drift); - - // If there is positive drift or large negative drift then the playhead - // may not be within the segment we just appended. - var bufferedAhead = this.mediaSourceEngine_.bufferedAheadOf( - mediaState.type, playheadTime); - if (bufferedAhead == 0) { - // Clear the buffer and try again. - shaka.log.debug(logPrefix, - 'playhead outside first segment:', - 'bufferStart=' + bufferStart); - mediaState.waitingToClearBuffer = true; - } - - return Promise.resolve(); -}; - - /** * Sets up all known Periods when startup completes; otherwise, does nothing. * @@ -1346,17 +1314,8 @@ shaka.media.StreamingEngine.prototype.handleStartup_ = function( var mediaStates = MapUtils.values(this.mediaStates_); this.startupComplete_ = mediaStates.every(function(ms) { // Startup completes once we have buffered at least one segment from each - // MediaState, have handled positive or large negative drift, and are not - // clearing the buffer. Hence, the following three cases: - // 1. if |drift| is null then we never appended anything; - // 2. if |drift| is non-null but we're clearing the buffer then either - // there was positive or large negative drift, or the user agent - // seeked; and - // 3. if |drift| is non-null and we're not clearing the buffer but the - // buffer is empty then there was positive or large negative - // drift but we never recovered. - return ms.drift != null && - !ms.waitingToClearBuffer && + // MediaState. + return !ms.waitingToClearBuffer && !ms.clearingBuffer && ms.lastSegmentReference; }); diff --git a/lib/util/error.js b/lib/util/error.js index 580a7cb0c7..f7f376d7d6 100644 --- a/lib/util/error.js +++ b/lib/util/error.js @@ -347,7 +347,6 @@ shaka.util.Error.Code = { */ 'UNPLAYABLE_PERIOD': 4011, - /** * The StreamingEngine appended a segment but the SourceBuffer is empty, or * the StreamingEngine removed all segments and the SourceBuffer is @@ -359,49 +358,6 @@ shaka.util.Error.Code = { */ 'INCONSISTENT_BUFFER_STATE': 5000, - /** - * The StreamingEngine cannot append the next segment because the segment's - * corresponding SegmentReference does not exist (i.e., findSegmentPosition() - * succeeded but getSegmentReference() failed) or the segment's corresponding - * SegmentReference has an invalid time range. - * - * This is a non-recoverable error. - * - *
error.data[0] is the type of content which caused the error. - *
error.data[1] is the index of the Period. - *
error.data[2] is the position of the segment. - */ - 'INVALID_SEGMENT_INDEX': 5001, - - /** - * The StreamingEngine cannot append the next segment because the next - * segment does not exist (i.e., findSegmentPosition() failed). This can - * occur for three reasons: - * 1. there is positive drift (the segments' timestamps are ahead of - * the manifest's timestamps) and the playhead is outside the drifted - * segment availability window. - * 2. the manifest is not updating fast enough for live presentations; or - * 3. the manifest is not complete. - * The first case is a recoverable error; recovery may be attempted by - * repositioning the playhead under a segment. - * - *
error.data[0] is the type of content which caused the error. - *
error.data[1] is the index of the Period. - *
error.data[2] is the timestamp needed. - */ - 'SEGMENT_DOES_NOT_EXIST': 5002, - - /** - * The StreamingEngine inserted a media segment, but the segment did not - * contain any actual media content. - * - * This is likely a non-recoverable error; however, recovery may be attempted - * by seeking forwards or backwards (e.g., nudging the playhead). - * - *
error.data[0] is the type of content which caused the error. - */ - 'BAD_SEGMENT': 5004, - /** * The StreamingEngine called onChooseStreams() but the callback receiver * did not return the correct number or type of Streams. diff --git a/test/streaming_engine_integration.js b/test/streaming_engine_integration.js index f1a89e8507..076891c10b 100644 --- a/test/streaming_engine_integration.js +++ b/test/streaming_engine_integration.js @@ -440,9 +440,10 @@ describe('StreamingEngine', function() { onStartupComplete.and.callFake(function() { // firstSegmentNumber = // [(segmentAvailabilityEnd - rebufferingGoal) / segmentDuration] + 1 + // Then -1 to account for drift safe buffering. var segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - netEngine.expectRequest('1_video_24', segmentType); - netEngine.expectRequest('1_audio_29', segmentType); + netEngine.expectRequest('1_video_23', segmentType); + netEngine.expectRequest('1_audio_28', segmentType); video.play(); }); diff --git a/test/streaming_engine_unit.js b/test/streaming_engine_unit.js index 3bcae98618..5e9b8c3cd9 100644 --- a/test/streaming_engine_unit.js +++ b/test/streaming_engine_unit.js @@ -531,15 +531,15 @@ describe('StreamingEngine', function() { text: [] }); - // Since we started playback from segment 11, the first 10 segments - // should not be buffered. - for (var i = 0; i <= 9; ++i) { + // Since we started playback from segment 11, segments 10 through 14 + // should be buffered. + for (var i = 0; i <= 8; ++i) { expect(mediaSourceEngine.segments.audio[i]).toBeFalsy(); expect(mediaSourceEngine.segments.video[i]).toBeFalsy(); expect(mediaSourceEngine.segments.text[i]).toBeFalsy(); } - for (var i = 10; i <= 13; ++i) { + for (var i = 9; i <= 13; ++i) { expect(mediaSourceEngine.segments.audio[i]).toBeTruthy(); expect(mediaSourceEngine.segments.video[i]).toBeTruthy(); expect(mediaSourceEngine.segments.text[i]).toBeTruthy(); @@ -866,9 +866,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, true, false, false], - video: [false, true, false, false], - text: [false, true, false, false] + audio: [true, true, false, false], + video: [true, true, false, false], + text: [true, true, false, false] }); onChooseStreams.and.throwError(new Error()); @@ -891,9 +891,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, true, true, true], - video: [false, true, true, true], - text: [false, true, true, true] + audio: [true, true, true, true], + video: [true, true, true, true], + text: [true, true, true, true] }); return streamingEngine.destroy(); @@ -955,9 +955,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, true, false, false], - video: [false, true, false, false], - text: [false, true, false, false] + audio: [true, true, false, false], + video: [true, true, false, false], + text: [true, true, false, false] }); onChooseStreams.and.throwError(new Error()); @@ -985,9 +985,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, true, true, true], - video: [false, true, true, true], - text: [false, true, true, true] + audio: [true, true, true, true], + video: [true, true, true, true], + text: [true, true, true, true] }); return streamingEngine.destroy(); @@ -1024,9 +1024,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, true, false, false], - video: [false, true, false, false], - text: [false, true, false, false] + audio: [true, true, false, false], + video: [true, true, false, false], + text: [true, true, false, false] }); onChooseStreams.and.throwError(new Error()); @@ -1053,9 +1053,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, true, true, true], - video: [false, true, true, true], - text: [false, true, true, true] + audio: [true, true, true, true], + video: [true, true, true, true], + text: [true, true, true, true] }); return streamingEngine.destroy(); @@ -1100,9 +1100,9 @@ describe('StreamingEngine', function() { text: [] }); expect(mediaSourceEngine.segments).toEqual({ - audio: [false, false, false, true], - video: [false, false, false, true], - text: [false, false, false, true] + audio: [false, false, true, true], + video: [false, false, true, true], + text: [false, false, true, true] }); return streamingEngine.destroy(); @@ -1171,7 +1171,10 @@ describe('StreamingEngine', function() { }); it('outside segment availability window', function(done) { - playhead.getTime.and.returnValue(100); + timeline.segmentAvailabilityStart = 90; + timeline.segmentAvailabilityEnd = 110; + + playhead.getTime.and.returnValue(90); onChooseStreams.and.callFake(function(period) { expect(period).toBe(manifest.periods[0]); @@ -1183,16 +1186,15 @@ describe('StreamingEngine', function() { }); onStartupComplete.and.callFake(function() { - setupFakeGetTime(100); + setupFakeGetTime(90); // Seek forward to an unbuffered and unavailable region in the second - // Period (note: |availabilityEnd| defaults to 120). - // Set playing to false since the playhead can't move at the seek - // target. - expect(playhead.getTime()).toBe(100); - expect(timeline.getSegmentAvailabilityStart()).toBe(100); - expect(timeline.getSegmentAvailabilityEnd()).toBe(120); - playheadTime += 25; + // Period; set playing to false since the playhead can't move at the + // seek target. + expect(playhead.getTime()).toBe(90); + expect(timeline.getSegmentAvailabilityStart()).toBe(90); + expect(timeline.getSegmentAvailabilityEnd()).toBe(110); + playheadTime += 35; playing = false; streamingEngine.seeked(); @@ -1205,15 +1207,16 @@ describe('StreamingEngine', function() { return defaultOnChooseStreams(period); }); - // Eventually StreamingEngine should request the first segment of the - // second Period when it becomes available. + // Eventually StreamingEngine should request the first segment (since + // it needs the second segment) of the second Period when it becomes + // available. var originalAppendBuffer = shaka.test.FakeMediaSourceEngine.prototype.appendBuffer; mediaSourceEngine.appendBuffer.and.callFake( function(type, data, startTime, endTime) { expect(playhead.getTime()).toBe(125); - expect(timeline.getSegmentAvailabilityStart()).toBe(105); - expect(timeline.getSegmentAvailabilityEnd()).toBe(125); + expect(timeline.getSegmentAvailabilityStart()).toBe(100); + expect(timeline.getSegmentAvailabilityEnd()).toBe(120); playing = true; var p = originalAppendBuffer.call( mediaSourceEngine, type, data, startTime, endTime); @@ -1475,7 +1478,6 @@ describe('StreamingEngine', function() { }); }); - // TODO: Add tests for eviction with drift. describe('VOD drift', function() { beforeEach(function() { setupVod(); @@ -1587,38 +1589,23 @@ describe('StreamingEngine', function() { expect(mediaSourceEngine.endOfStream).toHaveBeenCalled(); // Verify buffers. - // Note: after appending segment 11 of the first Period, - // StreamingEngine should clear the buffer and then append segment 12 - // of the first Period as the playhead is under that Segment when - // accounting for drift. When transitioning into the second Period, - // StreamingEngine should append the second segment of the second - // Period. It should not append the first segment of the second Period - // since that segment exists entirely outside the second Period's - // boundaries. expect(mediaSourceEngine.initSegments).toEqual({ audio: [false, true], video: [false, true], text: [] }); - for (var i = 0; i <= 10; ++i) { + for (var i = 0; i <= 8; ++i) { expect(mediaSourceEngine.segments.audio[i]).toBeFalsy(); expect(mediaSourceEngine.segments.video[i]).toBeFalsy(); expect(mediaSourceEngine.segments.text[i]).toBeFalsy(); } - // Second segment of first Period. - expect(mediaSourceEngine.segments.audio[11]).toBeTruthy(); - expect(mediaSourceEngine.segments.video[11]).toBeTruthy(); - expect(mediaSourceEngine.segments.text[11]).toBeTruthy(); - - expect(mediaSourceEngine.segments.audio[12]).toBeFalsy(); - expect(mediaSourceEngine.segments.video[12]).toBeFalsy(); - expect(mediaSourceEngine.segments.text[12]).toBeFalsy(); - - expect(mediaSourceEngine.segments.audio[13]).toBeTruthy(); - expect(mediaSourceEngine.segments.video[13]).toBeTruthy(); - expect(mediaSourceEngine.segments.text[13]).toBeTruthy(); + for (var i = 9; i <= 13; ++i) { + expect(mediaSourceEngine.segments.audio[i]).toBeTruthy(); + expect(mediaSourceEngine.segments.video[i]).toBeTruthy(); + expect(mediaSourceEngine.segments.text[i]).toBeTruthy(); + } return streamingEngine.destroy(); }).catch(fail).then(done);