05391966 by David LaPalomento

fix error with audio- or video-only streams. Closes #348

2 parents 385ed9fb b58a7fcb
...@@ -3,6 +3,7 @@ CHANGELOG ...@@ -3,6 +3,7 @@ CHANGELOG
3 3
4 ## HEAD (Unreleased) 4 ## HEAD (Unreleased)
5 * do not assume media sequence starts at zero ([view](https://github.com/videojs/videojs-contrib-hls/pull/346)) 5 * do not assume media sequence starts at zero ([view](https://github.com/videojs/videojs-contrib-hls/pull/346))
6 * fix error with audio- or video-only streams ([view](https://github.com/videojs/videojs-contrib-hls/pull/348))
6 7
7 -------------------- 8 --------------------
8 9
......
...@@ -5,7 +5,23 @@ ...@@ -5,7 +5,23 @@
5 'use strict'; 5 'use strict';
6 6
7 var DEFAULT_TARGET_DURATION = 10; 7 var DEFAULT_TARGET_DURATION = 10;
8 var accumulateDuration, ascendingNumeric, duration, intervalDuration, rangeDuration, seekable; 8 var accumulateDuration, ascendingNumeric, duration, intervalDuration, optionalMin, optionalMax, rangeDuration, seekable;
9
10 // Math.min that will return the alternative input if one of its
11 // parameters in undefined
12 optionalMin = function(left, right) {
13 left = isFinite(left) ? left : Infinity;
14 right = isFinite(right) ? right : Infinity;
15 return Math.min(left, right);
16 };
17
18 // Math.max that will return the alternative input if one of its
19 // parameters in undefined
20 optionalMax = function(left, right) {
21 left = isFinite(left) ? left: -Infinity;
22 right = isFinite(right) ? right: -Infinity;
23 return Math.max(left, right);
24 };
9 25
10 // Array.sort comparator to sort numbers in ascending order 26 // Array.sort comparator to sort numbers in ascending order
11 ascendingNumeric = function(left, right) { 27 ascendingNumeric = function(left, right) {
...@@ -91,7 +107,8 @@ ...@@ -91,7 +107,8 @@
91 // available PTS information 107 // available PTS information
92 for (left = range.start; left < range.end; left++) { 108 for (left = range.start; left < range.end; left++) {
93 segment = playlist.segments[left]; 109 segment = playlist.segments[left];
94 if (segment.minVideoPts !== undefined) { 110 if (segment.minVideoPts !== undefined ||
111 segment.minAudioPts !== undefined) {
95 break; 112 break;
96 } 113 }
97 result += segment.duration || targetDuration; 114 result += segment.duration || targetDuration;
...@@ -100,10 +117,12 @@ ...@@ -100,10 +117,12 @@
100 // see if there's enough information to include the trailing time 117 // see if there's enough information to include the trailing time
101 if (includeTrailingTime) { 118 if (includeTrailingTime) {
102 segment = playlist.segments[range.end]; 119 segment = playlist.segments[range.end];
103 if (segment && segment.minVideoPts !== undefined) { 120 if (segment &&
121 (segment.minVideoPts !== undefined ||
122 segment.minAudioPts !== undefined)) {
104 result += 0.001 * 123 result += 0.001 *
105 (Math.min(segment.minVideoPts, segment.minAudioPts) - 124 (optionalMin(segment.minVideoPts, segment.minAudioPts) -
106 Math.min(playlist.segments[left].minVideoPts, 125 optionalMin(playlist.segments[left].minVideoPts,
107 playlist.segments[left].minAudioPts)); 126 playlist.segments[left].minAudioPts));
108 return result; 127 return result;
109 } 128 }
...@@ -112,7 +131,8 @@ ...@@ -112,7 +131,8 @@
112 // do the same thing while finding the latest segment 131 // do the same thing while finding the latest segment
113 for (right = range.end - 1; right >= left; right--) { 132 for (right = range.end - 1; right >= left; right--) {
114 segment = playlist.segments[right]; 133 segment = playlist.segments[right];
115 if (segment.maxVideoPts !== undefined) { 134 if (segment.maxVideoPts !== undefined ||
135 segment.maxAudioPts !== undefined) {
116 break; 136 break;
117 } 137 }
118 result += segment.duration || targetDuration; 138 result += segment.duration || targetDuration;
...@@ -121,9 +141,9 @@ ...@@ -121,9 +141,9 @@
121 // add in the PTS interval in seconds between them 141 // add in the PTS interval in seconds between them
122 if (right >= left) { 142 if (right >= left) {
123 result += 0.001 * 143 result += 0.001 *
124 (Math.max(playlist.segments[right].maxVideoPts, 144 (optionalMax(playlist.segments[right].maxVideoPts,
125 playlist.segments[right].maxAudioPts) - 145 playlist.segments[right].maxAudioPts) -
126 Math.min(playlist.segments[left].minVideoPts, 146 optionalMin(playlist.segments[left].minVideoPts,
127 playlist.segments[left].minAudioPts)); 147 playlist.segments[left].minAudioPts));
128 } 148 }
129 149
...@@ -158,7 +178,7 @@ ...@@ -158,7 +178,7 @@
158 targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION; 178 targetDuration = playlist.targetDuration || DEFAULT_TARGET_DURATION;
159 179
160 // estimate expired segment duration using the target duration 180 // estimate expired segment duration using the target duration
161 expiredSegmentCount = Math.max(playlist.mediaSequence - startSequence, 0); 181 expiredSegmentCount = optionalMax(playlist.mediaSequence - startSequence, 0);
162 result += expiredSegmentCount * targetDuration; 182 result += expiredSegmentCount * targetDuration;
163 183
164 // accumulate the segment durations into the result 184 // accumulate the segment durations into the result
...@@ -257,9 +277,9 @@ ...@@ -257,9 +277,9 @@
257 // from the result. 277 // from the result.
258 for (i = playlist.segments.length - 1; i >= 0 && liveBuffer > 0; i--) { 278 for (i = playlist.segments.length - 1; i >= 0 && liveBuffer > 0; i--) {
259 segment = playlist.segments[i]; 279 segment = playlist.segments[i];
260 pending = Math.min(duration(playlist, 280 pending = optionalMin(duration(playlist,
261 playlist.mediaSequence + i, 281 playlist.mediaSequence + i,
262 playlist.mediaSequence + i + 1), 282 playlist.mediaSequence + i + 1),
263 liveBuffer); 283 liveBuffer);
264 liveBuffer -= pending; 284 liveBuffer -= pending;
265 end -= pending; 285 end -= pending;
......
...@@ -899,10 +899,14 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -899,10 +899,14 @@ videojs.Hls.prototype.drainBuffer = function(event) {
899 if (this.segmentParser_.tagsAvailable()) { 899 if (this.segmentParser_.tagsAvailable()) {
900 // record PTS information for the segment so we can calculate 900 // record PTS information for the segment so we can calculate
901 // accurate durations and seek reliably 901 // accurate durations and seek reliably
902 segment.minVideoPts = this.segmentParser_.stats.minVideoPts(); 902 if (this.segmentParser_.stats.h264Tags()) {
903 segment.maxVideoPts = this.segmentParser_.stats.maxVideoPts(); 903 segment.minVideoPts = this.segmentParser_.stats.minVideoPts();
904 segment.minAudioPts = this.segmentParser_.stats.minAudioPts(); 904 segment.maxVideoPts = this.segmentParser_.stats.maxVideoPts();
905 segment.maxAudioPts = this.segmentParser_.stats.maxAudioPts(); 905 }
906 if (this.segmentParser_.stats.aacTags()) {
907 segment.minAudioPts = this.segmentParser_.stats.minAudioPts();
908 segment.maxAudioPts = this.segmentParser_.stats.maxAudioPts();
909 }
906 } 910 }
907 911
908 while (this.segmentParser_.tagsAvailable()) { 912 while (this.segmentParser_.tagsAvailable()) {
......
...@@ -259,6 +259,30 @@ ...@@ -259,6 +259,30 @@
259 equal(duration, 30.1, 'used the PTS-based interval'); 259 equal(duration, 30.1, 'used the PTS-based interval');
260 }); 260 });
261 261
262 test('works for media without audio', function() {
263 equal(Playlist.duration({
264 mediaSequence: 0,
265 endList: true,
266 segments: [{
267 minVideoPts: 0,
268 maxVideoPts: 9 * 1000,
269 uri: 'no-audio.ts'
270 }]
271 }), 9, 'used video PTS values');
272 });
273
274 test('works for media without video', function() {
275 equal(Playlist.duration({
276 mediaSequence: 0,
277 endList: true,
278 segments: [{
279 minAudioPts: 0,
280 maxAudioPts: 9 * 1000,
281 uri: 'no-video.ts'
282 }]
283 }), 9, 'used video PTS values');
284 });
285
262 test('uses the largest continuous available PTS ranges', function() { 286 test('uses the largest continuous available PTS ranges', function() {
263 var playlist = { 287 var playlist = {
264 mediaSequence: 0, 288 mediaSequence: 0,
......
...@@ -284,6 +284,17 @@ ...@@ -284,6 +284,17 @@
284 equal(packets.length, 1, 'parsed non-payload metadata packet'); 284 equal(packets.length, 1, 'parsed non-payload metadata packet');
285 }); 285 });
286 286
287 test('returns undefined for PTS stats when a track is missing', function() {
288 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
289 programs: {
290 0x01: [0x01]
291 }
292 })));
293
294 strictEqual(parser.stats.h264Tags(), 0, 'no video tags yet');
295 strictEqual(parser.stats.aacTags(), 0, 'no audio tags yet');
296 });
297
287 test('parses the first bipbop segment', function() { 298 test('parses the first bipbop segment', function() {
288 parser.parseSegmentBinaryData(window.bcSegment); 299 parser.parseSegmentBinaryData(window.bcSegment);
289 300
......
...@@ -121,12 +121,18 @@ var ...@@ -121,12 +121,18 @@ var
121 ]); 121 ]);
122 122
123 this.stats = { 123 this.stats = {
124 h264Tags: function() {
125 return tags.length;
126 },
124 minVideoPts: function() { 127 minVideoPts: function() {
125 return tags[0].pts; 128 return tags[0].pts;
126 }, 129 },
127 maxVideoPts: function() { 130 maxVideoPts: function() {
128 return tags[tags.length - 1].pts; 131 return tags[tags.length - 1].pts;
129 }, 132 },
133 aacTags: function() {
134 return tags.length;
135 },
130 minAudioPts: function() { 136 minAudioPts: function() {
131 return tags[0].pts; 137 return tags[0].pts;
132 }, 138 },
...@@ -1044,6 +1050,64 @@ test('records the min and max PTS values for a segment', function() { ...@@ -1044,6 +1050,64 @@ test('records the min and max PTS values for a segment', function() {
1044 equal(player.hls.playlists.media().segments[0].maxAudioPts, 10, 'recorded max audio pts'); 1050 equal(player.hls.playlists.media().segments[0].maxAudioPts, 10, 'recorded max audio pts');
1045 }); 1051 });
1046 1052
1053 test('records PTS values for video-only segments', function() {
1054 var tags = [];
1055 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1056 player.src({
1057 src: 'manifest/media.m3u8',
1058 type: 'application/vnd.apple.mpegurl'
1059 });
1060 openMediaSource(player);
1061 standardXHRResponse(requests.pop()); // media.m3u8
1062
1063 player.hls.segmentParser_.stats.aacTags = function() {
1064 return 0;
1065 };
1066 player.hls.segmentParser_.stats.minAudioPts = function() {
1067 throw new Error('No audio tags');
1068 };
1069 player.hls.segmentParser_.stats.maxAudioPts = function() {
1070 throw new Error('No audio tags');
1071 };
1072 tags.push({ pts: 0, bytes: new Uint8Array(1) });
1073 tags.push({ pts: 10, bytes: new Uint8Array(1) });
1074 standardXHRResponse(requests.pop()); // segment 0
1075
1076 equal(player.hls.playlists.media().segments[0].minVideoPts, 0, 'recorded min video pts');
1077 equal(player.hls.playlists.media().segments[0].maxVideoPts, 10, 'recorded max video pts');
1078 strictEqual(player.hls.playlists.media().segments[0].minAudioPts, undefined, 'min audio pts is undefined');
1079 strictEqual(player.hls.playlists.media().segments[0].maxAudioPts, undefined, 'max audio pts is undefined');
1080 });
1081
1082 test('records PTS values for audio-only segments', function() {
1083 var tags = [];
1084 videojs.Hls.SegmentParser = mockSegmentParser(tags);
1085 player.src({
1086 src: 'manifest/media.m3u8',
1087 type: 'application/vnd.apple.mpegurl'
1088 });
1089 openMediaSource(player);
1090 standardXHRResponse(requests.pop()); // media.m3u8
1091
1092 player.hls.segmentParser_.stats.h264Tags = function() {
1093 return 0;
1094 };
1095 player.hls.segmentParser_.stats.minVideoPts = function() {
1096 throw new Error('No video tags');
1097 };
1098 player.hls.segmentParser_.stats.maxVideoPts = function() {
1099 throw new Error('No video tags');
1100 };
1101 tags.push({ pts: 0, bytes: new Uint8Array(1) });
1102 tags.push({ pts: 10, bytes: new Uint8Array(1) });
1103 standardXHRResponse(requests.pop()); // segment 0
1104
1105 equal(player.hls.playlists.media().segments[0].minAudioPts, 0, 'recorded min audio pts');
1106 equal(player.hls.playlists.media().segments[0].maxAudioPts, 10, 'recorded max audio pts');
1107 strictEqual(player.hls.playlists.media().segments[0].minVideoPts, undefined, 'min video pts is undefined');
1108 strictEqual(player.hls.playlists.media().segments[0].maxVideoPts, undefined, 'max video pts is undefined');
1109 });
1110
1047 test('waits to download new segments until the media playlist is stable', function() { 1111 test('waits to download new segments until the media playlist is stable', function() {
1048 var media; 1112 var media;
1049 player.src({ 1113 player.src({
......