fix error with audio- or video-only streams. Closes #348
Showing
6 changed files
with
140 additions
and
16 deletions
... | @@ -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({ | ... | ... |
-
Please register or sign in to post a comment