1811ffc3 by bc-bbay

Merge pull request #260 from videojs/preciseDuration

Use presentation time stamp to calculate playlist duration
2 parents 0d3208f0 781cb8eb
......@@ -712,11 +712,11 @@ videojs.Hls.prototype.drainBuffer = function(event) {
tags,
bytes,
segment,
durationOffset,
decrypter,
segIv,
ptsTime,
segmentOffset,
segmentOffset = 0,
segmentBuffer = this.segmentBuffer_;
if (!segmentBuffer.length || !this.sourceBuffer) {
......@@ -773,10 +773,32 @@ videojs.Hls.prototype.drainBuffer = function(event) {
this.segmentParser_.flushTags();
tags = [];
while (this.segmentParser_.tagsAvailable()) {
tags.push(this.segmentParser_.getNextTag());
}
// This block of code uses the presentation timestamp of the ts segment to calculate its exact duration, since this
// may differ by fractions of a second from what is reported. Using the exact, calculated 'preciseDuration' allows
// for smoother seeking and calculation of the total playlist duration, which previously (especially in short videos)
// was reported erroneously and made the play head overrun the end of the progress bar.
if (tags.length > 0) {
segment.preciseTimestamp = tags[tags.length - 1].pts;
if (playlist.segments[mediaIndex - 1]) {
if (playlist.segments[mediaIndex - 1].preciseTimestamp) {
durationOffset = playlist.segments[mediaIndex - 1].preciseTimestamp;
} else {
durationOffset = (playlist.targetDuration * (mediaIndex - 1) + playlist.segments[mediaIndex - 1].duration) * 1000;
}
segment.preciseDuration = (segment.preciseTimestamp - durationOffset) / 1000;
} else if (mediaIndex === 0) {
segment.preciseDuration = segment.preciseTimestamp / 1000;
}
}
this.updateDuration(this.playlists.media());
// if we're refilling the buffer after a seek, scan through the muxed
// FLV tags until we find the one that is closest to the desired
// playback time
......@@ -948,7 +970,7 @@ videojs.Hls.getPlaylistDuration = function(playlist, startIndex, endIndex) {
for (; i >= startIndex; i--) {
segment = playlist.segments[i];
dur += (segment.duration !== undefined ? segment.duration : playlist.targetDuration) || 0;
dur += segment.preciseDuration || segment.duration || playlist.targetDuration || 0;
}
return dur;
......
......@@ -269,7 +269,7 @@ test('sets the duration if one is available on the playlist', function() {
standardXHRResponse(requests[0]);
strictEqual(calls, 1, 'duration is set');
standardXHRResponse(requests[1]);
strictEqual(calls, 1, 'duration is set');
strictEqual(calls, 2, 'duration is set');
});
test('calculates the duration if needed', function() {
......@@ -1063,6 +1063,37 @@ test('flushes the parser after each segment', function() {
strictEqual(flushes, 1, 'tags are flushed at the end of a segment');
});
test('calculates preciseTimestamp and preciseDuration for a new segment', function() {
// mock out the segment parser
videojs.Hls.SegmentParser = function() {
var tagsAvailable = true,
tag = { pts : 200000 };
this.getFlvHeader = function() {
return [];
};
this.parseSegmentBinaryData = function() {};
this.flushTags = function() {};
this.tagsAvailable = function() { return tagsAvailable; };
this.getNextTag = function() { tagsAvailable = false; return tag; };
this.metadataStream = {
on: Function.prototype
};
};
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
openMediaSource(player);
standardXHRResponse(requests[0]);
strictEqual(player.duration(), 40, 'player duration is read from playlist on load');
standardXHRResponse(requests[1]);
strictEqual(player.hls.playlists.media().segments[0].preciseTimestamp, 200000, 'preciseTimestamp is calculated and stored');
strictEqual(player.hls.playlists.media().segments[0].preciseDuration, 200, 'preciseDuration is calculated and stored');
strictEqual(player.duration(), 230, 'player duration is calculated using preciseDuration');
});
test('exposes in-band metadata events as cues', function() {
var track;
player.src({
......