3e508892 by David LaPalomento

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.
1 parent f51b1039
...@@ -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',
......