598e5e89 by David LaPalomento

Merge pull request #389 from videojs/discontinuity

Discontinuity
2 parents 9e8618df 322b2318
...@@ -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.0.0-rc.51" 47 "video.js": "^5.0.0-rc.96"
48 }, 48 },
49 "dependencies": { 49 "dependencies": {
50 "pkcs7": "^0.2.2", 50 "pkcs7": "^0.2.2",
51 "videojs-contrib-media-sources": "videojs/videojs-contrib-media-sources.git#mse-mp2t-polyfill", 51 "videojs-contrib-media-sources": "videojs/videojs-contrib-media-sources.git#mse-mp2t-polyfill",
52 "videojs-swf": "5.0.0-rc0" 52 "videojs-swf": "5.0.0-rc1"
53 } 53 }
54 } 54 }
......
...@@ -24,8 +24,8 @@ keyFailed = function(key) { ...@@ -24,8 +24,8 @@ keyFailed = function(key) {
24 return key.retries && key.retries >= 2; 24 return key.retries && key.retries >= 2;
25 }; 25 };
26 26
27 videojs.Hls = videojs.extends(Component, { 27 videojs.Hls = videojs.extend(Component, {
28 constructor: function(tech, source) { 28 constructor: function(tech, options) {
29 var self = this, _player; 29 var self = this, _player;
30 30
31 Component.call(this, tech); 31 Component.call(this, tech);
...@@ -44,7 +44,8 @@ videojs.Hls = videojs.extends(Component, { ...@@ -44,7 +44,8 @@ videojs.Hls = videojs.extends(Component, {
44 } 44 }
45 } 45 }
46 this.tech_ = tech; 46 this.tech_ = tech;
47 this.source_ = source; 47 this.source_ = options.source;
48 this.mode_ = options.mode;
48 this.bytesReceived = 0; 49 this.bytesReceived = 0;
49 50
50 // loadingState_ tracks how far along the buffering process we 51 // loadingState_ tracks how far along the buffering process we
...@@ -87,28 +88,33 @@ videojs.Hls.canPlaySource = function() { ...@@ -87,28 +88,33 @@ videojs.Hls.canPlaySource = function() {
87 * the browser it is running in. It is not necessary to use or modify 88 * the browser it is running in. It is not necessary to use or modify
88 * this object in normal usage. 89 * this object in normal usage.
89 */ 90 */
90 videojs.HlsSourceHandler = { 91 videojs.HlsSourceHandler = function(mode) {
91 canHandleSource: function(srcObj) { 92 return {
92 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 93 canHandleSource: function(srcObj) {
93 94 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
94 // favor native HLS support if it's available 95
95 if (videojs.Hls.supportsNativeHls) { 96 // favor native HLS support if it's available
96 return false; 97 if (videojs.Hls.supportsNativeHls) {
98 return false;
99 }
100 return mpegurlRE.test(srcObj.type);
101 },
102 handleSource: function(source, tech) {
103 tech.hls = new videojs.Hls(tech, {
104 source: source,
105 mode: mode
106 });
107 tech.hls.src(source.src);
108 return tech.hls;
97 } 109 }
98 return mpegurlRE.test(srcObj.type); 110 };
99 },
100 handleSource: function(source, tech) {
101 tech.hls = new videojs.Hls(tech, source);
102 tech.hls.src(source.src);
103 return tech.hls;
104 }
105 }; 111 };
106 // register with the appropriate tech 112
113 // register source handlers with the appropriate techs
107 if (videojs.MediaSource.supportsNativeMediaSources()) { 114 if (videojs.MediaSource.supportsNativeMediaSources()) {
108 videojs.getComponent('Html5').registerSourceHandler(videojs.HlsSourceHandler); 115 videojs.getComponent('Html5').registerSourceHandler(videojs.HlsSourceHandler('html5'));
109 } else {
110 videojs.getComponent('Flash').registerSourceHandler(videojs.HlsSourceHandler);
111 } 116 }
117 videojs.getComponent('Flash').registerSourceHandler(videojs.HlsSourceHandler('flash'));
112 118
113 // the desired length of video to maintain in the buffer, in seconds 119 // the desired length of video to maintain in the buffer, in seconds
114 videojs.Hls.GOAL_BUFFER_LENGTH = 30; 120 videojs.Hls.GOAL_BUFFER_LENGTH = 30;
...@@ -121,7 +127,7 @@ videojs.Hls.prototype.src = function(src) { ...@@ -121,7 +127,7 @@ videojs.Hls.prototype.src = function(src) {
121 return; 127 return;
122 } 128 }
123 129
124 this.mediaSource = new videojs.MediaSource(); 130 this.mediaSource = new videojs.MediaSource({ mode: this.mode_ });
125 this.segmentBuffer_ = []; 131 this.segmentBuffer_ = [];
126 132
127 // if the stream contains ID3 metadata, expose that as a metadata 133 // if the stream contains ID3 metadata, expose that as a metadata
...@@ -739,6 +745,31 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() { ...@@ -739,6 +745,31 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() {
739 }; 745 };
740 746
741 /** 747 /**
748 * Attempts to find the buffered TimeRange where playback is currently
749 * happening. Returns a new TimeRange with one or zero ranges.
750 */
751 videojs.Hls.prototype.findCurrentBuffered_ = function() {
752 var
753 tech = this.tech_,
754 currentTime = tech.currentTime(),
755 buffered = this.tech_.buffered(),
756 i;
757
758 if (buffered && buffered.length) {
759 // Search for a range containing the play-head
760 for (i = 0;i < buffered.length; i++) {
761 if (buffered.start(i) <= currentTime &&
762 buffered.end(i) >= currentTime) {
763 return videojs.createTimeRange(buffered.start(i), buffered.end(i));
764 }
765 }
766 }
767
768 // Return an empty range if no ranges exist
769 return videojs.createTimeRange();
770 };
771
772 /**
742 * Determines whether there is enough video data currently in the buffer 773 * Determines whether there is enough video data currently in the buffer
743 * and downloads a new segment if the buffered time is less than the goal. 774 * and downloads a new segment if the buffered time is less than the goal.
744 * @param offset (optional) {number} the offset into the downloaded segment 775 * @param offset (optional) {number} the offset into the downloaded segment
...@@ -747,7 +778,8 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() { ...@@ -747,7 +778,8 @@ videojs.Hls.prototype.stopCheckingBuffer_ = function() {
747 videojs.Hls.prototype.fillBuffer = function(offset) { 778 videojs.Hls.prototype.fillBuffer = function(offset) {
748 var 779 var
749 tech = this.tech_, 780 tech = this.tech_,
750 buffered = this.tech_.buffered(), 781 currentTime = tech.currentTime(),
782 buffered = this.findCurrentBuffered_(),
751 bufferedTime = 0, 783 bufferedTime = 0,
752 segment, 784 segment,
753 segmentUri; 785 segmentUri;
...@@ -785,9 +817,10 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -785,9 +817,10 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
785 return; 817 return;
786 } 818 }
787 819
820 // To determine how much is buffered, we need to find the buffered region we
821 // are currently playing in and measure it's length
788 if (buffered && buffered.length) { 822 if (buffered && buffered.length) {
789 // assuming a single, contiguous buffer region 823 bufferedTime = Math.max(0, buffered.end(0) - currentTime);
790 bufferedTime = tech.buffered().end(0) - tech.currentTime();
791 } 824 }
792 825
793 // if there is plenty of content in the buffer and we're not 826 // if there is plenty of content in the buffer and we're not
...@@ -844,12 +877,13 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) { ...@@ -844,12 +877,13 @@ videojs.Hls.prototype.loadSegment = function(segmentUri, offset) {
844 // the segment request is no longer outstanding 877 // the segment request is no longer outstanding
845 self.segmentXhr_ = null; 878 self.segmentXhr_ = null;
846 879
847 if (error) { 880 // if a segment request times out, we may have better luck with another playlist
848 // if a segment request times out, we may have better luck with another playlist 881 if (request.timedout) {
849 if (request.timedout) { 882 self.bandwidth = 1;
850 self.bandwidth = 1; 883 return self.playlists.media(self.selectPlaylist());
851 return self.playlists.media(self.selectPlaylist()); 884 }
852 } 885
886 if (!request.aborted && error) {
853 // otherwise, try jumping ahead to the next segment 887 // otherwise, try jumping ahead to the next segment
854 self.error = { 888 self.error = {
855 status: request.status, 889 status: request.status,
...@@ -914,7 +948,6 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -914,7 +948,6 @@ videojs.Hls.prototype.drainBuffer = function(event) {
914 segment, 948 segment,
915 decrypter, 949 decrypter,
916 segIv, 950 segIv,
917 segmentOffset = 0,
918 // ptsTime, 951 // ptsTime,
919 segmentBuffer = this.segmentBuffer_; 952 segmentBuffer = this.segmentBuffer_;
920 953
...@@ -999,11 +1032,9 @@ videojs.Hls.prototype.drainBuffer = function(event) { ...@@ -999,11 +1032,9 @@ videojs.Hls.prototype.drainBuffer = function(event) {
999 // this.tech_.el().vjs_discontinuity(); 1032 // this.tech_.el().vjs_discontinuity();
1000 // } 1033 // }
1001 1034
1002 // determine the timestamp offset for the start of this segment 1035 if (segment.discontinuity) {
1003 segmentOffset = this.playlists.expiredPostDiscontinuity_ + this.playlists.expiredPreDiscontinuity_; 1036 this.sourceBuffer.timestampOffset = this.findCurrentBuffered_().end(0);
1004 segmentOffset += videojs.Hls.Playlist.duration(playlist, 1037 }
1005 playlist.mediaSequence,
1006 playlist.mediaSequence + mediaIndex);
1007 1038
1008 this.sourceBuffer.appendBuffer(bytes); 1039 this.sourceBuffer.appendBuffer(bytes);
1009 1040
......
...@@ -5,12 +5,34 @@ ...@@ -5,12 +5,34 @@
5 * A wrapper for videojs.xhr that tracks bandwidth. 5 * A wrapper for videojs.xhr that tracks bandwidth.
6 */ 6 */
7 videojs.Hls.xhr = function(options, callback) { 7 videojs.Hls.xhr = function(options, callback) {
8 var request = videojs.xhr(options, function(error, request) { 8 // Add a default timeout for all hls requests
9 if (request.response) { 9 options = videojs.mergeOptions({
10 timeout: 45e3
11 }, options);
12
13 var request = videojs.xhr(options, function(error, response) {
14 if (!error && request.response) {
10 request.responseTime = (new Date()).getTime(); 15 request.responseTime = (new Date()).getTime();
11 request.roundTripTime = request.responseTime - request.requestTime; 16 request.roundTripTime = request.responseTime - request.requestTime;
12 request.bytesReceived = request.response.byteLength || request.response.length; 17 request.bytesReceived = request.response.byteLength || request.response.length;
13 request.bandwidth = Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000); 18 if (!request.bandwidth) {
19 request.bandwidth = Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000);
20 }
21 }
22
23 // videojs.xhr now uses a specific code on the error object to signal that a request has
24 // timed out errors of setting a boolean on the request object
25 if (error || request.timedout) {
26 request.timedout = request.timedout || (error.code === 'ETIMEDOUT');
27 } else {
28 request.timedout = false;
29 }
30
31 // videojs.xhr no longer consider status codes outside of 200 and 0 (for file uris) to be
32 // errors but the old XHR did so emulate that behavior
33 if (!error && response.statusCode !== 200 && response.statusCode !== 0) {
34 error = new Error('XHR Failed with a response of: ' +
35 (request && (request.response || request.responseText)));
14 } 36 }
15 37
16 callback(error, request); 38 callback(error, request);
......
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
20 setup: function() { 20 setup: function() {
21 // fake XHRs 21 // fake XHRs
22 sinonXhr = sinon.useFakeXMLHttpRequest(); 22 sinonXhr = sinon.useFakeXMLHttpRequest();
23 videojs.xhr.XMLHttpRequest = sinonXhr;
24
23 requests = []; 25 requests = [];
24 sinonXhr.onCreate = function(xhr) { 26 sinonXhr.onCreate = function(xhr) {
25 // force the XHR2 timeout polyfill 27 // force the XHR2 timeout polyfill
...@@ -32,6 +34,7 @@ ...@@ -32,6 +34,7 @@
32 }, 34 },
33 teardown: function() { 35 teardown: function() {
34 sinonXhr.restore(); 36 sinonXhr.restore();
37 videojs.xhr.XMLHttpRequest = window.XMLHttpRequest;
35 clock.restore(); 38 clock.restore();
36 } 39 }
37 }); 40 });
......