Browse Source

buffer at the current time range end instead of incrementing a variable. closes #423

pull/6/head
jrivera 10 years ago
committed by David LaPalomento
parent
commit
bce88a1883
  1. 2
      CHANGELOG.md
  2. 2
      package.json
  3. 118
      src/playlist-loader.js
  4. 156
      src/videojs-hls.js
  5. 10
      test/playlist-loader_test.js
  6. 28
      test/videojs-hls_test.js

2
CHANGELOG.md

@ -2,7 +2,7 @@ CHANGELOG
========= =========
## HEAD (Unreleased) ## HEAD (Unreleased)
_(none)_
* buffer at the current time range end instead of incrementing a variable. ([view](https://github.com/videojs/videojs-contrib-hls/pull/423))
-------------------- --------------------

2
package.json

@ -44,7 +44,7 @@
"karma-sauce-launcher": "~0.1.8", "karma-sauce-launcher": "~0.1.8",
"qunitjs": "^1.18.0", "qunitjs": "^1.18.0",
"sinon": "1.10.2", "sinon": "1.10.2",
"video.js": "^5.0.0-rc.96"
"video.js": "^5.1.0"
}, },
"dependencies": { "dependencies": {
"pkcs7": "^0.2.2", "pkcs7": "^0.2.2",

118
src/playlist-loader.js

@ -367,7 +367,17 @@
* closest playback position that is currently available. * closest playback position that is currently available.
*/ */
PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) { PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) {
var i, j, segment, targetDuration;
var
i,
segment,
originalTime = time,
targetDuration = this.media_.targetDuration || 10,
numSegments = this.media_.segments.length,
lastSegment = numSegments - 1,
startIndex,
endIndex,
knownStart,
knownEnd;
if (!this.media_) { if (!this.media_) {
return 0; return 0;
@ -379,57 +389,105 @@
return 0; return 0;
} }
// 1) Walk backward until we find the latest segment with timeline
// 1) Walk backward until we find the first segment with timeline
// information that is earlier than `time` // information that is earlier than `time`
targetDuration = this.media_.targetDuration || 10;
i = this.media_.segments.length;
while (i--) {
for (i = lastSegment; i >= 0; i--) {
segment = this.media_.segments[i]; segment = this.media_.segments[i];
if (segment.end !== undefined && segment.end <= time) { if (segment.end !== undefined && segment.end <= time) {
time -= segment.end;
startIndex = i + 1;
knownStart = segment.end;
if (startIndex >= numSegments) {
// The last segment claims to end *before* the time we are
// searching for so just return it
return numSegments;
}
break; break;
} }
if (segment.start !== undefined && segment.start < time) {
if (segment.start !== undefined && segment.start <= time) {
if (segment.end !== undefined && segment.end > time) { if (segment.end !== undefined && segment.end > time) {
// we've found the target segment exactly // we've found the target segment exactly
return i; return i;
} }
startIndex = i;
knownStart = segment.start;
break;
}
}
// 2) Walk forward until we find the first segment with timeline
// information that is greater than `time`
for (i = 0; i < numSegments; i++) {
segment = this.media_.segments[i];
if (segment.start !== undefined && segment.start > time) {
endIndex = i - 1;
knownEnd = segment.start;
if (endIndex < 0) {
// The first segment claims to start *after* the time we are
// searching for so just return it
return -1;
}
break;
}
if (segment.end !== undefined && segment.end > time) {
endIndex = i;
knownEnd = segment.end;
break;
}
}
time -= segment.start;
if (startIndex !== undefined) {
// We have a known-start point that is before our desired time so
// walk from that point forwards
time = time - knownStart;
for (i = startIndex; i < (endIndex || numSegments); i++) {
segment = this.media_.segments[i];
time -= segment.duration || targetDuration; time -= segment.duration || targetDuration;
if (time < 0) { if (time < 0) {
// the segment with start information is also our best guess
// for the momment
return i; return i;
} }
break;
} }
if (i === endIndex) {
// We haven't found a segment but we did hit a known end point
// so fallback to "Algorithm Jon" - try to interpolate the segment
// index based on the known span of the timeline we are dealing with
// and the number of segments inside that span
return startIndex + Math.floor(
((originalTime - knownStart) / (knownEnd - knownStart)) *
(endIndex - startIndex));
} }
i++;
// 2) Walk forward, testing each segment to see if `time` falls within it
for (j = i; j < this.media_.segments.length; j++) {
segment = this.media_.segments[j];
// We _still_ haven't found a segment so load the last one
return lastSegment;
} else if (endIndex !== undefined) {
// We _only_ have a known-end point that is after our desired time so
// walk from that point backwards
time = knownEnd - time;
for (i = endIndex; i >= 0; i--) {
segment = this.media_.segments[i];
time -= segment.duration || targetDuration; time -= segment.duration || targetDuration;
if (time < 0) { if (time < 0) {
return j;
return i;
} }
// 2a) If we discover a segment that has timeline information
// before finding the result segment, the playlist information
// must have been inaccurate. Start a binary search for the
// segment which contains `time`. If the guess turns out to be
// incorrect, we'll have more info to work with next time.
if (segment.start !== undefined || segment.end !== undefined) {
return Math.floor((j - i) * 0.5);
}
// We haven't found a segment so load the first one
return 0;
} else {
// We known nothing so use "Algorithm A" - walk from the front
// of the playlist naively subtracking durations until we find
// a segment that contains time and return it
for (i = 0; i < numSegments; i++) {
segment = this.media_.segments[i];
time -= segment.duration || targetDuration;
if (time < 0) {
return i;
} }
} }
// the playback position is outside the range of available
// segments so return the length
return this.media_.segments.length;
// We are out of possible candidates so load the last one...
// The last one is the least likely to overlap a buffer and therefore
// the one most likely to tell us something about the timeline
return lastSegment;
}
}; };
videojs.Hls.PlaylistLoader = PlaylistLoader; videojs.Hls.PlaylistLoader = PlaylistLoader;

