Merge pull request #389 from videojs/discontinuity
Discontinuity
Showing
5 changed files
with
98 additions
and
42 deletions
... | @@ -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 | }); | ... | ... |
This diff is collapsed.
Click to expand it.
-
Please register or sign in to post a comment