recalculate currentTime whenever seeking
The more video is buffered, the more accurate our calculation of currentTime becomes. Make sure we don't artificially anchor our currentTime calculation and then get out of sync once more video has been downloaded.
Showing
5 changed files
with
263 additions
and
58 deletions
... | @@ -431,11 +431,12 @@ | ... | @@ -431,11 +431,12 @@ |
431 | for (i = 0; i < this.media_.segments.length; i++) { | 431 | for (i = 0; i < this.media_.segments.length; i++) { |
432 | time -= Playlist.duration(this.media_, | 432 | time -= Playlist.duration(this.media_, |
433 | this.media_.mediaSequence + i, | 433 | this.media_.mediaSequence + i, |
434 | this.media_.mediaSequence + i + 1); | 434 | this.media_.mediaSequence + i + 1, |
435 | true); | ||
435 | 436 | ||
436 | // HLS version 3 and lower round segment durations to the | 437 | // HLS version 3 and lower round segment durations to the |
437 | // nearest decimal integer. When the correct media index is | 438 | // nearest decimal integer. When the correct media index is |
438 | // ambiguous, prefer the lower one. | 439 | // ambiguous, prefer the higher one. |
439 | if (time <= 0) { | 440 | if (time <= 0) { |
440 | return i; | 441 | return i; |
441 | } | 442 | } | ... | ... |
... | @@ -17,10 +17,13 @@ | ... | @@ -17,10 +17,13 @@ |
17 | * boundary for the playlist. Defaults to 0. | 17 | * boundary for the playlist. Defaults to 0. |
18 | * @param endSequence {number} (optional) an exclusive upper boundary | 18 | * @param endSequence {number} (optional) an exclusive upper boundary |
19 | * for the playlist. Defaults to playlist length. | 19 | * for the playlist. Defaults to playlist length. |
20 | * @param strict {boolean} (optional) if true, the interval between | ||
21 | * the final segment and the subsequent segment will not be included | ||
22 | * in the result | ||
20 | * @return {number} the duration between the start index and end | 23 | * @return {number} the duration between the start index and end |
21 | * index. | 24 | * index. |
22 | */ | 25 | */ |
23 | segmentsDuration = function(playlist, startSequence, endSequence) { | 26 | segmentsDuration = function(playlist, startSequence, endSequence, strict) { |
24 | var targetDuration, i, j, segment, endSegment, expiredSegmentCount, result = 0; | 27 | var targetDuration, i, j, segment, endSegment, expiredSegmentCount, result = 0; |
25 | 28 | ||
26 | startSequence = startSequence || 0; | 29 | startSequence = startSequence || 0; |
... | @@ -54,11 +57,26 @@ | ... | @@ -54,11 +57,26 @@ |
54 | } | 57 | } |
55 | } | 58 | } |
56 | endSegment = playlist.segments[j - playlist.mediaSequence]; | 59 | endSegment = playlist.segments[j - playlist.mediaSequence]; |
60 | |||
57 | result += (Math.max(endSegment.maxVideoPts, endSegment.maxAudioPts) - | 61 | result += (Math.max(endSegment.maxVideoPts, endSegment.maxAudioPts) - |
58 | Math.min(segment.minVideoPts, segment.minAudioPts)) * 0.001; | 62 | Math.min(segment.minVideoPts, segment.minAudioPts)) * 0.001; |
59 | i = j; | 63 | i = j; |
60 | } | 64 | } |
61 | 65 | ||
66 | // attribute the gap between the latest PTS value in end segment | ||
67 | // and the earlier PTS in the next one to the result | ||
68 | segment = playlist.segments[endSequence - 1]; | ||
69 | endSegment = playlist.segments[endSequence]; | ||
70 | if (!strict && | ||
71 | endSegment && | ||
72 | !endSegment.discontinuity && | ||
73 | endSegment.minVideoPts && | ||
74 | segment && | ||
75 | segment.maxVideoPts) { | ||
76 | result += (Math.min(endSegment.minVideoPts, endSegment.minAudioPts) - | ||
77 | Math.max(segment.maxVideoPts, segment.maxAudioPts)) * 0.001; | ||
78 | } | ||
79 | |||
62 | return result; | 80 | return result; |
63 | }; | 81 | }; |
64 | 82 | ||
... | @@ -72,10 +90,13 @@ | ... | @@ -72,10 +90,13 @@ |
72 | * boundary for the playlist. Defaults to 0. | 90 | * boundary for the playlist. Defaults to 0. |
73 | * @param endSequence {number} (optional) an exclusive upper boundary | 91 | * @param endSequence {number} (optional) an exclusive upper boundary |
74 | * for the playlist. Defaults to playlist length. | 92 | * for the playlist. Defaults to playlist length. |
93 | * @param strict {boolean} (optional) if true, the interval between | ||
94 | * the final segment and the subsequent segment will not be included | ||
95 | * in the result | ||
75 | * @return {number} the duration between the start index and end | 96 | * @return {number} the duration between the start index and end |
76 | * index. | 97 | * index. |
77 | */ | 98 | */ |
78 | duration = function(playlist, startSequence, endSequence) { | 99 | duration = function(playlist, startSequence, endSequence, strict) { |
79 | if (!playlist) { | 100 | if (!playlist) { |
80 | return 0; | 101 | return 0; |
81 | } | 102 | } |
... | @@ -97,7 +118,8 @@ | ... | @@ -97,7 +118,8 @@ |
97 | // calculate the total duration based on the segment durations | 118 | // calculate the total duration based on the segment durations |
98 | return segmentsDuration(playlist, | 119 | return segmentsDuration(playlist, |
99 | startSequence, | 120 | startSequence, |
100 | endSequence); | 121 | endSequence, |
122 | strict); | ||
101 | }; | 123 | }; |
102 | 124 | ||
103 | /** | 125 | /** | ... | ... |
... | @@ -260,7 +260,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { | ... | @@ -260,7 +260,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { |
260 | // add a metadata cue whenever a metadata event is triggered during | 260 | // add a metadata cue whenever a metadata event is triggered during |
261 | // segment parsing | 261 | // segment parsing |
262 | metadataStream.on('data', function(metadata) { | 262 | metadataStream.on('data', function(metadata) { |
263 | var i, cue, frame, time, media, segmentOffset, hexDigit; | 263 | var i, hexDigit; |
264 | 264 | ||
265 | // create the metadata track if this is the first ID3 tag we've | 265 | // create the metadata track if this is the first ID3 tag we've |
266 | // seen | 266 | // seen |
... | @@ -276,19 +276,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { | ... | @@ -276,19 +276,7 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { |
276 | } | 276 | } |
277 | } | 277 | } |
278 | 278 | ||
279 | // calculate the start time for the segment that is currently being parsed | 279 | tech.addCuesForMetadata_(textTrack, metadata); |
280 | media = tech.playlists.media(); | ||
281 | segmentOffset = tech.playlists.expiredPreDiscontinuity_ + tech.playlists.expiredPostDiscontinuity_; | ||
282 | segmentOffset += videojs.Hls.Playlist.duration(media, media.mediaSequence, media.mediaSequence + tech.mediaIndex); | ||
283 | |||
284 | // create cue points for all the ID3 frames in this metadata event | ||
285 | for (i = 0; i < metadata.frames.length; i++) { | ||
286 | frame = metadata.frames[i]; | ||
287 | time = tech.segmentParser_.mediaTimelineOffset + ((metadata.pts - tech.segmentParser_.timestampOffset) * 0.001); | ||
288 | cue = new window.VTTCue(time, time, frame.value || frame.url || ''); | ||
289 | cue.frame = frame; | ||
290 | textTrack.addCue(cue); | ||
291 | } | ||
292 | }); | 280 | }); |
293 | 281 | ||
294 | // when seeking, clear out all cues ahead of the earliest position | 282 | // when seeking, clear out all cues ahead of the earliest position |
... | @@ -312,6 +300,25 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { | ... | @@ -312,6 +300,25 @@ videojs.Hls.prototype.setupMetadataCueTranslation_ = function() { |
312 | }); | 300 | }); |
313 | }; | 301 | }; |
314 | 302 | ||
303 | videojs.Hls.prototype.addCuesForMetadata_ = function(textTrack, metadata) { | ||
304 | var i, cue, frame, minPts, segmentInfo, segmentOffset, time; | ||
305 | segmentInfo = this.segmentBuffer_[0]; | ||
306 | segmentOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, | ||
307 | segmentInfo.playlist.mediaSequence, | ||
308 | segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex); | ||
309 | minPts = Math.min(this.segmentParser_.stats.minVideoPts(), | ||
310 | this.segmentParser_.stats.minAudioPts()); | ||
311 | |||
312 | // create cue points for all the ID3 frames in this metadata event | ||
313 | for (i = 0; i < metadata.frames.length; i++) { | ||
314 | frame = metadata.frames[i]; | ||
315 | time = segmentOffset + ((metadata.pts - minPts) * 0.001); | ||
316 | cue = new window.VTTCue(time, time, frame.value || frame.url || ''); | ||
317 | cue.frame = frame; | ||
318 | textTrack.addCue(cue); | ||
319 | } | ||
320 | }; | ||
321 | |||
315 | /** | 322 | /** |
316 | * Reset the mediaIndex if play() is called after the video has | 323 | * Reset the mediaIndex if play() is called after the video has |
317 | * ended. | 324 | * ended. |
... | @@ -812,8 +819,6 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -812,8 +819,6 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
812 | decrypter, | 819 | decrypter, |
813 | segIv, | 820 | segIv, |
814 | ptsTime, | 821 | ptsTime, |
815 | tagPts, | ||
816 | tagIndex, | ||
817 | segmentOffset = 0, | 822 | segmentOffset = 0, |
818 | segmentBuffer = this.segmentBuffer_; | 823 | segmentBuffer = this.segmentBuffer_; |
819 | 824 | ||
... | @@ -872,19 +877,12 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -872,19 +877,12 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
872 | } | 877 | } |
873 | 878 | ||
874 | event = event || {}; | 879 | event = event || {}; |
875 | segmentOffset = this.playlists.expiredPreDiscontinuity_; | ||
876 | segmentOffset += this.playlists.expiredPostDiscontinuity_; | ||
877 | segmentOffset += videojs.Hls.Playlist.duration(playlist, playlist.mediaSequence, playlist.mediaSequence + mediaIndex); | ||
878 | segmentOffset *= 1000; | ||
879 | 880 | ||
880 | // if this segment starts is the start of a new discontinuity | 881 | // if this segment starts is the start of a new discontinuity |
881 | // sequence, the segment parser's timestamp offset must be | 882 | // sequence, the segment parser's timestamp offset must be |
882 | // re-calculated | 883 | // re-calculated |
883 | if (segment.discontinuity) { | 884 | if (segment.discontinuity) { |
884 | this.segmentParser_.mediaTimelineOffset = segmentOffset * 0.001; | ||
885 | this.segmentParser_.timestampOffset = null; | 885 | this.segmentParser_.timestampOffset = null; |
886 | } else if (this.segmentParser_.mediaTimelineOffset === null) { | ||
887 | this.segmentParser_.mediaTimelineOffset = segmentOffset * 0.001; | ||
888 | } | 886 | } |
889 | 887 | ||
890 | // transmux the segment data from MP2T to FLV | 888 | // transmux the segment data from MP2T to FLV |
... | @@ -908,29 +906,66 @@ videojs.Hls.prototype.drainBuffer = function(event) { | ... | @@ -908,29 +906,66 @@ videojs.Hls.prototype.drainBuffer = function(event) { |
908 | 906 | ||
909 | this.updateDuration(this.playlists.media()); | 907 | this.updateDuration(this.playlists.media()); |
910 | 908 | ||
909 | /* | ||
910 | Live In-Progress | ||
911 | 0 s c m | ||
912 | . . . |~~~~~~~|--%-----^--|~~~~~%~~~~~|-----| . . . | ||
913 | p q AAJ | ||
914 | |||
915 | Live In-Progress 2 | ||
916 | 0 s c m | ||
917 | . . . |~~~~~~~~~~%~~|--^--|~~~~~%~~~~~|-----| . . . | ||
918 | q AAJ | ||
919 | |||
920 | 0 400 450 | ||
921 | . . . |-------X-----| . . . | ||
922 | |||
923 | Live Before Buffering | ||
924 | c | ||
925 | . . . |~~%~~~~~| . . . | ||
926 | ?? | ||
927 | |||
928 | p = earliest known pts | ||
929 | s = earliest playback position | ||
930 | q = earliest pts after the last discontinuity | ||
931 | c = current time | ||
932 | m = the latest buffered playback position | ||
933 | ~ = only EXTINF available | ||
934 | - = PTS available | ||
935 | % = discontinuity | ||
936 | . = expired or unavailable | ||
937 | A = buffered in actionscript | ||
938 | J = buffered in javascript | ||
939 | |||
940 | Calculate current pts from current time | ||
941 | - subtract current time from buffered end to find out the interval between the latest buffered playback position and current time | ||
942 | - determine the current segment by subtracting segment durations from the latest buffered playback position | ||
943 | - determine current pts based on max segment pts | ||
944 | Determine the target segment by calculating the duration of intermediate segments | ||
945 | Add the difference between current time and the target time to find the target pts | ||
946 | Skip samples until the next sample is greater than or equal to the target pts | ||
947 | */ | ||
948 | |||
911 | // if we're refilling the buffer after a seek, scan through the muxed | 949 | // if we're refilling the buffer after a seek, scan through the muxed |
912 | // FLV tags until we find the one that is closest to the desired | 950 | // FLV tags until we find the one that is closest to the desired |
913 | // playback time | 951 | // playback time |
914 | if (typeof offset === 'number') { | 952 | if (typeof offset === 'number') { |
915 | ptsTime = offset - segmentOffset + tags[0].pts; | 953 | // determine the offset within this segment we're seeking to |
916 | 954 | segmentOffset = this.playlists.expiredPostDiscontinuity_ + this.playlists.expiredPreDiscontinuity_; | |
917 | tagPts = tags[i].pts; | 955 | segmentOffset += videojs.Hls.Playlist.duration(playlist, |
918 | tagIndex = i; | 956 | playlist.mediaSequence, |
919 | while (tagPts < ptsTime) { | 957 | playlist.mediaSequence + mediaIndex); |
958 | segmentOffset = offset - (segmentOffset * 1000); | ||
959 | ptsTime = segmentOffset + tags[0].pts; | ||
960 | |||
961 | while (tags[i + 1] && tags[i].pts < ptsTime) { | ||
920 | i++; | 962 | i++; |
921 | if (tags[i] !== undefined) { | ||
922 | tagPts = tags[i].pts; | ||
923 | tagIndex = i; | ||
924 | } | ||
925 | else { | ||
926 | break; | ||
927 | } | ||
928 | } | 963 | } |
929 | 964 | ||
930 | // tell the SWF where we will be seeking to | 965 | // tell the SWF the media position of the first tag we'll be delivering |
931 | this.el().vjs_setProperty('currentTime', (tagPts - tags[0].pts + segmentOffset) * 0.001); | 966 | this.el().vjs_setProperty('currentTime', ((tags[i].pts - ptsTime + offset) * 0.001)); |
932 | 967 | ||
933 | tags = tags.slice(tagIndex); | 968 | tags = tags.slice(i); |
934 | 969 | ||
935 | this.lastSeekedTime_ = null; | 970 | this.lastSeekedTime_ = null; |
936 | } | 971 | } | ... | ... |
... | @@ -81,29 +81,85 @@ | ... | @@ -81,29 +81,85 @@ |
81 | maxAudioPts: 1 * 10 * 1000 + 1, | 81 | maxAudioPts: 1 * 10 * 1000 + 1, |
82 | uri: '0.ts' | 82 | uri: '0.ts' |
83 | }, { | 83 | }, { |
84 | duration: 10, | 84 | duration: 9, |
85 | uri: '1.ts' | 85 | uri: '1.ts' |
86 | }, { | 86 | }, { |
87 | duration: 10, | 87 | duration: 10, |
88 | uri: '2.ts' | ||
89 | }, { | ||
90 | duration: 10, | ||
88 | minVideoPts: 2 * 10 * 1000 + 7, | 91 | minVideoPts: 2 * 10 * 1000 + 7, |
89 | minAudioPts: 2 * 10 * 1000 + 10, | 92 | minAudioPts: 2 * 10 * 1000 + 10, |
90 | maxVideoPts: 3 * 10 * 1000 + 1, | 93 | maxVideoPts: 3 * 10 * 1000 + 1, |
91 | maxAudioPts: 3 * 10 * 1000 + 2, | 94 | maxAudioPts: 3 * 10 * 1000 + 2, |
92 | uri: '2.ts' | 95 | uri: '3.ts' |
93 | }, { | 96 | }, { |
94 | duration: 10, | 97 | duration: 10, |
95 | maxVideoPts: 4 * 10 * 1000 + 1, | 98 | maxVideoPts: 4 * 10 * 1000 + 1, |
96 | maxAudioPts: 4 * 10 * 1000 + 2, | 99 | maxAudioPts: 4 * 10 * 1000 + 2, |
97 | uri: '3.ts' | 100 | uri: '4.ts' |
98 | }] | 101 | }] |
99 | }, 0, 4); | 102 | }, 0, 5); |
100 | 103 | ||
101 | firstInterval = (1 * 10 * 1000 + 1) - 1; | 104 | firstInterval = (1 * 10 * 1000 + 1) - 1; |
102 | firstInterval *= 0.001; | 105 | firstInterval *= 0.001; |
103 | secondInterval = (4 * 10 * 1000 + 2) - (2 * 10 * 1000 + 7); | 106 | secondInterval = (4 * 10 * 1000 + 2) - (2 * 10 * 1000 + 7); |
104 | secondInterval *= 0.001; | 107 | secondInterval *= 0.001; |
105 | 108 | ||
106 | equal(duration, firstInterval + 10 + secondInterval, 'calculated with mixed intervals'); | 109 | equal(duration, |
110 | firstInterval + 9 + 10 + secondInterval, | ||
111 | 'calculated with mixed intervals'); | ||
112 | }); | ||
113 | |||
114 | test('interval duration handles trailing segments without PTS information', function() { | ||
115 | var duration = Playlist.duration({ | ||
116 | mediaSequence: 0, | ||
117 | endList: true, | ||
118 | segments: [{ | ||
119 | minVideoPts: 0, | ||
120 | minAudioPts: 0, | ||
121 | maxVideoPts: 10 * 1000, | ||
122 | maxAudioPts: 10 * 1000, | ||
123 | uri: '0.ts' | ||
124 | }, { | ||
125 | duration: 9, | ||
126 | uri: '1.ts' | ||
127 | }, { | ||
128 | duration: 10, | ||
129 | uri: '2.ts' | ||
130 | }, { | ||
131 | minVideoPts: 30 * 1000, | ||
132 | minAudioPts: 30 * 1000, | ||
133 | maxVideoPts: 40 * 1000, | ||
134 | maxAudioPts: 40 * 1000, | ||
135 | uri: '3.ts' | ||
136 | }] | ||
137 | }, 0, 3); | ||
138 | |||
139 | equal(duration, 10 + 9 + 10, 'calculated duration'); | ||
140 | }); | ||
141 | |||
142 | test('interval duration counts the time between segments as part of the later segment duration', function() { | ||
143 | var duration = Playlist.duration({ | ||
144 | mediaSequence: 0, | ||
145 | endList: true, | ||
146 | segments: [{ | ||
147 | minVideoPts: 0, | ||
148 | minAudioPts: 0, | ||
149 | maxVideoPts: 1 * 10 * 1000, | ||
150 | maxAudioPts: 1 * 10 * 1000, | ||
151 | uri: '0.ts' | ||
152 | }, { | ||
153 | minVideoPts: 1 * 10 * 1000 + 100, | ||
154 | minAudioPts: 1 * 10 * 1000 + 100, | ||
155 | maxVideoPts: 2 * 10 * 1000 + 100, | ||
156 | maxAudioPts: 2 * 10 * 1000 + 100, | ||
157 | duration: 10, | ||
158 | uri: '1.ts' | ||
159 | }] | ||
160 | }, 0, 1); | ||
161 | |||
162 | equal(duration, (1 * 10 * 1000 + 100) * 0.001, 'included the segment gap'); | ||
107 | }); | 163 | }); |
108 | 164 | ||
109 | test('interval duration accounts for discontinuities', function() { | 165 | test('interval duration accounts for discontinuities', function() { |
... | @@ -130,6 +186,53 @@ | ... | @@ -130,6 +186,53 @@ |
130 | equal(duration, 10 + 10, 'handles discontinuities'); | 186 | equal(duration, 10 + 10, 'handles discontinuities'); |
131 | }); | 187 | }); |
132 | 188 | ||
189 | test('interval duration does not count ending segment gaps across a discontinuity', function() { | ||
190 | var duration = Playlist.duration({ | ||
191 | mediaSequence: 0, | ||
192 | endList: true, | ||
193 | segments: [{ | ||
194 | minVideoPts: 0, | ||
195 | minAudioPts: 0, | ||
196 | maxVideoPts: 1 * 10 * 1000, | ||
197 | maxAudioPts: 1 * 10 * 1000, | ||
198 | uri: '0.ts' | ||
199 | }, { | ||
200 | discontinuity: true, | ||
201 | minVideoPts: 1 * 10 * 1000 + 100, | ||
202 | minAudioPts: 1 * 10 * 1000 + 100, | ||
203 | maxVideoPts: 2 * 10 * 1000 + 100, | ||
204 | maxAudioPts: 2 * 10 * 1000 + 100, | ||
205 | duration: 10, | ||
206 | uri: '1.ts' | ||
207 | }] | ||
208 | }, 0, 1); | ||
209 | |||
210 | equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap'); | ||
211 | }); | ||
212 | |||
213 | test('strict interval duration does not count ending segment gaps', function() { | ||
214 | var duration = Playlist.duration({ | ||
215 | mediaSequence: 0, | ||
216 | endList: true, | ||
217 | segments: [{ | ||
218 | minVideoPts: 0, | ||
219 | minAudioPts: 0, | ||
220 | maxVideoPts: 1 * 10 * 1000, | ||
221 | maxAudioPts: 1 * 10 * 1000, | ||
222 | uri: '0.ts' | ||
223 | }, { | ||
224 | minVideoPts: 1 * 10 * 1000 + 100, | ||
225 | minAudioPts: 1 * 10 * 1000 + 100, | ||
226 | maxVideoPts: 2 * 10 * 1000 + 100, | ||
227 | maxAudioPts: 2 * 10 * 1000 + 100, | ||
228 | duration: 10, | ||
229 | uri: '1.ts' | ||
230 | }] | ||
231 | }, 0, 1, true); | ||
232 | |||
233 | equal(duration, (1 * 10 * 1000) * 0.001, 'did not include the segment gap'); | ||
234 | }); | ||
235 | |||
133 | test('calculates seekable time ranges from the available segments', function() { | 236 | test('calculates seekable time ranges from the available segments', function() { |
134 | var playlist = { | 237 | var playlist = { |
135 | mediaSequence: 0, | 238 | mediaSequence: 0, | ... | ... |
... | @@ -97,7 +97,7 @@ var | ... | @@ -97,7 +97,7 @@ var |
97 | var MockSegmentParser; | 97 | var MockSegmentParser; |
98 | 98 | ||
99 | if (tags === undefined) { | 99 | if (tags === undefined) { |
100 | tags = []; | 100 | tags = [{ pts: 0, bytes: new Uint8Array(1) }]; |
101 | } | 101 | } |
102 | MockSegmentParser = function() { | 102 | MockSegmentParser = function() { |
103 | this.getFlvHeader = function() { | 103 | this.getFlvHeader = function() { |
... | @@ -1287,30 +1287,32 @@ test('clears in-band cues ahead of current time on seek', function() { | ... | @@ -1287,30 +1287,32 @@ test('clears in-band cues ahead of current time on seek', function() { |
1287 | 1287 | ||
1288 | player.hls.segmentParser_.parseSegmentBinaryData = function() { | 1288 | player.hls.segmentParser_.parseSegmentBinaryData = function() { |
1289 | // trigger a metadata event | 1289 | // trigger a metadata event |
1290 | if (events.length) { | 1290 | while (events.length) { |
1291 | player.hls.segmentParser_.metadataStream.trigger('data', events.shift()); | 1291 | player.hls.segmentParser_.metadataStream.trigger('data', events.shift()); |
1292 | } | 1292 | } |
1293 | }; | 1293 | }; |
1294 | standardXHRResponse(requests.shift()); // media | 1294 | standardXHRResponse(requests.shift()); // media |
1295 | tags.push({ pts: 10 * 1000, bytes: new Uint8Array(1) }); | 1295 | tags.push({ pts: 0, bytes: new Uint8Array(1) }, |
1296 | { pts: 10 * 1000, bytes: new Uint8Array(1) }); | ||
1296 | events.push({ | 1297 | events.push({ |
1297 | pts: 20 * 1000, | 1298 | pts: 9.9 * 1000, |
1298 | data: new Uint8Array([]), | 1299 | data: new Uint8Array([]), |
1299 | frames: [{ | 1300 | frames: [{ |
1300 | id: 'TXXX', | 1301 | id: 'TXXX', |
1301 | value: 'cue 3' | 1302 | value: 'cue 1' |
1302 | }] | 1303 | }] |
1303 | }); | 1304 | }); |
1304 | events.push({ | 1305 | events.push({ |
1305 | pts: 9.9 * 1000, | 1306 | pts: 20 * 1000, |
1306 | data: new Uint8Array([]), | 1307 | data: new Uint8Array([]), |
1307 | frames: [{ | 1308 | frames: [{ |
1308 | id: 'TXXX', | 1309 | id: 'TXXX', |
1309 | value: 'cue 1' | 1310 | value: 'cue 3' |
1310 | }] | 1311 | }] |
1311 | }); | 1312 | }); |
1312 | standardXHRResponse(requests.shift()); // segment 0 | 1313 | standardXHRResponse(requests.shift()); // segment 0 |
1313 | tags.push({ pts: 20 * 1000, bytes: new Uint8Array(1) }); | 1314 | tags.push({ pts: 10 * 1000 + 1, bytes: new Uint8Array(1) }, |
1315 | { pts: 20 * 1000, bytes: new Uint8Array(1) }); | ||
1314 | events.push({ | 1316 | events.push({ |
1315 | pts: 19.9 * 1000, | 1317 | pts: 19.9 * 1000, |
1316 | data: new Uint8Array([]), | 1318 | data: new Uint8Array([]), |
... | @@ -1323,12 +1325,12 @@ test('clears in-band cues ahead of current time on seek', function() { | ... | @@ -1323,12 +1325,12 @@ test('clears in-band cues ahead of current time on seek', function() { |
1323 | standardXHRResponse(requests.shift()); // segment 1 | 1325 | standardXHRResponse(requests.shift()); // segment 1 |
1324 | 1326 | ||
1325 | track = player.textTracks()[0]; | 1327 | track = player.textTracks()[0]; |
1326 | equal(track.cues.length, 2, 'added the cues'); | 1328 | equal(track.cues.length, 3, 'added the cues'); |
1327 | 1329 | ||
1328 | // seek into segment 1 | 1330 | // seek into segment 1 |
1329 | player.currentTime(11); | 1331 | player.currentTime(11); |
1330 | player.trigger('seeking'); | 1332 | player.trigger('seeking'); |
1331 | equal(track.cues.length, 1, 'removed a cue'); | 1333 | equal(track.cues.length, 1, 'removed later cues'); |
1332 | equal(track.cues[0].startTime, 9.9, 'retained the earlier cue'); | 1334 | equal(track.cues[0].startTime, 9.9, 'retained the earlier cue'); |
1333 | }); | 1335 | }); |
1334 | 1336 | ||
... | @@ -2010,6 +2012,48 @@ test('continues playing after seek to discontinuity', function() { | ... | @@ -2010,6 +2012,48 @@ test('continues playing after seek to discontinuity', function() { |
2010 | strictEqual(aborts, 1, 'cleared the segment buffer on a seek'); | 2012 | strictEqual(aborts, 1, 'cleared the segment buffer on a seek'); |
2011 | }); | 2013 | }); |
2012 | 2014 | ||
2015 | test('seeking does not fail when targeted between segments', function() { | ||
2016 | var tags = [], currentTime, segmentUrl; | ||
2017 | videojs.Hls.SegmentParser = mockSegmentParser(tags); | ||
2018 | player.src({ | ||
2019 | src: 'media.m3u8', | ||
2020 | type: 'application/vnd.apple.mpegurl' | ||
2021 | }); | ||
2022 | openMediaSource(player); | ||
2023 | |||
2024 | // mock out the currentTime callbacks | ||
2025 | player.hls.el().vjs_setProperty = function(property, value) { | ||
2026 | if (property === 'currentTime') { | ||
2027 | currentTime = value; | ||
2028 | } | ||
2029 | }; | ||
2030 | player.hls.el().vjs_getProperty = function(property) { | ||
2031 | if (property === 'currentTime') { | ||
2032 | return currentTime; | ||
2033 | } | ||
2034 | }; | ||
2035 | |||
2036 | standardXHRResponse(requests.shift()); // media | ||
2037 | tags.push({ pts: 100, bytes: new Uint8Array(1) }, | ||
2038 | { pts: 9 * 1000 + 100, bytes: new Uint8Array(1) }); | ||
2039 | standardXHRResponse(requests.shift()); // segment 0 | ||
2040 | player.hls.checkBuffer_(); | ||
2041 | tags.push({ pts: 9.5 * 1000 + 100, bytes: new Uint8Array(1) }, | ||
2042 | { pts: 20 * 1000 + 100, bytes: new Uint8Array(1) }); | ||
2043 | segmentUrl = requests[0].url; | ||
2044 | standardXHRResponse(requests.shift()); // segment 1 | ||
2045 | |||
2046 | // seek to a time that is greater than the last tag in segment 0 but | ||
2047 | // less than the first in segment 1 | ||
2048 | player.currentTime(9.4); | ||
2049 | equal(requests[0].url, segmentUrl, 'requested the later segment'); | ||
2050 | |||
2051 | tags.push({ pts: 9.5 * 1000 + 100, bytes: new Uint8Array(1) }, | ||
2052 | { pts: 20 * 1000 + 100, bytes: new Uint8Array(1) }); | ||
2053 | standardXHRResponse(requests.shift()); // segment 1 | ||
2054 | equal(player.currentTime(), 9.5, 'seeked to the later time'); | ||
2055 | }); | ||
2056 | |||
2013 | test('resets the switching algorithm if a request times out', function() { | 2057 | test('resets the switching algorithm if a request times out', function() { |
2014 | player.src({ | 2058 | player.src({ |
2015 | src: 'master.m3u8', | 2059 | src: 'master.m3u8', | ... | ... |
-
Please register or sign in to post a comment