bce88a18 by jrivera Committed by David LaPalomento

buffer at the current time range end instead of incrementing a variable. closes #423

1 parent c7d5b626
...@@ -2,7 +2,7 @@ CHANGELOG ...@@ -2,7 +2,7 @@ CHANGELOG
2 ========= 2 =========
3 3
4 ## HEAD (Unreleased) 4 ## HEAD (Unreleased)
5 _(none)_ 5 * buffer at the current time range end instead of incrementing a variable. ([view](https://github.com/videojs/videojs-contrib-hls/pull/423))
6 6
7 -------------------- 7 --------------------
8 8
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
44 "karma-sauce-launcher": "~0.1.8", 44 "karma-sauce-launcher": "~0.1.8",
45 "qunitjs": "^1.18.0", 45 "qunitjs": "^1.18.0",
46 "sinon": "1.10.2", 46 "sinon": "1.10.2",
47 "video.js": "^5.0.0-rc.96" 47 "video.js": "^5.1.0"
48 }, 48 },
49 "dependencies": { 49 "dependencies": {
50 "pkcs7": "^0.2.2", 50 "pkcs7": "^0.2.2",
......
...@@ -367,7 +367,17 @@ ...@@ -367,7 +367,17 @@
367 * closest playback position that is currently available. 367 * closest playback position that is currently available.
368 */ 368 */
369 PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) { 369 PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) {
370 var i, j, segment, targetDuration; 370 var
371 i,
372 segment,
373 originalTime = time,
374 targetDuration = this.media_.targetDuration || 10,
375 numSegments = this.media_.segments.length,
376 lastSegment = numSegments - 1,
377 startIndex,
378 endIndex,
379 knownStart,
380 knownEnd;
371 381
372 if (!this.media_) { 382 if (!this.media_) {
373 return 0; 383 return 0;
...@@ -379,57 +389,105 @@ ...@@ -379,57 +389,105 @@
379 return 0; 389 return 0;
380 } 390 }
381 391
382 // 1) Walk backward until we find the latest segment with timeline 392 // 1) Walk backward until we find the first segment with timeline
383 // information that is earlier than `time` 393 // information that is earlier than `time`
384 targetDuration = this.media_.targetDuration || 10; 394 for (i = lastSegment; i >= 0; i--) {
385 i = this.media_.segments.length;
386 while (i--) {
387 segment = this.media_.segments[i]; 395 segment = this.media_.segments[i];
388 if (segment.end !== undefined && segment.end <= time) { 396 if (segment.end !== undefined && segment.end <= time) {
389 time -= segment.end; 397 startIndex = i + 1;
398 knownStart = segment.end;
399 if (startIndex >= numSegments) {
400 // The last segment claims to end *before* the time we are
401 // searching for so just return it
402 return numSegments;
403 }
390 break; 404 break;
391 } 405 }
392 if (segment.start !== undefined && segment.start < time) { 406 if (segment.start !== undefined && segment.start <= time) {
393
394 if (segment.end !== undefined && segment.end > time) { 407 if (segment.end !== undefined && segment.end > time) {
395 // we've found the target segment exactly 408 // we've found the target segment exactly
396 return i; 409 return i;
397 } 410 }
411 startIndex = i;
412 knownStart = segment.start;
413 break;
414 }
415 }
416
417 // 2) Walk forward until we find the first segment with timeline
418 // information that is greater than `time`
419 for (i = 0; i < numSegments; i++) {
420 segment = this.media_.segments[i];
421 if (segment.start !== undefined && segment.start > time) {
422 endIndex = i - 1;
423 knownEnd = segment.start;
424 if (endIndex < 0) {
425 // The first segment claims to start *after* the time we are
426 // searching for so just return it
427 return -1;
428 }
429 break;
430 }
431 if (segment.end !== undefined && segment.end > time) {
432 endIndex = i;
433 knownEnd = segment.end;
434 break;
435 }
436 }
398 437
399 time -= segment.start; 438 if (startIndex !== undefined) {
439 // We have a known-start point that is before our desired time so
440 // walk from that point forwards
441 time = time - knownStart;
442 for (i = startIndex; i < (endIndex || numSegments); i++) {
443 segment = this.media_.segments[i];
400 time -= segment.duration || targetDuration; 444 time -= segment.duration || targetDuration;
401 if (time < 0) { 445 if (time < 0) {
402 // the segment with start information is also our best guess
403 // for the momment
404 return i; 446 return i;
405 } 447 }
406 break;
407 } 448 }
449
450 if (i === endIndex) {
451 // We haven't found a segment but we did hit a known end point
452 // so fallback to "Algorithm Jon" - try to interpolate the segment
453 // index based on the known span of the timeline we are dealing with
454 // and the number of segments inside that span
455 return startIndex + Math.floor(
456 ((originalTime - knownStart) / (knownEnd - knownStart)) *
457 (endIndex - startIndex));
408 } 458 }
409 i++;
410 459
411 // 2) Walk forward, testing each segment to see if `time` falls within it 460 // We _still_ haven't found a segment so load the last one
412 for (j = i; j < this.media_.segments.length; j++) { 461 return lastSegment;
413 segment = this.media_.segments[j]; 462 } else if (endIndex !== undefined) {
463 // We _only_ have a known-end point that is after our desired time so
464 // walk from that point backwards
465 time = knownEnd - time;
466 for (i = endIndex; i >= 0; i--) {
467 segment = this.media_.segments[i];
414 time -= segment.duration || targetDuration; 468 time -= segment.duration || targetDuration;
415
416 if (time < 0) { 469 if (time < 0) {
417 return j; 470 return i;
418 } 471 }
419 472 }
420 // 2a) If we discover a segment that has timeline information 473 // We haven't found a segment so load the first one
421 // before finding the result segment, the playlist information 474 return 0;
422 // must have been inaccurate. Start a binary search for the 475 } else {
423 // segment which contains `time`. If the guess turns out to be 476 // We known nothing so use "Algorithm A" - walk from the front
424 // incorrect, we'll have more info to work with next time. 477 // of the playlist naively subtracking durations until we find
425 if (segment.start !== undefined || segment.end !== undefined) { 478 // a segment that contains time and return it
426 return Math.floor((j - i) * 0.5); 479 for (i = 0; i < numSegments; i++) {
480 segment = this.media_.segments[i];
481 time -= segment.duration || targetDuration;
482 if (time < 0) {
483 return i;
427 } 484 }
428 } 485 }
429 486 // We are out of possible candidates so load the last one...
430 // the playback position is outside the range of available 487 // The last one is the least likely to overlap a buffer and therefore
431 // segments so return the length 488 // the one most likely to tell us something about the timeline
432 return this.media_.segments.length; 489 return lastSegment;
490 }
433 }; 491 };
434 492
435 videojs.Hls.PlaylistLoader = PlaylistLoader; 493 videojs.Hls.PlaylistLoader = PlaylistLoader;
......
...@@ -304,13 +304,26 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { ...@@ -304,13 +304,26 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
304 // transition the sourcebuffer to the ended state if we've hit the end of 304 // transition the sourcebuffer to the ended state if we've hit the end of
305 // the playlist 305 // the playlist
306 this.sourceBuffer.addEventListener('updateend', function() { 306 this.sourceBuffer.addEventListener('updateend', function() {
307 var segmentInfo = this.pendingSegment_, segment, currentBuffered, timelineUpdates; 307 var
308 segmentInfo = this.pendingSegment_,
309 segment,
310 playlist,
311 currentMediaIndex,
312 currentBuffered,
313 timelineUpdates;
314
315 // stop here if the update errored or was aborted
316 if (!segmentInfo) {
317 return;
318 }
308 319
309 this.pendingSegment_ = null; 320 this.pendingSegment_ = null;
310 321
311 // if we've buffered to the end of the video, let the MediaSource know 322 // if we've buffered to the end of the video, let the MediaSource know
312 currentBuffered = this.findCurrentBuffered_(); 323 currentBuffered = this.findCurrentBuffered_();
313 if (currentBuffered.length && this.duration() === currentBuffered.end(0)) { 324 if (currentBuffered.length &&
325 this.duration() === currentBuffered.end(0) &&
326 this.mediaSource.readyState === 'open') {
314 this.mediaSource.endOfStream(); 327 this.mediaSource.endOfStream();
315 } 328 }
316 329
...@@ -319,26 +332,46 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { ...@@ -319,26 +332,46 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
319 return; 332 return;
320 } 333 }
321 334
335 // if we switched renditions don't try to add segment timeline
336 // information to the playlist
337 if (segmentInfo.playlist.uri !== this.playlists.media().uri) {
338 return this.fillBuffer();
339 }
340
341 playlist = this.playlists.media();
342 currentMediaIndex = segmentInfo.mediaIndex + (segmentInfo.mediaSequence - playlist.mediaSequence);
343
322 // annotate the segment with any start and end time information 344 // annotate the segment with any start and end time information
323 // added by the media processing 345 // added by the media processing
324 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 346 segment = playlist.segments[currentMediaIndex];
347
325 timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered, 348 timelineUpdates = videojs.Hls.bufferedAdditions_(segmentInfo.buffered,
326 this.tech_.buffered()); 349 this.tech_.buffered());
327 timelineUpdates.forEach(function(update) { 350
351 timelineUpdates.forEach(function (update) {
352 if (segment) {
328 if (update.start !== undefined) { 353 if (update.start !== undefined) {
329 segment.start = update.start; 354 segment.start = update.start;
330 } 355 }
331 if (update.end !== undefined) { 356 if (update.end !== undefined) {
332 segment.end = update.end; 357 segment.end = update.end;
333 } 358 }
359 }
334 }); 360 });
335 361
336 if (timelineUpdates.length) { 362 if (timelineUpdates.length) {
337 this.updateDuration(segmentInfo.playlist); 363 this.updateDuration(playlist);
364 // check if it's time to download the next segment
365 this.fillBuffer();
366 return;
338 } 367 }
339 368
340 // check if it's time to download the next segment 369 // the last segment append must have been entirely in the
341 this.checkBuffer_(); 370 // already buffered time ranges. just buffer forward until we
371 // find a segment that adds to the buffered time ranges and
372 // improves subsequent media index calculations.
373 this.fillBuffer(currentMediaIndex + 1);
374 return;
342 }.bind(this)); 375 }.bind(this));
343 }; 376 };
344 377
...@@ -426,11 +459,8 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { ...@@ -426,11 +459,8 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
426 this.cancelKeyXhr(); 459 this.cancelKeyXhr();
427 } 460 }
428 461
429 // clear out the segment being processed
430 this.pendingSegment_ = null;
431
432 // begin filling the buffer at the new position 462 // begin filling the buffer at the new position
433 this.fillBuffer(currentTime); 463 this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime));
434 }; 464 };
435 465
436 videojs.Hls.prototype.duration = function() { 466 videojs.Hls.prototype.duration = function() {
...@@ -465,15 +495,25 @@ videojs.Hls.prototype.updateDuration = function(playlist) { ...@@ -465,15 +495,25 @@ videojs.Hls.prototype.updateDuration = function(playlist) {
465 this.mediaSource.duration = newDuration; 495 this.mediaSource.duration = newDuration;
466 this.tech_.trigger('durationchange'); 496 this.tech_.trigger('durationchange');
467 this.mediaSource.removeEventListener('sourceopen', setDuration); 497 this.mediaSource.removeEventListener('sourceopen', setDuration);
468 }.bind(this); 498 }.bind(this),
499 seekable = this.seekable();
500
501 // TODO: Move to videojs-contrib-media-sources
502 if (seekable.length && newDuration === Infinity) {
503 if (isNaN(oldDuration)) {
504 oldDuration = 0;
505 }
506 newDuration = Math.max(oldDuration,
507 seekable.end(0) + playlist.targetDuration * 3);
508 }
469 509
470 // if the duration has changed, invalidate the cached value 510 // if the duration has changed, invalidate the cached value
471 if (oldDuration !== newDuration) { 511 if (oldDuration !== newDuration) {
472 if (this.mediaSource.readyState === 'open') { 512 if (this.mediaSource.readyState !== 'open') {
513 this.mediaSource.addEventListener('sourceopen', setDuration);
514 } else if (!this.sourceBuffer || !this.sourceBuffer.updating) {
473 this.mediaSource.duration = newDuration; 515 this.mediaSource.duration = newDuration;
474 this.tech_.trigger('durationchange'); 516 this.tech_.trigger('durationchange');
475 } else {
476 this.mediaSource.addEventListener('sourceopen', setDuration);
477 } 517 }
478 } 518 }
479 }; 519 };
...@@ -507,6 +547,8 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { ...@@ -507,6 +547,8 @@ videojs.Hls.prototype.cancelSegmentXhr = function() {
507 this.segmentXhr_.abort(); 547 this.segmentXhr_.abort();
508 this.segmentXhr_ = null; 548 this.segmentXhr_ = null;
509 } 549 }
550 // clear out the segment being processed
551 this.pendingSegment_ = null;
510 }; 552 };
511 553
512 /** 554 /**
...@@ -667,11 +709,17 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() { ...@@ -667,11 +709,17 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() {
667 */ 709 */
668 videojs.Hls.prototype.findCurrentBuffered_ = function() { 710 videojs.Hls.prototype.findCurrentBuffered_ = function() {
669 var 711 var
670 tech = this.tech_,
671 currentTime = tech.currentTime(),
672 buffered = this.tech_.buffered(),
673 ranges, 712 ranges,
674 i; 713 i,
714 tech = this.tech_,
715 // !!The order of the next two lines is important!!
716 // `currentTime` must be equal-to or greater-than the start of the
717 // buffered range. Flash executes out-of-process so, every value can
718 // change behind the scenes from line-to-line. By reading `currentTime`
719 // after `buffered`, we ensure that it is always a current or later
720 // value during playback.
721 buffered = tech.buffered(),
722 currentTime = tech.currentTime();
675 723
676 if (buffered && buffered.length) { 724 if (buffered && buffered.length) {
677 // Search for a range containing the play-head 725 // Search for a range containing the play-head
...@@ -697,13 +745,13 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { ...@@ -697,13 +745,13 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() {
697 * @param seekToTime (optional) {number} the offset into the downloaded segment 745 * @param seekToTime (optional) {number} the offset into the downloaded segment
698 * to seek to, in seconds 746 * to seek to, in seconds
699 */ 747 */
700 videojs.Hls.prototype.fillBuffer = function(seekToTime) { 748 videojs.Hls.prototype.fillBuffer = function(mediaIndex) {
701 var 749 var
702 tech = this.tech_, 750 tech = this.tech_,
703 currentTime = tech.currentTime(), 751 currentTime = tech.currentTime(),
704 currentBuffered = this.findCurrentBuffered_(), 752 currentBuffered = this.findCurrentBuffered_(),
753 currentBufferedEnd = 0,
705 bufferedTime = 0, 754 bufferedTime = 0,
706 mediaIndex = 0,
707 segment, 755 segment,
708 segmentInfo; 756 segmentInfo;
709 757
...@@ -739,39 +787,46 @@ videojs.Hls.prototype.fillBuffer = function(seekToTime) { ...@@ -739,39 +787,46 @@ videojs.Hls.prototype.fillBuffer = function(seekToTime) {
739 return; 787 return;
740 } 788 }
741 789
742 // find the next segment to download 790 if (mediaIndex === undefined) {
743 if (typeof seekToTime === 'number') { 791 if (currentBuffered && currentBuffered.length) {
744 mediaIndex = this.playlists.getMediaIndexForTime_(seekToTime); 792 currentBufferedEnd = currentBuffered.end(0);
745 } else if (currentBuffered && currentBuffered.length) { 793 mediaIndex = this.playlists.getMediaIndexForTime_(currentBufferedEnd);
746 mediaIndex = this.playlists.getMediaIndexForTime_(currentBuffered.end(0)); 794 bufferedTime = Math.max(0, currentBufferedEnd - currentTime);
747 bufferedTime = Math.max(0, currentBuffered.end(0) - currentTime); 795
796 // if there is plenty of content in the buffer and we're not
797 // seeking, relax for awhile
798 if (bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) {
799 return;
800 }
748 } else { 801 } else {
749 mediaIndex = this.playlists.getMediaIndexForTime_(this.tech_.currentTime()); 802 mediaIndex = this.playlists.getMediaIndexForTime_(this.tech_.currentTime());
750 } 803 }
804 }
751 segment = this.playlists.media().segments[mediaIndex]; 805 segment = this.playlists.media().segments[mediaIndex];
752 806
753 // if the video has finished downloading, stop trying to buffer 807 // if the video has finished downloading
754 if (!segment) { 808 if (!segment) {
755 return; 809 return;
756 } 810 }
757 811
758 // if there is plenty of content in the buffer and we're not 812 // we have entered a state where we are fetching the same segment,
759 // seeking, relax for awhile 813 // try to walk forward
760 if (typeof seekToTime !== 'number' && 814 if (this.lastSegmentLoaded_ &&
761 bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) { 815 this.lastSegmentLoaded_ === this.playlistUriToUrl(segment.uri)) {
762 return; 816 return this.fillBuffer(mediaIndex + 1);
763 } 817 }
764 818
765 // package up all the work to append the segment 819 // package up all the work to append the segment
766 segmentInfo = { 820 segmentInfo = {
767 // resolve the segment URL relative to the playlist 821 // resolve the segment URL relative to the playlist
768 uri: this.playlistUriToUrl(segment.uri), 822 uri: this.playlistUriToUrl(segment.uri),
769 // the segment's mediaIndex at the time it was received 823 // the segment's mediaIndex & mediaSequence at the time it was requested
770 mediaIndex: mediaIndex, 824 mediaIndex: mediaIndex,
825 mediaSequence: this.playlists.media().mediaSequence,
771 // the segment's playlist 826 // the segment's playlist
772 playlist: this.playlists.media(), 827 playlist: this.playlists.media(),
773 // optionally, a time offset to seek to within the segment 828 // The state of the buffer when this segment was requested
774 offset: seekToTime, 829 currentBufferedEnd: currentBufferedEnd,
775 // unencrypted bytes of the segment 830 // unencrypted bytes of the segment
776 bytes: null, 831 bytes: null,
777 // when a key is defined for this segment, the encrypted bytes 832 // when a key is defined for this segment, the encrypted bytes
...@@ -856,6 +911,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) { ...@@ -856,6 +911,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) {
856 return; 911 return;
857 } 912 }
858 913
914 self.lastSegmentLoaded_ = segmentInfo.uri;
859 self.setBandwidth(request); 915 self.setBandwidth(request);
860 916
861 if (segment.key) { 917 if (segment.key) {
...@@ -944,41 +1000,33 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -944,41 +1000,33 @@ videojs.Hls.prototype.drainBuffer = function(event) {
944 1000
945 event = event || {}; 1001 event = event || {};
946 1002
1003 if (segmentInfo.mediaIndex > 0) {
1004 segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
1005 playlist.mediaSequence + segmentInfo.mediaIndex);
1006 }
1007
947 // If we have seeked into a non-buffered time-range, remove all buffered 1008 // If we have seeked into a non-buffered time-range, remove all buffered
948 // time-ranges because they could have been incorrectly placed originally 1009 // time-ranges because they could have been incorrectly placed originally
949 if (this.tech_.seeking() && outsideBufferedRanges) { 1010 if (this.tech_.seeking() && outsideBufferedRanges) {
950 if (hasBufferedContent) {
951 // In Chrome, it seems that too many independent buffered time-ranges can
952 // cause playback to fail to resume when seeking so just kill all of them
953 this.sourceBuffer.remove(0, Infinity);
954 return;
955 }
956
957 // If there are discontinuities in the playlist, we can't be sure of anything 1011 // If there are discontinuities in the playlist, we can't be sure of anything
958 // related to time so we reset the timestamp offset and start appending data 1012 // related to time so we reset the timestamp offset and start appending data
959 // anew on every seek 1013 // anew on every seek
960 if (segmentInfo.playlist.discontinuityStarts.length) { 1014 if (segmentInfo.playlist.discontinuityStarts.length) {
961 if (segmentInfo.mediaIndex > 0) {
962 segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist, segmentInfo.mediaIndex);
963 }
964
965 // Now that the forward buffer is clear, we have to set timestamp offset to 1015 // Now that the forward buffer is clear, we have to set timestamp offset to
966 // the start of the buffered region 1016 // the start of the buffered region
967 this.sourceBuffer.timestampOffset = segmentTimestampOffset; 1017 this.sourceBuffer.timestampOffset = segmentTimestampOffset;
968 } 1018 }
969 } else if (segment.discontinuity) { 1019 } else if (segment.discontinuity && currentBuffered.length) {
970 // If we aren't seeking and are crossing a discontinuity, we should set 1020 // If we aren't seeking and are crossing a discontinuity, we should set
971 // timestampOffset for new segments to be appended the end of the current 1021 // timestampOffset for new segments to be appended the end of the current
972 // buffered time-range 1022 // buffered time-range
973 this.sourceBuffer.timestampOffset = currentBuffered.end(0); 1023 this.sourceBuffer.timestampOffset = currentBuffered.end(0);
1024 } else if (!hasBufferedContent && this.tech_.currentTime() > 0.05) {
1025 // If we are trying to play at a position that is not zero but we aren't
1026 // currently seeking according to the video element
1027 this.sourceBuffer.timestampOffset = segmentTimestampOffset;
974 } 1028 }
975 1029
976 if (currentBuffered.length) {
977 // Chrome 45 stalls if appends overlap the playhead
978 this.sourceBuffer.appendWindowStart = Math.min(this.tech_.currentTime(), currentBuffered.end(0));
979 } else {
980 this.sourceBuffer.appendWindowStart = 0;
981 }
982 this.pendingSegment_.buffered = this.tech_.buffered(); 1030 this.pendingSegment_.buffered = this.tech_.buffered();
983 1031
984 // the segment is asynchronously added to the current buffered data 1032 // the segment is asynchronously added to the current buffered data
......
...@@ -653,8 +653,8 @@ ...@@ -653,8 +653,8 @@
653 equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero'); 653 equal(loader.getMediaIndexForTime_(3), 0, 'time three is index zero');
654 equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2'); 654 equal(loader.getMediaIndexForTime_(10), 2, 'time 10 is index 2');
655 equal(loader.getMediaIndexForTime_(22), 655 equal(loader.getMediaIndexForTime_(22),
656 3, 656 2,
657 'time greater than the length is index 3'); 657 'time greater than the length is index 2');
658 }); 658 });
659 659
660 test('returns the lower index when calculating for a segment boundary', function() { 660 test('returns the lower index when calculating for a segment boundary', function() {
...@@ -683,9 +683,9 @@ ...@@ -683,9 +683,9 @@
683 '1002.ts\n'); 683 '1002.ts\n');
684 loader.media().segments[0].start = 150; 684 loader.media().segments[0].start = 150;
685 685
686 equal(loader.getMediaIndexForTime_(0), 0, 'the lowest returned value is zero'); 686 equal(loader.getMediaIndexForTime_(0), -1, 'the lowest returned value is negative one');
687 equal(loader.getMediaIndexForTime_(45), 0, 'expired content returns zero'); 687 equal(loader.getMediaIndexForTime_(45), -1, 'expired content returns negative one');
688 equal(loader.getMediaIndexForTime_(75), 0, 'expired content returns zero'); 688 equal(loader.getMediaIndexForTime_(75), -1, 'expired content returns negative one');
689 equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position'); 689 equal(loader.getMediaIndexForTime_(50 + 100), 0, 'calculates the earliest available position');
690 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment'); 690 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
691 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment'); 691 equal(loader.getMediaIndexForTime_(50 + 100 + 2), 0, 'calculates within the first segment');
......
...@@ -218,6 +218,7 @@ module('HLS', { ...@@ -218,6 +218,7 @@ module('HLS', {
218 var el = document.createElement('div'); 218 var el = document.createElement('div');
219 el.id = 'vjs_mock_flash_' + nextId++; 219 el.id = 'vjs_mock_flash_' + nextId++;
220 el.className = 'vjs-tech vjs-mock-flash'; 220 el.className = 'vjs-tech vjs-mock-flash';
221 el.duration = Infinity;
221 el.vjs_load = function() {}; 222 el.vjs_load = function() {};
222 el.vjs_getProperty = function(attr) { 223 el.vjs_getProperty = function(attr) {
223 if (attr === 'buffered') { 224 if (attr === 'buffered') {
...@@ -1131,7 +1132,7 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function ...@@ -1131,7 +1132,7 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
1131 return videojs.createTimeRange(buffered); 1132 return videojs.createTimeRange(buffered);
1132 }; 1133 };
1133 currentTime = 8; 1134 currentTime = 8;
1134 buffered = [[0, 10], [20, 40]]; 1135 buffered = [[0, 10], [20, 30]];
1135 1136
1136 standardXHRResponse(requests[0]); 1137 standardXHRResponse(requests[0]);
1137 standardXHRResponse(requests[1]); 1138 standardXHRResponse(requests[1]);
...@@ -1144,14 +1145,9 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function ...@@ -1144,14 +1145,9 @@ test('buffers based on the correct TimeRange if multiple ranges exist', function
1144 currentTime = 22; 1145 currentTime = 22;
1145 player.tech_.hls.sourceBuffer.trigger('updateend'); 1146 player.tech_.hls.sourceBuffer.trigger('updateend');
1146 player.tech_.hls.checkBuffer_(); 1147 player.tech_.hls.checkBuffer_();
1147 strictEqual(requests.length, 2, 'made no additional requests');
1148
1149 buffered = [[0, 10], [20, 30]];
1150 player.tech_.hls.checkBuffer_();
1151 standardXHRResponse(requests[2]);
1152 strictEqual(requests.length, 3, 'made three requests'); 1148 strictEqual(requests.length, 3, 'made three requests');
1153 strictEqual(requests[2].url, 1149 strictEqual(requests[2].url,
1154 absoluteUrl('manifest/media-00004.ts'), 1150 absoluteUrl('manifest/media-00003.ts'),
1155 'made segment request'); 1151 'made segment request');
1156 }); 1152 });
1157 1153
...@@ -1381,7 +1377,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() { ...@@ -1381,7 +1377,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() {
1381 equal(requests.length, requestsLength, 'made no additional requests'); 1377 equal(requests.length, requestsLength, 'made no additional requests');
1382 }); 1378 });
1383 1379
1384 test('duration is Infinity for live playlists', function() { 1380 test('tech\'s duration reports Infinity for live playlists', function() {
1385 player.src({ 1381 player.src({
1386 src: 'http://example.com/manifest/missingEndlist.m3u8', 1382 src: 'http://example.com/manifest/missingEndlist.m3u8',
1387 type: 'application/vnd.apple.mpegurl' 1383 type: 'application/vnd.apple.mpegurl'
...@@ -1390,9 +1386,13 @@ test('duration is Infinity for live playlists', function() { ...@@ -1390,9 +1386,13 @@ test('duration is Infinity for live playlists', function() {
1390 1386
1391 standardXHRResponse(requests[0]); 1387 standardXHRResponse(requests[0]);
1392 1388
1393 strictEqual(player.tech_.hls.mediaSource.duration, 1389 strictEqual(player.tech_.duration(),
1390 Infinity,
1391 'duration on the tech is infinity');
1392
1393 notEqual(player.tech_.hls.mediaSource.duration,
1394 Infinity, 1394 Infinity,
1395 'duration is infinity'); 1395 'duration on the mediaSource is not infinity');
1396 }); 1396 });
1397 1397
1398 test('live playlist starts three target durations before live', function() { 1398 test('live playlist starts three target durations before live', function() {
...@@ -1644,7 +1644,7 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() { ...@@ -1644,7 +1644,7 @@ test('calls mediaSource\'s timestampOffset on discontinuity', function() {
1644 }); 1644 });
1645 1645
1646 test('sets timestampOffset when seeking with discontinuities', function() { 1646 test('sets timestampOffset when seeking with discontinuities', function() {
1647 var removes = [], timeRange = videojs.createTimeRange(0, 10); 1647 var timeRange = videojs.createTimeRange(0, 10);
1648 1648
1649 player.src({ 1649 player.src({
1650 src: 'discontinuity.m3u8', 1650 src: 'discontinuity.m3u8',
...@@ -1670,18 +1670,12 @@ test('sets timestampOffset when seeking with discontinuities', function() { ...@@ -1670,18 +1670,12 @@ test('sets timestampOffset when seeking with discontinuities', function() {
1670 '3.ts\n' + 1670 '3.ts\n' +
1671 '#EXT-X-ENDLIST\n'); 1671 '#EXT-X-ENDLIST\n');
1672 player.tech_.hls.sourceBuffer.timestampOffset = 0; 1672 player.tech_.hls.sourceBuffer.timestampOffset = 0;
1673 player.tech_.hls.sourceBuffer.remove = function(start, end) {
1674 timeRange = videojs.createTimeRange();
1675 removes.push([start, end]);
1676 };
1677
1678 player.currentTime(21); 1673 player.currentTime(21);
1679 clock.tick(1); 1674 clock.tick(1);
1680 equal(requests.shift().aborted, true, 'aborted first request'); 1675 equal(requests.shift().aborted, true, 'aborted first request');
1681 standardXHRResponse(requests.pop()); // 3.ts 1676 standardXHRResponse(requests.pop()); // 3.ts
1682 clock.tick(1000); 1677 clock.tick(1000);
1683 equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero'); 1678 equal(player.tech_.hls.sourceBuffer.timestampOffset, 20, 'timestampOffset starts at zero');
1684 equal(removes.length, 1, 'remove was called');
1685 }); 1679 });
1686 1680
1687 test('can seek before the source buffer opens', function() { 1681 test('can seek before the source buffer opens', function() {
......