156
src/videojs-hls.js

@ -304,13 +304,26 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
// transition the sourcebuffer to the ended state if we've hit the end of // transition the sourcebuffer to the ended state if we've hit the end of
// the playlist // the playlist
this.sourceBuffer.addEventListener('updateend', function() { this.sourceBuffer.addEventListener('updateend', function() {
var segmentInfo = this.pendingSegment_, segment, currentBuffered, timelineUpdates;
var
segmentInfo = this.pendingSegment_,
segment,
playlist,
currentMediaIndex,
currentBuffered,
timelineUpdates;
// stop here if the update errored or was aborted
if (!segmentInfo) {
return;
}
this.pendingSegment_ = null; this.pendingSegment_ = null;
// if we've buffered to the end of the video, let the MediaSource know // if we've buffered to the end of the video, let the MediaSource know
currentBuffered = this.findCurrentBuffered_(); currentBuffered = this.findCurrentBuffered_();
if (currentBuffered.length && this.duration() === currentBuffered.end(0)) {
if (currentBuffered.length &&
this.duration() === currentBuffered.end(0) &&
this.mediaSource.readyState === 'open') {
this.mediaSource.endOfStream(); this.mediaSource.endOfStream();
} }
@ -319,26 +332,46 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
return; return;
} }
// if we switched renditions don't try to add segment timeline
// information to the playlist
if (segmentInfo.playlist.uri !== this.playlists.media().uri) {
return this.fillBuffer();
}
playlist = this.playlists.media();
currentMediaIndex = segmentInfo.mediaIndex + (segmentInfo.mediaSequence - playlist.mediaSequence);
// annotate the segment with any start and end time information // annotate the segment with any start and end time information
// added by the media processing // added by the media processing
segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
segment = playlist.segments[currentMediaIndex];
timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered, timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered,
this.tech_.buffered()); this.tech_.buffered());
timelineUpdates.forEach(function(update) {
timelineUpdates.forEach(function (update) {
if (segment) {
if (update.start !== undefined) { if (update.start !== undefined) {
segment.start = update.start; segment.start = update.start;
} }
if (update.end !== undefined) { if (update.end !== undefined) {
segment.end = update.end; segment.end = update.end;
} }
}
}); });
if (timelineUpdates.length) { if (timelineUpdates.length) {
this.updateDuration(segmentInfo.playlist);
this.updateDuration(playlist);
// check if it's time to download the next segment
this.fillBuffer();
return;
} }
// check if it's time to download the next segment
this.checkBuffer_();
// the last segment append must have been entirely in the
// already buffered time ranges. just buffer forward until we
// find a segment that adds to the buffered time ranges and
// improves subsequent media index calculations.
this.fillBuffer(currentMediaIndex + 1);
return;
}.bind(this)); }.bind(this));
}; };
@ -426,11 +459,8 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
this.cancelKeyXhr(); this.cancelKeyXhr();
} }
// clear out the segment being processed
this.pendingSegment_ = null;
// begin filling the buffer at the new position // begin filling the buffer at the new position
this.fillBuffer(currentTime);
this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime));
}; };
videojs.Hls.prototype.duration = function() { videojs.Hls.prototype.duration = function() {
@ -465,15 +495,25 @@ videojs.Hls.prototype.updateDuration = function(playlist) {
this.mediaSource.duration = newDuration; this.mediaSource.duration = newDuration;
this.tech_.trigger('durationchange'); this.tech_.trigger('durationchange');
this.mediaSource.removeEventListener('sourceopen', setDuration); this.mediaSource.removeEventListener('sourceopen', setDuration);
}.bind(this);
}.bind(this),
seekable = this.seekable();
// TODO: Move to videojs-contrib-media-sources
if (seekable.length && newDuration === Infinity) {
if (isNaN(oldDuration)) {
oldDuration = 0;
}
newDuration = Math.max(oldDuration,
seekable.end(0) + playlist.targetDuration * 3);
}
// if the duration has changed, invalidate the cached value // if the duration has changed, invalidate the cached value
if (oldDuration !== newDuration) { if (oldDuration !== newDuration) {
if (this.mediaSource.readyState === 'open') {
if (this.mediaSource.readyState !== 'open') {
this.mediaSource.addEventListener('sourceopen', setDuration);
} else if (!this.sourceBuffer || !this.sourceBuffer.updating) {
this.mediaSource.duration = newDuration; this.mediaSource.duration = newDuration;
this.tech_.trigger('durationchange'); this.tech_.trigger('durationchange');
} else {
this.mediaSource.addEventListener('sourceopen', setDuration);
} }
} }
}; };
@ -507,6 +547,8 @@ videojs.Hls.prototype.cancelSegmentXhr = function() {
this.segmentXhr_.abort(); this.segmentXhr_.abort();
this.segmentXhr_ = null; this.segmentXhr_ = null;
} }
// clear out the segment being processed
this.pendingSegment_ = null;
}; };
/** /**
@ -667,11 +709,17 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() {
*/ */
videojs.Hls.prototype.findCurrentBuffered_ = function() { videojs.Hls.prototype.findCurrentBuffered_ = function() {
var var
tech = this.tech_,
currentTime = tech.currentTime(),
buffered = this.tech_.buffered(),
ranges, ranges,
i;
i,
tech = this.tech_,
// !!The order of the next two lines is important!!
// `currentTime` must be equal-to or greater-than the start of the
// buffered range. Flash executes out-of-process so, every value can
// change behind the scenes from line-to-line. By reading `currentTime`
// after `buffered`, we ensure that it is always a current or later
// value during playback.
buffered = tech.buffered(),
currentTime = tech.currentTime();
if (buffered && buffered.length) { if (buffered && buffered.length) {
// Search for a range containing the play-head // Search for a range containing the play-head
@ -697,13 +745,13 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() {
* @param seekToTime (optional) {number} the offset into the downloaded segment * @param seekToTime (optional) {number} the offset into the downloaded segment
* to seek to, in seconds * to seek to, in seconds
*/ */
videojs.Hls.prototype.fillBuffer = function(seekToTime) {
videojs.Hls.prototype.fillBuffer = function(mediaIndex) {
var var
tech = this.tech_, tech = this.tech_,
currentTime = tech.currentTime(), currentTime = tech.currentTime(),
currentBuffered = this.findCurrentBuffered_(), currentBuffered = this.findCurrentBuffered_(),
currentBufferedEnd = 0,
bufferedTime = 0, bufferedTime = 0,
mediaIndex = 0,
segment, segment,
segmentInfo; segmentInfo;
@ -739,39 +787,46 @@ videojs.Hls.prototype.fillBuffer = function(seekToTime) {
return; return;
} }
// find the next segment to download
if (typeof seekToTime === 'number') {
mediaIndex = this.playlists.getMediaIndexForTime_(seekToTime);
} else if (currentBuffered && currentBuffered.length) {
mediaIndex = this.playlists.getMediaIndexForTime_(currentBuffered.end(0));
bufferedTime = Math.max(0, currentBuffered.end(0) - currentTime);
if (mediaIndex === undefined) {
if (currentBuffered && currentBuffered.length) {
currentBufferedEnd = currentBuffered.end(0);
mediaIndex = this.playlists.getMediaIndexForTime_(currentBufferedEnd);
bufferedTime = Math.max(0, currentBufferedEnd - currentTime);
// if there is plenty of content in the buffer and we're not
// seeking, relax for awhile
if (bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) {
return;
}
} else { } else {
mediaIndex = this.playlists.getMediaIndexForTime_(this.tech_.currentTime()); mediaIndex = this.playlists.getMediaIndexForTime_(this.tech_.currentTime());
} }
}
segment = this.playlists.media().segments[mediaIndex]; segment = this.playlists.media().segments[mediaIndex];
// if the video has finished downloading, stop trying to buffer
// if the video has finished downloading
if (!segment) { if (!segment) {
return; return;
} }
// if there is plenty of content in the buffer and we're not
// seeking, relax for awhile
if (typeof seekToTime !== 'number' &&
bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) {
return;
// we have entered a state where we are fetching the same segment,
// try to walk forward
if (this.lastSegmentLoaded_ &&
this.lastSegmentLoaded_ === this.playlistUriToUrl(segment.uri)) {
return this.fillBuffer(mediaIndex + 1);
} }
// package up all the work to append the segment // package up all the work to append the segment
segmentInfo = { segmentInfo = {
// resolve the segment URL relative to the playlist // resolve the segment URL relative to the playlist
uri: this.playlistUriToUrl(segment.uri), uri: this.playlistUriToUrl(segment.uri),
// the segment's mediaIndex at the time it was received
// the segment's mediaIndex & mediaSequence at the time it was requested
mediaIndex: mediaIndex, mediaIndex: mediaIndex,
mediaSequence: this.playlists.media().mediaSequence,
// the segment's playlist // the segment's playlist
playlist: this.playlists.media(), playlist: this.playlists.media(),
// optionally, a time offset to seek to within the segment
offset: seekToTime,
// The state of the buffer when this segment was requested
currentBufferedEnd: currentBufferedEnd,
// unencrypted bytes of the segment // unencrypted bytes of the segment
bytes: null, bytes: null,
// when a key is defined for this segment, the encrypted bytes // when a key is defined for this segment, the encrypted bytes
@ -856,6 +911,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) {
return; return;
} }
self.lastSegmentLoaded_ = segmentInfo.uri;
self.setBandwidth(request); self.setBandwidth(request);
if (segment.key) { if (segment.key) {
@ -944,41 +1000,33 @@ videojs.Hls.prototype.drainBuffer = function(event) {
event = event || {}; event = event || {};
if (segmentInfo.mediaIndex > 0) {
segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
playlist.mediaSequence + segmentInfo.mediaIndex);
}
// If we have seeked into a non-buffered time-range, remove all buffered // If we have seeked into a non-buffered time-range, remove all buffered
// time-ranges because they could have been incorrectly placed originally // time-ranges because they could have been incorrectly placed originally
if (this.tech_.seeking() && outsideBufferedRanges) { if (this.tech_.seeking() && outsideBufferedRanges) {
if (hasBufferedContent) {
// In Chrome, it seems that too many independent buffered time-ranges can
// cause playback to fail to resume when seeking so just kill all of them
this.sourceBuffer.remove(0, Infinity);
return;
}
// If there are discontinuities in the playlist, we can't be sure of anything // If there are discontinuities in the playlist, we can't be sure of anything
// related to time so we reset the timestamp offset and start appending data // related to time so we reset the timestamp offset and start appending data
// anew on every seek // anew on every seek
if (segmentInfo.playlist.discontinuityStarts.length) { if (segmentInfo.playlist.discontinuityStarts.length) {
if (segmentInfo.mediaIndex > 0) {
segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, segmentInfo.mediaIndex);
}
// Now that the forward buffer is clear, we have to set timestamp offset to // Now that the forward buffer is clear, we have to set timestamp offset to
// the start of the buffered region // the start of the buffered region
this.sourceBuffer.timestampOffset = segmentTimestampOffset; this.sourceBuffer.timestampOffset = segmentTimestampOffset;
} }
} else if (segment.discontinuity) {
} else if (segment.discontinuity && currentBuffered.length) {
// If we aren't seeking and are crossing a discontinuity, we should set // If we aren't seeking and are crossing a discontinuity, we should set
// timestampOffset for new segments to be appended the end of the current // timestampOffset for new segments to be appended the end of the current
// buffered time-range // buffered time-range
this.sourceBuffer.timestampOffset = currentBuffered.end(0); this.sourceBuffer.timestampOffset = currentBuffered.end(0);
} else if (!hasBufferedContent && this.tech_.currentTime() > 0.05) {
// If we are trying to play at a position that is not zero but we aren't
// currently seeking according to the video element
this.sourceBuffer.timestampOffset = segmentTimestampOffset;
} }
if (currentBuffered.length) {
// Chrome 45 stalls if appends overlap the playhead
this.sourceBuffer.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0));
} else {
this.sourceBuffer.appendWindowStart = 0;
}
this.pendingSegment_.buffered = this.tech_.buffered(); this.pendingSegment_.buffered = this.tech_.buffered();
// the segment is asynchronously added to the current buffered data // the segment is asynchronously added to the current buffered data

10
test/playlist-loader_test.js

@ -653,8 +653,8 @@
equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero'); equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero');
equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2'); equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2');
equal(loader.getMediaIndexForTime_(22), equal(loader.getMediaIndexForTime_(22),
3,
'time greater than the length is index 3');
2,
'time greater than the length is index 2');
}); });
test('returns the lower index when calculating for a segment boundary', function() { test('returns the lower index when calculating for a segment boundary', function() {
@ -683,9 +683,9 @@
'1002.ts\n'); '1002.ts\n');
loader.media().segments[0].start = 150; loader.media().segments[0].start = 150;
equal(loader.getMediaIndexForTime_(0), 0, 'the lowest returned value is zero');
equal(loader.getMediaIndexForTime_(45), 0, 'expired content returns zero');
equal(loader.getMediaIndexForTime_(75), 0, 'expired content returns zero');
equal(loader.getMediaIndexForTime_(0), -1, 'the lowest returned value is negative one');
equal(loader.getMediaIndexForTime_(45), -1, 'expired content returns negative one');
equal(loader.getMediaIndexForTime_(75), -1, 'expired content returns negative one');
equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position'); equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position');
equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment'); equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment'); equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');

