5364888a by David LaPalomento

Manage seekable ranges for live streams

Use the experimental implementation of MediaSource.addSeekableRange_() to allow HLS to specify greater seekable ranges then the current buffered region. Requires https://github.com/videojs/videojs-contrib-media-sources/pull/55.
1 parent 7c97a6a5
...@@ -493,25 +493,26 @@ videojs.HlsHandler.prototype.updateDuration = function(playlist) { ...@@ -493,25 +493,26 @@ videojs.HlsHandler.prototype.updateDuration = function(playlist) {
493 setDuration = function() { 493 setDuration = function() {
494 this.mediaSource.duration = newDuration; 494 this.mediaSource.duration = newDuration;
495 this.tech_.trigger('durationchange'); 495 this.tech_.trigger('durationchange');
496
497 // update seekable
498 if (seekable.length !== 0 && newDuration === Infinity) {
499 this.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0));
500 }
496 this.mediaSource.removeEventListener('sourceopen', setDuration); 501 this.mediaSource.removeEventListener('sourceopen', setDuration);
497 }.bind(this), 502 }.bind(this),
498 seekable = this.seekable(); 503 seekable = this.seekable();
499 504
500 // TODO: Move to videojs-contrib-media-sources
501 if (seekable.length && newDuration === Infinity) {
502 if (isNaN(oldDuration)) {
503 oldDuration = 0;
504 }
505 newDuration = Math.max(oldDuration,
506 seekable.end(0) + playlist.targetDuration * 3);
507 }
508
509 // if the duration has changed, invalidate the cached value 505 // if the duration has changed, invalidate the cached value
510 if (oldDuration !== newDuration) { 506 if (oldDuration !== newDuration) {
507 // update the duration
511 if (this.mediaSource.readyState !== 'open') { 508 if (this.mediaSource.readyState !== 'open') {
512 this.mediaSource.addEventListener('sourceopen', setDuration); 509 this.mediaSource.addEventListener('sourceopen', setDuration);
513 } else if (!this.sourceBuffer || !this.sourceBuffer.updating) { 510 } else if (!this.sourceBuffer || !this.sourceBuffer.updating) {
514 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 }
515 this.tech_.trigger('durationchange'); 516 this.tech_.trigger('durationchange');
516 } 517 }
517 } 518 }
......
...@@ -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
...@@ -1385,7 +1388,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() { ...@@ -1385,7 +1388,7 @@ test('seeking in an empty playlist is a non-erroring noop', function() {
1385 equal(requests.length, requestsLength, 'made no additional requests'); 1388 equal(requests.length, requestsLength, 'made no additional requests');
1386 }); 1389 });
1387 1390
1388 test('tech\'s duration reports Infinity for live playlists', function() { 1391 test('sets seekable and duration for live playlists', function() {
1389 player.src({ 1392 player.src({
1390 src: 'http://example.com/manifest/missingEndlist.m3u8', 1393 src: 'http://example.com/manifest/missingEndlist.m3u8',
1391 type: 'application/vnd.apple.mpegurl' 1394 type: 'application/vnd.apple.mpegurl'
...@@ -1394,13 +1397,19 @@ test('tech\'s duration reports Infinity for live playlists', function() { ...@@ -1394,13 +1397,19 @@ test('tech\'s duration reports Infinity for live playlists', function() {
1394 1397
1395 standardXHRResponse(requests[0]); 1398 standardXHRResponse(requests[0]);
1396 1399
1397 strictEqual(player.tech_.duration(), 1400 equal(player.tech_.hls.mediaSource.seekable.length,
1398 Infinity, 1401 1,
1399 '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');
1400 1409
1401 notEqual(player.tech_.hls.mediaSource.duration, 1410 strictEqual(player.tech_.hls.mediaSource.duration,
1402 Infinity, 1411 Infinity,
1403 'duration on the mediaSource is not infinity'); 1412 'duration on the mediaSource is infinity');
1404 }); 1413 });
1405 1414
1406 test('live playlist starts three target durations before live', function() { 1415 test('live playlist starts three target durations before live', function() {
......