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) { ...@@ -712,11 +712,11 @@ videojs.Hls.prototype.drainBuffer = function(event) {
712 tags, 712 tags,
713 bytes, 713 bytes,
714 segment, 714 segment,
715 durationOffset,
715 decrypter, 716 decrypter,
716 segIv, 717 segIv,
717
718 ptsTime, 718 ptsTime,
719 segmentOffset, 719 segmentOffset = 0,
720 segmentBuffer = this.segmentBuffer_; 720 segmentBuffer = this.segmentBuffer_;
721 721
722 if (!segmentBuffer.length || !this.sourceBuffer) { 722 if (!segmentBuffer.length || !this.sourceBuffer) {
...@@ -773,10 +773,32 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -773,10 +773,32 @@ videojs.Hls.prototype.drainBuffer = function(event) {
773 this.segmentParser_.flushTags(); 773 this.segmentParser_.flushTags();
774 774
775 tags = []; 775 tags = [];
776
776 while (this.segmentParser_.tagsAvailable()) { 777 while (this.segmentParser_.tagsAvailable()) {
777 tags.push(this.segmentParser_.getNextTag()); 778 tags.push(this.segmentParser_.getNextTag());
778 } 779 }
779 780
781 // This block of code uses the presentation timestamp of the ts segment to calculate its exact duration, since this
782 // may differ by fractions of a second from what is reported. Using the exact, calculated 'preciseDuration' allows
783 // for smoother seeking and calculation of the total playlist duration, which previously (especially in short videos)
784 // was reported erroneously and made the play head overrun the end of the progress bar.
785 if (tags.length > 0) {
786 segment.preciseTimestamp = tags[tags.length - 1].pts;
787
788 if (playlist.segments[mediaIndex - 1]) {
789 if (playlist.segments[mediaIndex - 1].preciseTimestamp) {
790 durationOffset = playlist.segments[mediaIndex - 1].preciseTimestamp;
791 } else {
792 durationOffset = (playlist.targetDuration * (mediaIndex - 1) + playlist.segments[mediaIndex - 1].duration) * 1000;
793 }
794 segment.preciseDuration = (segment.preciseTimestamp - durationOffset) / 1000;
795 } else if (mediaIndex === 0) {
796 segment.preciseDuration = segment.preciseTimestamp / 1000;
797 }
798 }
799
800 this.updateDuration(this.playlists.media());
801
780 // if we're refilling the buffer after a seek, scan through the muxed 802 // if we're refilling the buffer after a seek, scan through the muxed
781 // FLV tags until we find the one that is closest to the desired 803 // FLV tags until we find the one that is closest to the desired
782 // playback time 804 // playback time
...@@ -948,7 +970,7 @@ videojs.Hls.getPlaylistDuration = function(playlist, startIndex, endIndex) { ...@@ -948,7 +970,7 @@ videojs.Hls.getPlaylistDuration = function(playlist, startIndex, endIndex) {
948 970
949 for (; i >= startIndex; i--) { 971 for (; i >= startIndex; i--) {
950 segment = playlist.segments[i]; 972 segment = playlist.segments[i];
951 dur += (segment.duration !== undefined ? segment.duration : playlist.targetDuration) || 0; 973 dur += segment.preciseDuration || segment.duration || playlist.targetDuration || 0;
952 } 974 }
953 975
954 return dur; 976 return dur;
......
...@@ -269,7 +269,7 @@ test('sets the duration if one is available on the playlist', function() { ...@@ -269,7 +269,7 @@ test('sets the duration if one is available on the playlist', function() {
269 standardXHRResponse(requests[0]); 269 standardXHRResponse(requests[0]);
270 strictEqual(calls, 1, 'duration is set'); 270 strictEqual(calls, 1, 'duration is set');
271 standardXHRResponse(requests[1]); 271 standardXHRResponse(requests[1]);
272 strictEqual(calls, 1, 'duration is set'); 272 strictEqual(calls, 2, 'duration is set');
273 }); 273 });
274 274
275 test('calculates the duration if needed', function() { 275 test('calculates the duration if needed', function() {
...@@ -1063,6 +1063,37 @@ test('flushes the parser after each segment', function() { ...@@ -1063,6 +1063,37 @@ test('flushes the parser after each segment', function() {
1063 strictEqual(flushes, 1, 'tags are flushed at the end of a segment'); 1063 strictEqual(flushes, 1, 'tags are flushed at the end of a segment');
1064 }); 1064 });
1065 1065
1066 test('calculates preciseTimestamp and preciseDuration for a new segment', function() {
1067 // mock out the segment parser
1068 videojs.Hls.SegmentParser = function() {
1069 var tagsAvailable = true,
1070 tag = { pts : 200000 };
1071 this.getFlvHeader = function() {
1072 return [];
1073 };
1074 this.parseSegmentBinaryData = function() {};
1075 this.flushTags = function() {};
1076 this.tagsAvailable = function() { return tagsAvailable; };
1077 this.getNextTag = function() { tagsAvailable = false; return tag; };
1078 this.metadataStream = {
1079 on: Function.prototype
1080 };
1081 };
1082
1083 player.src({
1084 src: 'manifest/media.m3u8',
1085 type: 'application/vnd.apple.mpegurl'
1086 });
1087 openMediaSource(player);
1088
1089 standardXHRResponse(requests[0]);
1090 strictEqual(player.duration(), 40, 'player duration is read from playlist on load');
1091 standardXHRResponse(requests[1]);
1092 strictEqual(player.hls.playlists.media().segments[0].preciseTimestamp, 200000, 'preciseTimestamp is calculated and stored');
1093 strictEqual(player.hls.playlists.media().segments[0].preciseDuration, 200, 'preciseDuration is calculated and stored');
1094 strictEqual(player.duration(), 230, 'player duration is calculated using preciseDuration');
1095 });
1096
1066 test('exposes in-band metadata events as cues', function() { 1097 test('exposes in-band metadata events as cues', function() {
1067 var track; 1098 var track;
1068 player.src({ 1099 player.src({
......