781cb8eb by Brandon Bay

Use presentation time stamp to calculate playlist duration

This 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.
1 parent 0d3208f0
......@@ -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({
......