1551d9c4 by David LaPalomento

Merge pull request #432 from dmlap/drain-race

Don't append segments that have already been appended
2 parents eb5c0c61 2270048a
...@@ -9,21 +9,8 @@ ...@@ -9,21 +9,8 @@
9 <!-- video.js --> 9 <!-- video.js -->
10 <script src="node_modules/video.js/dist/video.js"></script> 10 <script src="node_modules/video.js/dist/video.js"></script>
11 11
12 <!-- transmuxing -->
13 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/stream.js"></script>
14 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/flv-tag.js"></script>
15 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/exp-golomb.js"></script>
16 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/h264-extradata.js"></script>
17 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/h264-stream.js"></script>
18 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/aac-stream.js"></script>
19 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/metadata-stream.js"></script>
20 <script src="node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/segment-parser.js"></script>
21
22 <!-- Media Sources plugin --> 12 <!-- Media Sources plugin -->
23 <script src="node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> 13 <script src="node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
24 <script>
25 videojs.MediaSource.webWorkerURI = 'node_modules/videojs-contrib-media-sources/src/transmuxer_worker.js';
26 </script>
27 14
28 <!-- HLS plugin --> 15 <!-- HLS plugin -->
29 <script src="src/videojs-hls.js"></script> 16 <script src="src/videojs-hls.js"></script>
......
...@@ -44,11 +44,11 @@ ...@@ -44,11 +44,11 @@
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.1.0" 47 "video.js": "^5.2.1"
48 }, 48 },
49 "dependencies": { 49 "dependencies": {
50 "pkcs7": "^0.2.2", 50 "pkcs7": "^0.2.2",
51 "videojs-contrib-media-sources": "^2.0.0", 51 "videojs-contrib-media-sources": "^2.4.0",
52 "videojs-swf": "^5.0.0" 52 "videojs-swf": "^5.0.0"
53 } 53 }
54 } 54 }
......
...@@ -23,7 +23,8 @@ keyFailed = function(key) { ...@@ -23,7 +23,8 @@ keyFailed = function(key) {
23 return key.retries && key.retries >= 2; 23 return key.retries && key.retries >= 2;
24 }; 24 };
25 25
26 videojs.Hls = videojs.extend(Component, { 26 videojs.Hls = {};
27 videojs.HlsHandler = videojs.extend(Component, {
27 constructor: function(tech, options) { 28 constructor: function(tech, options) {
28 var self = this, _player; 29 var self = this, _player;
29 30
...@@ -110,7 +111,7 @@ videojs.HlsSourceHandler = function(mode) { ...@@ -110,7 +111,7 @@ videojs.HlsSourceHandler = function(mode) {
110 tech.trigger('loadstart'); 111 tech.trigger('loadstart');
111 }, 1); 112 }, 1);
112 } 113 }
113 tech.hls = new videojs.Hls(tech, { 114 tech.hls = new videojs.HlsHandler(tech, {
114 source: source, 115 source: source,
115 mode: mode 116 mode: mode
116 }); 117 });
...@@ -129,7 +130,7 @@ videojs.getComponent('Flash').registerSourceHandler(videojs.HlsSourceHandler('fl ...@@ -129,7 +130,7 @@ videojs.getComponent('Flash').registerSourceHandler(videojs.HlsSourceHandler('fl
129 // the desired length of video to maintain in the buffer, in seconds 130 // the desired length of video to maintain in the buffer, in seconds
130 videojs.Hls.GOAL_BUFFER_LENGTH = 30; 131 videojs.Hls.GOAL_BUFFER_LENGTH = 30;
131 132
132 videojs.Hls.prototype.src = function(src) { 133 videojs.HlsHandler.prototype.src = function(src) {
133 var oldMediaPlaylist; 134 var oldMediaPlaylist;
134 135
135 // do nothing if the src is falsey 136 // do nothing if the src is falsey
...@@ -208,7 +209,7 @@ videojs.Hls.prototype.src = function(src) { ...@@ -208,7 +209,7 @@ videojs.Hls.prototype.src = function(src) {
208 this.tech_.src(videojs.URL.createObjectURL(this.mediaSource)); 209 this.tech_.src(videojs.URL.createObjectURL(this.mediaSource));
209 }; 210 };
210 211
211 videojs.Hls.prototype.handleSourceOpen = function() { 212 videojs.HlsHandler.prototype.handleSourceOpen = function() {
212 // Only attempt to create the source buffer if none already exist. 213 // Only attempt to create the source buffer if none already exist.
213 // handleSourceOpen is also called when we are "re-opening" a source buffer 214 // handleSourceOpen is also called when we are "re-opening" a source buffer
214 // after `endOfStream` has been called (in response to a seek for instance) 215 // after `endOfStream` has been called (in response to a seek for instance)
...@@ -284,7 +285,7 @@ videojs.Hls.bufferedAdditions_ = function(original, update) { ...@@ -284,7 +285,7 @@ videojs.Hls.bufferedAdditions_ = function(original, update) {
284 return result; 285 return result;
285 }; 286 };
286 287
287 videojs.Hls.prototype.setupSourceBuffer_ = function() { 288 videojs.HlsHandler.prototype.setupSourceBuffer_ = function() {
288 var media = this.playlists.media(), mimeType; 289 var media = this.playlists.media(), mimeType;
289 290
290 // wait until a media playlist is available and the Media Source is 291 // wait until a media playlist is available and the Media Source is
...@@ -375,7 +376,7 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() { ...@@ -375,7 +376,7 @@ videojs.Hls.prototype.setupSourceBuffer_ = function() {
375 * Seek to the latest media position if this is a live video and the 376 * Seek to the latest media position if this is a live video and the
376 * player and video are loaded and initialized. 377 * player and video are loaded and initialized.
377 */ 378 */
378 videojs.Hls.prototype.setupFirstPlay = function() { 379 videojs.HlsHandler.prototype.setupFirstPlay = function() {
379 var seekable, media; 380 var seekable, media;
380 media = this.playlists.media(); 381 media = this.playlists.media();
381 382
...@@ -405,7 +406,7 @@ videojs.Hls.prototype.setupFirstPlay = function() { ...@@ -405,7 +406,7 @@ videojs.Hls.prototype.setupFirstPlay = function() {
405 /** 406 /**
406 * Begin playing the video. 407 * Begin playing the video.
407 */ 408 */
408 videojs.Hls.prototype.play = function() { 409 videojs.HlsHandler.prototype.play = function() {
409 this.loadingState_ = 'segments'; 410 this.loadingState_ = 'segments';
410 411
411 if (this.tech_.ended()) { 412 if (this.tech_.ended()) {
...@@ -425,7 +426,7 @@ videojs.Hls.prototype.play = function() { ...@@ -425,7 +426,7 @@ videojs.Hls.prototype.play = function() {
425 } 426 }
426 }; 427 };
427 428
428 videojs.Hls.prototype.setCurrentTime = function(currentTime) { 429 videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) {
429 var 430 var
430 buffered = this.findCurrentBuffered_(); 431 buffered = this.findCurrentBuffered_();
431 432
...@@ -461,7 +462,7 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) { ...@@ -461,7 +462,7 @@ videojs.Hls.prototype.setCurrentTime = function(currentTime) {
461 this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime)); 462 this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime));
462 }; 463 };
463 464
464 videojs.Hls.prototype.duration = function() { 465 videojs.HlsHandler.prototype.duration = function() {
465 var playlists = this.playlists; 466 var playlists = this.playlists;
466 if (playlists) { 467 if (playlists) {
467 return videojs.Hls.Playlist.duration(playlists.media()); 468 return videojs.Hls.Playlist.duration(playlists.media());
...@@ -469,7 +470,7 @@ videojs.Hls.prototype.duration = function() { ...@@ -469,7 +470,7 @@ videojs.Hls.prototype.duration = function() {
469 return 0; 470 return 0;
470 }; 471 };
471 472
472 videojs.Hls.prototype.seekable = function() { 473 videojs.HlsHandler.prototype.seekable = function() {
473 var media; 474 var media;
474 475
475 if (!this.playlists) { 476 if (!this.playlists) {
...@@ -486,31 +487,32 @@ videojs.Hls.prototype.seekable = function() { ...@@ -486,31 +487,32 @@ videojs.Hls.prototype.seekable = function() {
486 /** 487 /**
487 * Update the player duration 488 * Update the player duration
488 */ 489 */
489 videojs.Hls.prototype.updateDuration = function(playlist) { 490 videojs.HlsHandler.prototype.updateDuration = function(playlist) {
490 var oldDuration = this.mediaSource.duration, 491 var oldDuration = this.mediaSource.duration,
491 newDuration = videojs.Hls.Playlist.duration(playlist), 492 newDuration = videojs.Hls.Playlist.duration(playlist),
492 setDuration = function() { 493 setDuration = function() {
493 this.mediaSource.duration = newDuration; 494 this.mediaSource.duration = newDuration;
495 // update seekable
496 if (seekable.length !== 0 && newDuration === Infinity) {
497 this.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0));
498 }
494 this.tech_.trigger('durationchange'); 499 this.tech_.trigger('durationchange');
500
495 this.mediaSource.removeEventListener('sourceopen', setDuration); 501 this.mediaSource.removeEventListener('sourceopen', setDuration);
496 }.bind(this), 502 }.bind(this),
497 seekable = this.seekable(); 503 seekable = this.seekable();
498 504
499 // TODO: Move to videojs-contrib-media-sources
500 if (seekable.length && newDuration === Infinity) {
501 if (isNaN(oldDuration)) {
502 oldDuration = 0;
503 }
504 newDuration = Math.max(oldDuration,
505 seekable.end(0) + playlist.targetDuration * 3);
506 }
507
508 // if the duration has changed, invalidate the cached value 505 // if the duration has changed, invalidate the cached value
509 if (oldDuration !== newDuration) { 506 if (oldDuration !== newDuration) {
507 // update the duration
510 if (this.mediaSource.readyState !== 'open') { 508 if (this.mediaSource.readyState !== 'open') {
511 this.mediaSource.addEventListener('sourceopen', setDuration); 509 this.mediaSource.addEventListener('sourceopen', setDuration);
512 } else if (!this.sourceBuffer || !this.sourceBuffer.updating) { 510 } else if (!this.sourceBuffer || !this.sourceBuffer.updating) {
513 this.mediaSource.duration = newDuration; 511 this.mediaSource.duration = newDuration;
512 // update seekable
513 if (seekable.length !== 0 && newDuration === Infinity) {
514 this.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0));
515 }
514 this.tech_.trigger('durationchange'); 516 this.tech_.trigger('durationchange');
515 } 517 }
516 } 518 }
...@@ -521,7 +523,7 @@ videojs.Hls.prototype.updateDuration = function(playlist) { ...@@ -521,7 +523,7 @@ videojs.Hls.prototype.updateDuration = function(playlist) {
521 * source. After this function is called, the tech should be in a 523 * source. After this function is called, the tech should be in a
522 * state suitable for switching to a different video. 524 * state suitable for switching to a different video.
523 */ 525 */
524 videojs.Hls.prototype.resetSrc_ = function() { 526 videojs.HlsHandler.prototype.resetSrc_ = function() {
525 this.cancelSegmentXhr(); 527 this.cancelSegmentXhr();
526 this.cancelKeyXhr(); 528 this.cancelKeyXhr();
527 529
...@@ -530,7 +532,7 @@ videojs.Hls.prototype.resetSrc_ = function() { ...@@ -530,7 +532,7 @@ videojs.Hls.prototype.resetSrc_ = function() {
530 } 532 }
531 }; 533 };
532 534
533 videojs.Hls.prototype.cancelKeyXhr = function() { 535 videojs.HlsHandler.prototype.cancelKeyXhr = function() {
534 if (this.keyXhr_) { 536 if (this.keyXhr_) {
535 this.keyXhr_.onreadystatechange = null; 537 this.keyXhr_.onreadystatechange = null;
536 this.keyXhr_.abort(); 538 this.keyXhr_.abort();
...@@ -538,7 +540,7 @@ videojs.Hls.prototype.cancelKeyXhr = function() { ...@@ -538,7 +540,7 @@ videojs.Hls.prototype.cancelKeyXhr = function() {
538 } 540 }
539 }; 541 };
540 542
541 videojs.Hls.prototype.cancelSegmentXhr = function() { 543 videojs.HlsHandler.prototype.cancelSegmentXhr = function() {
542 if (this.segmentXhr_) { 544 if (this.segmentXhr_) {
543 // Prevent error handler from running. 545 // Prevent error handler from running.
544 this.segmentXhr_.onreadystatechange = null; 546 this.segmentXhr_.onreadystatechange = null;
...@@ -552,7 +554,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { ...@@ -552,7 +554,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() {
552 /** 554 /**
553 * Abort all outstanding work and cleanup. 555 * Abort all outstanding work and cleanup.
554 */ 556 */
555 videojs.Hls.prototype.dispose = function() { 557 videojs.HlsHandler.prototype.dispose = function() {
556 this.stopCheckingBuffer_(); 558 this.stopCheckingBuffer_();
557 559
558 if (this.playlists) { 560 if (this.playlists) {
...@@ -569,7 +571,7 @@ videojs.Hls.prototype.dispose = function() { ...@@ -569,7 +571,7 @@ videojs.Hls.prototype.dispose = function() {
569 * @return the highest bitrate playlist less than the currently detected 571 * @return the highest bitrate playlist less than the currently detected
570 * bandwidth, accounting for some amount of bandwidth variance 572 * bandwidth, accounting for some amount of bandwidth variance
571 */ 573 */
572 videojs.Hls.prototype.selectPlaylist = function () { 574 videojs.HlsHandler.prototype.selectPlaylist = function () {
573 var 575 var
574 effectiveBitrate, 576 effectiveBitrate,
575 sortedPlaylists = this.playlists.master.playlists.slice(), 577 sortedPlaylists = this.playlists.master.playlists.slice(),
...@@ -662,7 +664,7 @@ videojs.Hls.prototype.selectPlaylist = function () { ...@@ -662,7 +664,7 @@ videojs.Hls.prototype.selectPlaylist = function () {
662 /** 664 /**
663 * Periodically request new segments and append video data. 665 * Periodically request new segments and append video data.
664 */ 666 */
665 videojs.Hls.prototype.checkBuffer_ = function() { 667 videojs.HlsHandler.prototype.checkBuffer_ = function() {
666 // calling this method directly resets any outstanding buffer checks 668 // calling this method directly resets any outstanding buffer checks
667 if (this.checkBufferTimeout_) { 669 if (this.checkBufferTimeout_) {
668 window.clearTimeout(this.checkBufferTimeout_); 670 window.clearTimeout(this.checkBufferTimeout_);
...@@ -681,7 +683,7 @@ videojs.Hls.prototype.checkBuffer_ = function() { ...@@ -681,7 +683,7 @@ videojs.Hls.prototype.checkBuffer_ = function() {
681 * Setup a periodic task to request new segments if necessary and 683 * Setup a periodic task to request new segments if necessary and
682 * append bytes into the SourceBuffer. 684 * append bytes into the SourceBuffer.
683 */ 685 */
684 videojs.Hls.prototype.startCheckingBuffer_ = function() { 686 videojs.HlsHandler.prototype.startCheckingBuffer_ = function() {
685 // if the player ever stalls, check if there is video data available 687 // if the player ever stalls, check if there is video data available
686 // to append immediately 688 // to append immediately
687 this.tech_.on('waiting', (this.drainBuffer).bind(this)); 689 this.tech_.on('waiting', (this.drainBuffer).bind(this));
...@@ -693,7 +695,7 @@ videojs.Hls.prototype.startCheckingBuffer_ = function() { ...@@ -693,7 +695,7 @@ videojs.Hls.prototype.startCheckingBuffer_ = function() {
693 * Stop the periodic task requesting new segments and feeding the 695 * Stop the periodic task requesting new segments and feeding the
694 * SourceBuffer. 696 * SourceBuffer.
695 */ 697 */
696 videojs.Hls.prototype.stopCheckingBuffer_ = function() { 698 videojs.HlsHandler.prototype.stopCheckingBuffer_ = function() {
697 if (this.checkBufferTimeout_) { 699 if (this.checkBufferTimeout_) {
698 window.clearTimeout(this.checkBufferTimeout_); 700 window.clearTimeout(this.checkBufferTimeout_);
699 this.checkBufferTimeout_ = null; 701 this.checkBufferTimeout_ = null;
...@@ -705,7 +707,7 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() { ...@@ -705,7 +707,7 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() {
705 * Attempts to find the buffered TimeRange where playback is currently 707 * Attempts to find the buffered TimeRange where playback is currently
706 * happening. Returns a new TimeRange with one or zero ranges. 708 * happening. Returns a new TimeRange with one or zero ranges.
707 */ 709 */
708 videojs.Hls.prototype.findCurrentBuffered_ = function() { 710 videojs.HlsHandler.prototype.findCurrentBuffered_ = function() {
709 var 711 var
710 ranges, 712 ranges,
711 i, 713 i,
...@@ -743,7 +745,7 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() { ...@@ -743,7 +745,7 @@ videojs.Hls.prototype.findCurrentBuffered_ = function() {
743 * @param seekToTime (optional) {number} the offset into the downloaded segment 745 * @param seekToTime (optional) {number} the offset into the downloaded segment
744 * to seek to, in seconds 746 * to seek to, in seconds
745 */ 747 */
746 videojs.Hls.prototype.fillBuffer = function(mediaIndex) { 748 videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
747 var 749 var
748 tech = this.tech_, 750 tech = this.tech_,
749 currentTime = tech.currentTime(), 751 currentTime = tech.currentTime(),
...@@ -840,7 +842,7 @@ videojs.Hls.prototype.fillBuffer = function(mediaIndex) { ...@@ -840,7 +842,7 @@ videojs.Hls.prototype.fillBuffer = function(mediaIndex) {
840 this.loadSegment(segmentInfo); 842 this.loadSegment(segmentInfo);
841 }; 843 };
842 844
843 videojs.Hls.prototype.playlistUriToUrl = function(segmentRelativeUrl) { 845 videojs.HlsHandler.prototype.playlistUriToUrl = function(segmentRelativeUrl) {
844 var playListUrl; 846 var playListUrl;
845 // resolve the segment URL relative to the playlist 847 // resolve the segment URL relative to the playlist
846 if (this.playlists.media().uri === this.source_.src) { 848 if (this.playlists.media().uri === this.source_.src) {
...@@ -859,7 +861,7 @@ videojs.Hls.prototype.playlistUriToUrl = function(segmentRelativeUrl) { ...@@ -859,7 +861,7 @@ videojs.Hls.prototype.playlistUriToUrl = function(segmentRelativeUrl) {
859 * * `bytesReceived` - amount of bytes downloaded 861 * * `bytesReceived` - amount of bytes downloaded
860 * `bandwidth` is the only required property. 862 * `bandwidth` is the only required property.
861 */ 863 */
862 videojs.Hls.prototype.setBandwidth = function(xhr) { 864 videojs.HlsHandler.prototype.setBandwidth = function(xhr) {
863 // calculate the download bandwidth 865 // calculate the download bandwidth
864 this.segmentXhrTime = xhr.roundTripTime; 866 this.segmentXhrTime = xhr.roundTripTime;
865 this.bandwidth = xhr.bandwidth; 867 this.bandwidth = xhr.bandwidth;
...@@ -868,7 +870,7 @@ videojs.Hls.prototype.setBandwidth = function(xhr) { ...@@ -868,7 +870,7 @@ videojs.Hls.prototype.setBandwidth = function(xhr) {
868 this.tech_.trigger('bandwidthupdate'); 870 this.tech_.trigger('bandwidthupdate');
869 }; 871 };
870 872
871 videojs.Hls.prototype.loadSegment = function(segmentInfo) { 873 videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
872 var 874 var
873 self = this, 875 self = this,
874 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 876 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
...@@ -927,7 +929,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) { ...@@ -927,7 +929,7 @@ videojs.Hls.prototype.loadSegment = function(segmentInfo) {
927 }); 929 });
928 }; 930 };
929 931
930 videojs.Hls.prototype.drainBuffer = function(event) { 932 videojs.HlsHandler.prototype.drainBuffer = function(event) {
931 var 933 var
932 segmentInfo, 934 segmentInfo,
933 mediaIndex, 935 mediaIndex,
...@@ -948,6 +950,12 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -948,6 +950,12 @@ videojs.Hls.prototype.drainBuffer = function(event) {
948 return; 950 return;
949 } 951 }
950 952
953 // the pending segment has already been appended and we're waiting
954 // for updateend to fire
955 if (this.pendingSegment_.buffered) {
956 return;
957 }
958
951 // we can't append more data if the source buffer is busy processing 959 // we can't append more data if the source buffer is busy processing
952 // what we've already sent 960 // what we've already sent
953 if (this.sourceBuffer.updating) { 961 if (this.sourceBuffer.updating) {
...@@ -1034,7 +1042,7 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -1034,7 +1042,7 @@ videojs.Hls.prototype.drainBuffer = function(event) {
1034 /** 1042 /**
1035 * Attempt to retrieve the key for a particular media segment. 1043 * Attempt to retrieve the key for a particular media segment.
1036 */ 1044 */
1037 videojs.Hls.prototype.fetchKey_ = function(segment) { 1045 videojs.HlsHandler.prototype.fetchKey_ = function(segment) {
1038 var key, self, settings, receiveKey; 1046 var key, self, settings, receiveKey;
1039 1047
1040 // if there is a pending XHR or no segments, don't do anything 1048 // if there is a pending XHR or no segments, don't do anything
......
...@@ -9,23 +9,9 @@ ...@@ -9,23 +9,9 @@
9 <!-- video.js --> 9 <!-- video.js -->
10 <script src="../../node_modules/video.js/dist/video.js"></script> 10 <script src="../../node_modules/video.js/dist/video.js"></script>
11 11
12 <!-- transmuxing -->
13 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/stream.js"></script>
14 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-generator.js"></script>
15 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/transmuxer.js"></script>
16 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/flv-tag.js"></script>
17 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/exp-golomb.js"></script>
18 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/h264-extradata.js"></script>
19 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/h264-stream.js"></script>
20 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/aac-stream.js"></script>
21 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/metadata-stream.js"></script>
22 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/segment-parser.js"></script>
23
24 <!-- Media Sources plugin --> 12 <!-- Media Sources plugin -->
25 <script src="../../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> 13 <script src="../../node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
26 <script> 14
27 videojs.MediaSource.webWorkerURI = '../../node_modules/videojs-contrib-media-sources/src/transmuxer_worker.js';
28 </script>
29 <!-- HLS plugin --> 15 <!-- HLS plugin -->
30 <script src="../../src/videojs-hls.js"></script> 16 <script src="../../src/videojs-hls.js"></script>
31 17
......
...@@ -134,11 +134,6 @@ var ...@@ -134,11 +134,6 @@ var
134 type: 'sourceopen', 134 type: 'sourceopen',
135 swfId: player.tech_.el().id 135 swfId: player.tech_.el().id
136 }); 136 });
137
138 // endOfStream triggers an exception if flash isn't available
139 player.tech_.hls.mediaSource.endOfStream = function(error) {
140 this.error_ = error;
141 };
142 }, 137 },
143 standardXHRResponse = function(request) { 138 standardXHRResponse = function(request) {
144 if (!request.url) { 139 if (!request.url) {
...@@ -170,6 +165,11 @@ var ...@@ -170,6 +165,11 @@ var
170 // a no-op MediaSource implementation to allow synchronous testing 165 // a no-op MediaSource implementation to allow synchronous testing
171 MockMediaSource = videojs.extend(videojs.EventTarget, { 166 MockMediaSource = videojs.extend(videojs.EventTarget, {
172 constructor: function() {}, 167 constructor: function() {},
168 duration: NaN,
169 seekable: videojs.createTimeRange(),
170 addSeekableRange_: function(start, end) {
171 this.seekable = videojs.createTimeRange(start, end);
172 },
173 addSourceBuffer: function() { 173 addSourceBuffer: function() {
174 return new (videojs.extend(videojs.EventTarget, { 174 return new (videojs.extend(videojs.EventTarget, {
175 constructor: function() {}, 175 constructor: function() {},
...@@ -179,7 +179,10 @@ var ...@@ -179,7 +179,10 @@ var
179 remove: function() {} 179 remove: function() {}
180 }))(); 180 }))();
181 }, 181 },
182 endOfStream: function() {} 182 // endOfStream triggers an exception if flash isn't available
183 endOfStream: function(error) {
184 this.error_ = error;
185 }
183 }), 186 }),
184 187
185 // do a shallow copy of the properties of source onto the target object 188 // do a shallow copy of the properties of source onto the target object
...@@ -1180,16 +1183,24 @@ test('only makes one segment request at a time', function() { ...@@ -1180,16 +1183,24 @@ test('only makes one segment request at a time', function() {
1180 }); 1183 });
1181 1184
1182 test('only appends one segment at a time', function() { 1185 test('only appends one segment at a time', function() {
1186 var appends = 0;
1183 player.src({ 1187 player.src({
1184 src: 'manifest/media.m3u8', 1188 src: 'manifest/media.m3u8',
1185 type: 'application/vnd.apple.mpegurl' 1189 type: 'application/vnd.apple.mpegurl'
1186 }); 1190 });
1187 openMediaSource(player); 1191 openMediaSource(player);
1188 standardXHRResponse(requests.pop()); // media.m3u8 1192 standardXHRResponse(requests.pop()); // media.m3u8
1193 player.tech_.hls.sourceBuffer.appendBuffer = function() {
1194 appends++;
1195 };
1196
1189 standardXHRResponse(requests.pop()); // segment 0 1197 standardXHRResponse(requests.pop()); // segment 0
1190 1198
1191 player.tech_.hls.checkBuffer_(); 1199 player.tech_.hls.checkBuffer_();
1192 equal(requests.length, 0, 'did not request while updating'); 1200 equal(requests.length, 0, 'did not request while updating');
1201
1202 player.tech_.hls.checkBuffer_();
1203 equal(appends, 1, 'appended once');
1193 }); 1204 });
1194 1205
1195 test('waits to download new segments until the media playlist is stable', function() { 1206 test('waits to download new segments until the media playlist is stable', function() {
...@@ -1377,7 +1388,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() { ...@@ -1377,7 +1388,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() {
1377 equal(requests.length, requestsLength, 'made no additional requests'); 1388 equal(requests.length, requestsLength, 'made no additional requests');
1378 }); 1389 });
1379 1390
1380 test('tech\'s duration reports Infinity for live playlists', function() { 1391 test('sets seekable and duration for live playlists', function() {
1381 player.src({ 1392 player.src({
1382 src: 'http://example.com/manifest/missingEndlist.m3u8', 1393 src: 'http://example.com/manifest/missingEndlist.m3u8',
1383 type: 'application/vnd.apple.mpegurl' 1394 type: 'application/vnd.apple.mpegurl'
...@@ -1386,13 +1397,19 @@ test('tech\'s duration reports Infinity for live playlists', function() { ...@@ -1386,13 +1397,19 @@ test('tech\'s duration reports Infinity for live playlists', function() {
1386 1397
1387 standardXHRResponse(requests[0]); 1398 standardXHRResponse(requests[0]);
1388 1399
1389 strictEqual(player.tech_.duration(), 1400 equal(player.tech_.hls.mediaSource.seekable.length,
1390 Infinity, 1401 1,
1391 'duration on the tech is infinity'); 1402 'set one seekable range');
1403 equal(player.tech_.hls.mediaSource.seekable.start(0),
1404 player.tech_.hls.seekable().start(0),
1405 'set seekable start');
1406 equal(player.tech_.hls.mediaSource.seekable.end(0),
1407 player.tech_.hls.seekable().end(0),
1408 'set seekable end');
1392 1409
1393 notEqual(player.tech_.hls.mediaSource.duration, 1410 strictEqual(player.tech_.hls.mediaSource.duration,
1394 Infinity, 1411 Infinity,
1395 'duration on the mediaSource is not infinity'); 1412 'duration on the mediaSource is infinity');
1396 }); 1413 });
1397 1414
1398 test('live playlist starts three target durations before live', function() { 1415 test('live playlist starts three target durations before live', function() {
...@@ -1518,6 +1535,7 @@ test('reloads out-of-date live playlists when switching variants', function() { ...@@ -1518,6 +1535,7 @@ test('reloads out-of-date live playlists when switching variants', function() {
1518 }); 1535 });
1519 1536
1520 test('if withCredentials global option is used, withCredentials is set on the XHR object', function() { 1537 test('if withCredentials global option is used, withCredentials is set on the XHR object', function() {
1538 var hlsOptions = videojs.options.hls;
1521 player.dispose(); 1539 player.dispose();
1522 videojs.options.hls = { 1540 videojs.options.hls = {
1523 withCredentials: true 1541 withCredentials: true
...@@ -1530,6 +1548,7 @@ test('if withCredentials global option is used, withCredentials is set on the XH ...@@ -1530,6 +1548,7 @@ test('if withCredentials global option is used, withCredentials is set on the XH
1530 openMediaSource(player); 1548 openMediaSource(player);
1531 ok(requests[0].withCredentials, 1549 ok(requests[0].withCredentials,
1532 'with credentials should be set to true if that option is passed in'); 1550 'with credentials should be set to true if that option is passed in');
1551 videojs.options.hls = hlsOptions;
1533 }); 1552 });
1534 1553
1535 test('if withCredentials src option is used, withCredentials is set on the XHR object', function() { 1554 test('if withCredentials src option is used, withCredentials is set on the XHR object', function() {
...@@ -1901,10 +1920,10 @@ test('the source handler supports HLS mime types', function() { ...@@ -1901,10 +1920,10 @@ test('the source handler supports HLS mime types', function() {
1901 1920
1902 ok(!(videojs.HlsSourceHandler(techName).canHandleSource({ 1921 ok(!(videojs.HlsSourceHandler(techName).canHandleSource({
1903 type: 'video/mp4' 1922 type: 'video/mp4'
1904 }) instanceof videojs.Hls), 'does not support mp4'); 1923 }) instanceof videojs.HlsHandler), 'does not support mp4');
1905 ok(!(videojs.HlsSourceHandler(techName).canHandleSource({ 1924 ok(!(videojs.HlsSourceHandler(techName).canHandleSource({
1906 type: 'video/x-flv' 1925 type: 'video/x-flv'
1907 }) instanceof videojs.Hls), 'does not support flv'); 1926 }) instanceof videojs.HlsHandler), 'does not support flv');
1908 }); 1927 });
1909 }); 1928 });
1910 1929
......