From fc0fa9aaba9caa0b849a91f1c989e52ab4a7ca15 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Wed, 1 Sep 2021 17:32:18 -0700 Subject: av-merge: Fix segments not properly reappended during QuotaExceeded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues fixed: 1. The append was only retried if it was the result of a seek event. But if the video is paused (such as if the video was finished and the user seeks back to the beginning), the seek won't happen because the MediaSource will not issue a sourceopen until the user plays the video. A better strategy that solves the true issue is to retry the append if it is for the segment corresponding to the current time, since that is critical to get immediately. 2. If the append was not retried, entry.requested was not getting marked as false, so it would refuse to ever rerequest the segment, so it would stall. Set it to false if we decide not to retry the append, so it can be rerequested later. Signed-off-by: Jesús --- youtube/static/js/av-merge.js | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) (limited to 'youtube/static/js/av-merge.js') diff --git a/youtube/static/js/av-merge.js b/youtube/static/js/av-merge.js index dfbf3c9..6a5e044 100644 --- a/youtube/static/js/av-merge.js +++ b/youtube/static/js/av-merge.js @@ -166,7 +166,7 @@ function Stream(avMerge, source, startTime, avRatio) { this.mediaSource = avMerge.mediaSource; this.sidx = null; this.appendRetries = 0; - this.appendQueue = []; // list of [segmentIdx, forSeek, data] + this.appendQueue = []; // list of [segmentIdx, data] this.sourceBuffer = this.mediaSource.addSourceBuffer(this.mimeCodec); this.sourceBuffer.mode = 'segments'; this.sourceBuffer.addEventListener('error', (e) => { @@ -189,7 +189,7 @@ Stream.prototype.setup = async function(){ var init_end = this.initRange.end - this.initRange.start + 1; var index_start = this.indexRange.start - this.initRange.start; var index_end = this.indexRange.end - this.initRange.start + 1; - this.appendSegment(null, false, buffer.slice(0, init_end)); + this.appendSegment(null, buffer.slice(0, init_end)); this.setupSegments(buffer.slice(index_start, index_end)); } ) @@ -199,7 +199,7 @@ Stream.prototype.setup = async function(){ this.url, this.initRange.start, this.initRange.end, - this.appendSegment.bind(this, false, null), + this.appendSegment.bind(this, null), ); // sidx (segment index) table fetchRange( @@ -224,7 +224,7 @@ Stream.prototype.close = function() { this.mediaSource.removeSourceBuffer(this.sourceBuffer); this.updateendEvt.remove(); } -Stream.prototype.appendSegment = function(segmentIdx, forSeek, chunk) { +Stream.prototype.appendSegment = function(segmentIdx, chunk) { if (this.closed) return; @@ -233,7 +233,7 @@ Stream.prototype.appendSegment = function(segmentIdx, forSeek, chunk) { // cannot append right now, schedule for updateend if (this.sourceBuffer.updating) { this.reportDebug('sourceBuffer updating, queueing for later'); - this.appendQueue.push([segmentIdx, forSeek, chunk]); + this.appendQueue.push([segmentIdx, chunk]); if (this.appendQueue.length > 2){ this.reportWarning('appendQueue length:', this.appendQueue.length); } @@ -267,25 +267,26 @@ Stream.prototype.appendSegment = function(segmentIdx, forSeek, chunk) { this.reportDebug('New buffer target:', this.bufferTarget); } - // Delete 3 segments (arbitrary) from buffer, making sure + // Delete 10 segments (arbitrary) from buffer, making sure // not to delete current one var currentSegment = this.getSegmentIdx(this.video.currentTime); var numDeleted = 0; var i = 0; + const DELETION_TARGET = 10; var toDelete = []; // See below for why we have to schedule it this.reportDebug('Deleting segments from beginning of buffer.'); - while (numDeleted < 3 && i < currentSegment) { + while (numDeleted < DELETION_TARGET && i < currentSegment) { if (this.sidx.entries[i].have) { toDelete.push(i) numDeleted++; } i++; } - if (numDeleted < 3) + if (numDeleted < DELETION_TARGET) this.reportDebug('Deleting segments from end of buffer.'); i = this.sidx.entries.length - 1; - while (numDeleted < 3 && i > currentSegment) { + while (numDeleted < DELETION_TARGET && i > currentSegment) { if (this.sidx.entries[i].have) { toDelete.push(i) numDeleted++; @@ -297,15 +298,21 @@ Stream.prototype.appendSegment = function(segmentIdx, forSeek, chunk) { // state, and remove cannot be called until it is done. So we have // to delete on the updateend event for subsequent ones. var removeFinishedEvent; + var deletedStuff = (toDelete.length !== 0) var deleteSegment = () => { if (toDelete.length === 0) { - // If QuotaExceeded happened during seeking, retry the append - // Pass false as forSeek to avoid infinite looping if it - // doesn't work. Rescheduling will take care of updating=true - // problem. removeFinishedEvent.remove(); - if (forSeek) { - this.appendSegment(segmentIdx, false, chunk); + // If QuotaExceeded happened for current segment, retry the + // append + // Rescheduling will take care of updating=true problem. + // Also check that we found segments to delete, to avoid + // infinite looping if we can't delete anything + if (segmentIdx === currentSegment && deletedStuff) { + this.reportDebug('Retrying appendSegment for', segmentIdx); + this.appendSegment(segmentIdx, chunk); + } else { + this.reportDebug('Not retrying segment', segmentIdx); + this.sidx.entries[segmentIdx].requested = false; } return; } @@ -440,7 +447,7 @@ Stream.prototype.fetchSegment = function(segmentIdx) { this.url, entry.start, entry.end, - this.appendSegment.bind(this, segmentIdx, this.avMerge.seeking), + this.appendSegment.bind(this, segmentIdx), ); } Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) { -- cgit v1.2.3