aacd01ed by David LaPalomento

Merge pull request #223 from videojs/hotfix/src-changes

Clean up the playlist loader when sources change
2 parents c849e312 88fba5f3
...@@ -10,6 +10,9 @@ var ...@@ -10,6 +10,9 @@ var
10 // a fudge factor to apply to advertised playlist bitrates to account for 10 // a fudge factor to apply to advertised playlist bitrates to account for
11 // temporary flucations in client bandwidth 11 // temporary flucations in client bandwidth
12 bandwidthVariance = 1.1, 12 bandwidthVariance = 1.1,
13
14 // the amount of time to wait between checking the state of the buffer
15 bufferCheckInterval = 500,
13 keyXhr, 16 keyXhr,
14 keyFailed, 17 keyFailed,
15 resolveUrl; 18 resolveUrl;
...@@ -36,6 +39,11 @@ videojs.Hls = videojs.Flash.extend({ ...@@ -36,6 +39,11 @@ videojs.Hls = videojs.Flash.extend({
36 this.currentTime = videojs.Hls.prototype.currentTime; 39 this.currentTime = videojs.Hls.prototype.currentTime;
37 this.setCurrentTime = videojs.Hls.prototype.setCurrentTime; 40 this.setCurrentTime = videojs.Hls.prototype.setCurrentTime;
38 41
42 // periodically check if new data needs to be downloaded or
43 // buffered data should be appended to the source buffer
44 this.segmentBuffer_ = [];
45 this.startCheckingBuffer_();
46
39 videojs.Hls.prototype.src.call(this, options.source && options.source.src); 47 videojs.Hls.prototype.src.call(this, options.source && options.source.src);
40 } 48 }
41 }); 49 });
...@@ -49,7 +57,10 @@ videojs.Hls.GOAL_BUFFER_LENGTH = 30; ...@@ -49,7 +57,10 @@ videojs.Hls.GOAL_BUFFER_LENGTH = 30;
49 videojs.Hls.prototype.src = function(src) { 57 videojs.Hls.prototype.src = function(src) {
50 var 58 var
51 tech = this, 59 tech = this,
60 player = this.player(),
61 settings = player.options().hls || {},
52 mediaSource, 62 mediaSource,
63 oldMediaPlaylist,
53 source; 64 source;
54 65
55 // do nothing if the src is falsey 66 // do nothing if the src is falsey
...@@ -115,46 +126,13 @@ videojs.Hls.prototype.src = function(src) { ...@@ -115,46 +126,13 @@ videojs.Hls.prototype.src = function(src) {
115 // load the MediaSource into the player 126 // load the MediaSource into the player
116 this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen)); 127 this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen));
117 128
118 this.player().ready(function() { 129 // cleanup the old playlist loader, if necessary
119 // do nothing if the tech has been disposed already
120 // this can occur if someone sets the src in player.ready(), for instance
121 if (!tech.el()) {
122 return;
123 }
124 tech.el().vjs_src(source.src);
125 });
126 };
127
128 videojs.Hls.setMediaIndexForLive = function(selectedPlaylist) {
129 var tailIterator = selectedPlaylist.segments.length,
130 tailDuration = 0,
131 targetTail = (selectedPlaylist.targetDuration || 10) * 3;
132
133 while (tailDuration < targetTail && tailIterator > 0) {
134 tailDuration += selectedPlaylist.segments[tailIterator - 1].duration;
135 tailIterator--;
136 }
137
138 return tailIterator;
139 };
140
141 videojs.Hls.prototype.handleSourceOpen = function() {
142 // construct the video data buffer and set the appropriate MIME type
143 var
144 player = this.player(),
145 settings = player.options().hls || {},
146 sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'),
147 oldMediaPlaylist;
148
149 this.sourceBuffer = sourceBuffer;
150 sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader());
151
152 this.mediaIndex = 0;
153
154 if (this.playlists) { 130 if (this.playlists) {
155 this.playlists.dispose(); 131 this.playlists.dispose();
156 } 132 }
157 133
134 this.mediaIndex = 0;
135
158 this.playlists = new videojs.Hls.PlaylistLoader(this.src_, settings.withCredentials); 136 this.playlists = new videojs.Hls.PlaylistLoader(this.src_, settings.withCredentials);
159 137
160 this.playlists.on('loadedmetadata', videojs.bind(this, function() { 138 this.playlists.on('loadedmetadata', videojs.bind(this, function() {
...@@ -164,11 +142,7 @@ videojs.Hls.prototype.handleSourceOpen = function() { ...@@ -164,11 +142,7 @@ videojs.Hls.prototype.handleSourceOpen = function() {
164 setupEvents = function() { 142 setupEvents = function() {
165 this.fillBuffer(); 143 this.fillBuffer();
166 144
167 // periodically check if new data needs to be downloaded or 145
168 // buffered data should be appended to the source buffer
169 player.on('timeupdate', videojs.bind(this, this.fillBuffer));
170 player.on('timeupdate', videojs.bind(this, this.drainBuffer));
171 player.on('waiting', videojs.bind(this, this.drainBuffer));
172 146
173 player.trigger('loadedmetadata'); 147 player.trigger('loadedmetadata');
174 }; 148 };
...@@ -255,6 +229,39 @@ videojs.Hls.prototype.handleSourceOpen = function() { ...@@ -255,6 +229,39 @@ videojs.Hls.prototype.handleSourceOpen = function() {
255 player.trigger('mediachange'); 229 player.trigger('mediachange');
256 })); 230 }));
257 231
232 this.player().ready(function() {
233 // do nothing if the tech has been disposed already
234 // this can occur if someone sets the src in player.ready(), for instance
235 if (!tech.el()) {
236 return;
237 }
238 tech.el().vjs_src(source.src);
239 });
240 };
241
242 videojs.Hls.setMediaIndexForLive = function(selectedPlaylist) {
243 var tailIterator = selectedPlaylist.segments.length,
244 tailDuration = 0,
245 targetTail = (selectedPlaylist.targetDuration || 10) * 3;
246
247 while (tailDuration < targetTail && tailIterator > 0) {
248 tailDuration += selectedPlaylist.segments[tailIterator - 1].duration;
249 tailIterator--;
250 }
251
252 return tailIterator;
253 };
254
255 videojs.Hls.prototype.handleSourceOpen = function() {
256 // construct the video data buffer and set the appropriate MIME type
257 var
258 player = this.player(),
259 sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
260
261 this.sourceBuffer = sourceBuffer;
262 sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader());
263
264
258 // if autoplay is enabled, begin playback. This is duplicative of 265 // if autoplay is enabled, begin playback. This is duplicative of
259 // code in video.js but is required because play() must be invoked 266 // code in video.js but is required because play() must be invoked
260 // *after* the media source has opened. 267 // *after* the media source has opened.
...@@ -377,12 +384,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() { ...@@ -377,12 +384,7 @@ videojs.Hls.prototype.cancelSegmentXhr = function() {
377 * Abort all outstanding work and cleanup. 384 * Abort all outstanding work and cleanup.
378 */ 385 */
379 videojs.Hls.prototype.dispose = function() { 386 videojs.Hls.prototype.dispose = function() {
380 var player = this.player(); 387 this.stopCheckingBuffer_();
381
382 // remove event handlers
383 player.off('timeupdate', this.fillBuffer);
384 player.off('timeupdate', this.drainBuffer);
385 player.off('waiting', this.drainBuffer);
386 388
387 if (this.playlists) { 389 if (this.playlists) {
388 this.playlists.dispose(); 390 this.playlists.dispose();
...@@ -468,6 +470,46 @@ videojs.Hls.prototype.selectPlaylist = function () { ...@@ -468,6 +470,46 @@ videojs.Hls.prototype.selectPlaylist = function () {
468 }; 470 };
469 471
470 /** 472 /**
473 * Periodically request new segments and append video data.
474 */
475 videojs.Hls.prototype.checkBuffer_ = function() {
476 // calling this method directly resets any outstanding buffer checks
477 if (this.checkBufferTimeout_) {
478 window.clearTimeout(this.checkBufferTimeout_);
479 this.checkBufferTimeout_ = null;
480 }
481
482 this.fillBuffer();
483 this.drainBuffer();
484
485 // wait awhile and try again
486 this.checkBufferTimeout_ = window.setTimeout(videojs.bind(this, this.checkBuffer_),
487 bufferCheckInterval);
488 };
489
490 /**
491 * Setup a periodic task to request new segments if necessary and
492 * append bytes into the SourceBuffer.
493 */
494 videojs.Hls.prototype.startCheckingBuffer_ = function() {
495 // if the player ever stalls, check if there is video data available
496 // to append immediately
497 this.player().on('waiting', videojs.bind(this, this.drainBuffer));
498
499 this.checkBuffer_();
500 };
501
502 /**
503 * Stop the periodic task requesting new segments and feeding the
504 * SourceBuffer.
505 */
506 videojs.Hls.prototype.stopCheckingBuffer_ = function() {
507 window.clearTimeout(this.checkBufferTimeout_);
508 this.checkBufferTimeout_ = null;
509 this.player().off('waiting', this.drainBuffer);
510 };
511
512 /**
471 * Determines whether there is enough video data currently in the buffer 513 * Determines whether there is enough video data currently in the buffer
472 * and downloads a new segment if the buffered time is less than the goal. 514 * and downloads a new segment if the buffered time is less than the goal.
473 * @param offset (optional) {number} the offset into the downloaded segment 515 * @param offset (optional) {number} the offset into the downloaded segment
...@@ -481,6 +523,11 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -481,6 +523,11 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
481 segment, 523 segment,
482 segmentUri; 524 segmentUri;
483 525
526 // if a video has not been specified, do nothing
527 if (!player.currentSrc() || !this.playlists) {
528 return;
529 }
530
484 // if there is a request already in flight, do nothing 531 // if there is a request already in flight, do nothing
485 if (this.segmentXhr_) { 532 if (this.segmentXhr_) {
486 return; 533 return;
...@@ -488,6 +535,7 @@ videojs.Hls.prototype.fillBuffer = function(offset) { ...@@ -488,6 +535,7 @@ videojs.Hls.prototype.fillBuffer = function(offset) {
488 535
489 // if no segments are available, do nothing 536 // if no segments are available, do nothing
490 if (this.playlists.state === "HAVE_NOTHING" || 537 if (this.playlists.state === "HAVE_NOTHING" ||
538 !this.playlists.media() ||
491 !this.playlists.media().segments) { 539 !this.playlists.media().segments) {
492 return; 540 return;
493 } 541 }
......