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.
Showing
2 changed files
with
83 additions
and
32 deletions
... | @@ -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 | ... | ... |
-
Please register or sign in to post a comment