e1692fcd by David LaPalomento

Decouple appending bytes from downloading and parsing segments

We have to wait until the SourceBuffer is empty before resetting the decoder when crossing over a timestamp discontinuity. Make appending bytes happen asynchronously so that we can delay appends util the buffer is empty when we see a discontinuity in the playlist. Fix tests.
1 parent 654f42cb
...@@ -161,7 +161,7 @@ var ...@@ -161,7 +161,7 @@ var
161 i; 161 i;
162 162
163 startIndex = startIndex || 0; 163 startIndex = startIndex || 0;
164 endIndex = endIndex || (playlist.segments || []).length; 164 endIndex = endIndex !== undefined ? endIndex : (playlist.segments || []).length;
165 i = endIndex - 1; 165 i = endIndex - 1;
166 166
167 for (; i >= startIndex; i--) { 167 for (; i >= startIndex; i--) {
...@@ -201,10 +201,12 @@ var ...@@ -201,10 +201,12 @@ var
201 var 201 var
202 segmentParser = new videojs.Hls.SegmentParser(), 202 segmentParser = new videojs.Hls.SegmentParser(),
203 settings = videojs.util.mergeOptions({}, player.options().hls), 203 settings = videojs.util.mergeOptions({}, player.options().hls),
204 segmentBuffer = [],
204 205
205 lastSeekedTime, 206 lastSeekedTime,
206 segmentXhr, 207 segmentXhr,
207 fillBuffer, 208 fillBuffer,
209 drainBuffer,
208 updateDuration; 210 updateDuration;
209 211
210 212
...@@ -385,6 +387,8 @@ var ...@@ -385,6 +387,8 @@ var
385 responseType: 'arraybuffer', 387 responseType: 'arraybuffer',
386 withCredentials: settings.withCredentials 388 withCredentials: settings.withCredentials
387 }, function(error, url) { 389 }, function(error, url) {
390 var tags;
391
388 // the segment request is no longer outstanding 392 // the segment request is no longer outstanding
389 segmentXhr = null; 393 segmentXhr = null;
390 394
...@@ -405,10 +409,6 @@ var ...@@ -405,10 +409,6 @@ var
405 return; 409 return;
406 } 410 }
407 411
408 if (segment.discontinuity) {
409 player.hls.sourceBuffer.abort();
410 }
411
412 // calculate the download bandwidth 412 // calculate the download bandwidth
413 player.hls.segmentXhrTime = (+new Date()) - startTime; 413 player.hls.segmentXhrTime = (+new Date()) - startTime;
414 player.hls.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000; 414 player.hls.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000;
...@@ -417,52 +417,97 @@ var ...@@ -417,52 +417,97 @@ var
417 segmentParser.parseSegmentBinaryData(new Uint8Array(this.response)); 417 segmentParser.parseSegmentBinaryData(new Uint8Array(this.response));
418 segmentParser.flushTags(); 418 segmentParser.flushTags();
419 419
420 // package up all the work to append the segment
421 // if the segment is the start of a timestamp discontinuity,
422 // we have to wait until the sourcebuffer is empty before
423 // aborting the source buffer processing
424 tags = [];
425 while (segmentParser.tagsAvailable()) {
426 tags.push(segmentParser.getNextTag());
427 }
428 segmentBuffer.push({
429 mediaIndex: player.hls.mediaIndex,
430 playlist: player.hls.playlists.media(),
431 offset: offset,
432 tags: tags
433 });
434 drainBuffer();
435
436 player.hls.mediaIndex++;
437
438 // figure out what stream the next segment should be downloaded from
439 // with the updated bandwidth information
440 player.hls.playlists.media(player.hls.selectPlaylist());
441 });
442 };
443
444 drainBuffer = function(event) {
445 var
446 i = 0,
447 mediaIndex,
448 playlist,
449 offset,
450 tags,
451 segment,
452
453 ptsTime,
454 segmentOffset;
455
456 if (!segmentBuffer.length) {
457 return;
458 }
459
460 mediaIndex = segmentBuffer[0].mediaIndex;
461 playlist = segmentBuffer[0].playlist;
462 offset = segmentBuffer[0].offset;
463 tags = segmentBuffer[0].tags;
464 segment = playlist.segments[mediaIndex];
465
466 event = event || {};
467
468 // abort() clears any data queued in the source buffer so wait
469 // until it empties before calling it when a discontinuity is
470 // next in the buffer
471 if (segment.discontinuity) {
472 if (event.type !== 'waiting') {
473 return;
474 }
475 player.hls.sourceBuffer.abort();
476 }
477
420 // if we're refilling the buffer after a seek, scan through the muxed 478 // if we're refilling the buffer after a seek, scan through the muxed
421 // FLV tags until we find the one that is closest to the desired 479 // FLV tags until we find the one that is closest to the desired
422 // playback time 480 // playback time
423 if (typeof offset === 'number') { 481 if (typeof offset === 'number') {
424 (function() { 482 segmentOffset = duration(playlist, 0, mediaIndex) * 1000;
425 var tag = segmentParser.getTags()[0], 483 ptsTime = offset - segmentOffset + tags[0].pts;
426 ptsnaught = tag.pts,
427 playlist = player.hls.playlists.media(),
428 segmentOffset,
429 ptsTime;
430
431 segmentOffset = duration(playlist, 0, player.hls.mediaIndex) * 1000;
432 484
433 ptsTime = offset - segmentOffset + ptsnaught; 485 while (tags[i].pts < ptsTime) {
434 486 i++;
435 for (; tag && tag.pts < ptsTime; tag = segmentParser.getTags()[0]) {
436 segmentParser.getNextTag();
437 } 487 }
438 488
439 // tell the SWF where we will be seeking to 489 // tell the SWF where we will be seeking to
440 player.hls.el().vjs_setProperty('currentTime', (tag.pts - ptsnaught + segmentOffset) * 0.001); 490 player.hls.el().vjs_setProperty('currentTime', (tags[i].pts - tags[0].pts + segmentOffset) * 0.001);
491
492 tags = tags.slice(i);
441 493
442 lastSeekedTime = null; 494 lastSeekedTime = null;
443 })();
444 } 495 }
445 496
446 while (segmentParser.tagsAvailable()) { 497 for (i = 0; i < tags.length; i++) {
447 // queue up the bytes to be appended to the SourceBuffer 498 // queue up the bytes to be appended to the SourceBuffer
448 // the queue gives control back to the browser between tags 499 // the queue gives control back to the browser between tags
449 // so that large segments don't cause a "hiccup" in playback 500 // so that large segments don't cause a "hiccup" in playback
450 501
451 player.hls.sourceBuffer.appendBuffer(segmentParser.getNextTag().bytes, 502 player.hls.sourceBuffer.appendBuffer(tags[i].bytes, player);
452 player);
453
454 } 503 }
455 504
456 player.hls.mediaIndex++; 505 // we're done processing this segment
506 segmentBuffer.shift();
457 507
458 if (player.hls.mediaIndex === player.hls.playlists.media().segments.length) { 508 if (mediaIndex === playlist.segments.length) {
459 mediaSource.endOfStream(); 509 mediaSource.endOfStream();
460 } 510 }
461
462 // figure out what stream the next segment should be downloaded from
463 // with the updated bandwidth information
464 player.hls.playlists.media(player.hls.selectPlaylist());
465 });
466 }; 511 };
467 512
468 // load the MediaSource into the player 513 // load the MediaSource into the player
...@@ -481,9 +526,12 @@ var ...@@ -481,9 +526,12 @@ var
481 player.hls.playlists.on('loadedmetadata', function() { 526 player.hls.playlists.on('loadedmetadata', function() {
482 oldMediaPlaylist = player.hls.playlists.media(); 527 oldMediaPlaylist = player.hls.playlists.media();
483 528
484 // periodicaly check if the buffer needs to be refilled 529 // periodically check if new data needs to be downloaded or
530 // buffered data should be appended to the source buffer
485 fillBuffer(); 531 fillBuffer();
486 player.on('timeupdate', fillBuffer); 532 player.on('timeupdate', fillBuffer);
533 player.on('timeupdate', drainBuffer);
534 player.on('waiting', drainBuffer);
487 535
488 player.trigger('loadedmetadata'); 536 player.trigger('loadedmetadata');
489 }); 537 });
......
...@@ -840,8 +840,11 @@ test('calls abort() on the SourceBuffer before seeking', function() { ...@@ -840,8 +840,11 @@ test('calls abort() on the SourceBuffer before seeking', function() {
840 standardXHRResponse(requests[0]); 840 standardXHRResponse(requests[0]);
841 standardXHRResponse(requests[1]); 841 standardXHRResponse(requests[1]);
842 842
843 // seek to 7s 843 // drainBuffer() uses the first PTS value to account for any timestamp discontinuities in the stream
844 // adding a tag with a PTS of zero looks like a stream with no discontinuities
845 tags.push({ pts: 0, bytes: 0 });
844 tags.push({ pts: 7000, bytes: 7 }); 846 tags.push({ pts: 7000, bytes: 7 });
847 // seek to 7s
845 player.currentTime(7); 848 player.currentTime(7);
846 standardXHRResponse(requests[2]); 849 standardXHRResponse(requests[2]);
847 850
......