28
test/videojs-hls_test.js

@ -218,6 +218,7 @@ module('HLS', {
var el = document.createElement('div'); var el = document.createElement('div');
el.id = 'vjs_mock_flash_' + nextId++; el.id = 'vjs_mock_flash_' + nextId++;
el.className = 'vjs-tech vjs-mock-flash'; el.className = 'vjs-tech vjs-mock-flash';
el.duration = Infinity;
el.vjs_load = function() {}; el.vjs_load = function() {};
el.vjs_getProperty = function(attr) { el.vjs_getProperty = function(attr) {
if (attr === 'buffered') { if (attr === 'buffered') {
@ -1131,7 +1132,7 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
return videojs.createTimeRange(buffered); return videojs.createTimeRange(buffered);
}; };
currentTime = 8; currentTime = 8;
buffered = [[0, 10], [20, 40]];
buffered = [[0, 10], [20, 30]];
standardXHRResponse(requests[0]); standardXHRResponse(requests[0]);
standardXHRResponse(requests[1]); standardXHRResponse(requests[1]);
@ -1144,14 +1145,9 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
currentTime = 22; currentTime = 22;
player.tech_.hls.sourceBuffer.trigger('updateend'); player.tech_.hls.sourceBuffer.trigger('updateend');
player.tech_.hls.checkBuffer_(); player.tech_.hls.checkBuffer_();
strictEqual(requests.length, 2, 'made no additional requests');
buffered = [[0, 10], [20, 30]];
player.tech_.hls.checkBuffer_();
standardXHRResponse(requests[2]);
strictEqual(requests.length, 3, 'made three requests'); strictEqual(requests.length, 3, 'made three requests');
strictEqual(requests[2].url, strictEqual(requests[2].url,
absoluteUrl('manifest/media-00004.ts'),
absoluteUrl('manifest/media-00003.ts'),
'made segment request'); 'made segment request');
}); });
@ -1381,7 +1377,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() {
equal(requests.length, requestsLength, 'made no additional requests'); equal(requests.length, requestsLength, 'made no additional requests');
}); });
test('duration is Infinity for live playlists', function() {
test('tech\'s duration reports Infinity for live playlists', function() {
player.src({ player.src({
src: 'http://example.com/manifest/missingEndlist.m3u8', src: 'http://example.com/manifest/missingEndlist.m3u8',
type: 'application/vnd.apple.mpegurl' type: 'application/vnd.apple.mpegurl'
@ -1390,9 +1386,13 @@ test('duration is Infinity for live playlists', function() {
standardXHRResponse(requests[0]); standardXHRResponse(requests[0]);
strictEqual(player.tech_.hls.mediaSource.duration,
strictEqual(player.tech_.duration(),
Infinity,
'duration on the tech is infinity');
notEqual(player.tech_.hls.mediaSource.duration,
Infinity, Infinity,
'duration is infinity');
'duration on the mediaSource is not infinity');
}); });
test('live playlist starts three target durations before live', function() { test('live playlist starts three target durations before live', function() {
@ -1644,7 +1644,7 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() {
}); });
test('sets timestampOffset when seeking with discontinuities', function() { test('sets timestampOffset when seeking with discontinuities', function() {
var removes = [], timeRange = videojs.createTimeRange(0, 10);
var timeRange = videojs.createTimeRange(0, 10);
player.src({ player.src({
src: 'discontinuity.m3u8', src: 'discontinuity.m3u8',
@ -1670,18 +1670,12 @@ test('sets timestampOffset when seeking with discontinuities', function() {
'3.ts\n' + '3.ts\n' +
'#EXT-X-ENDLIST\n'); '#EXT-X-ENDLIST\n');
player.tech_.hls.sourceBuffer.timestampOffset = 0; player.tech_.hls.sourceBuffer.timestampOffset = 0;
player.tech_.hls.sourceBuffer.remove = function(start, end) {
timeRange = videojs.createTimeRange();
removes.push([start, end]);
};
player.currentTime(21); player.currentTime(21);
clock.tick(1); clock.tick(1);
equal(requests.shift().aborted, true, 'aborted first request'); equal(requests.shift().aborted, true, 'aborted first request');
standardXHRResponse(requests.pop()); // 3.ts standardXHRResponse(requests.pop()); // 3.ts
clock.tick(1000); clock.tick(1000);
equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero'); equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero');
equal(removes.length, 1, 'remove was called');
}); });
test('can seek before the source buffer opens', function() { test('can seek before the source buffer opens', function() {

Loading…
Cancel
Save