aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/static/js/av-merge.js
diff options
context:
space:
mode:
authorJames Taylor <user234683@users.noreply.github.com>2021-08-30 19:48:24 -0700
committerJesús <heckyel@hyperbola.info>2021-08-31 11:37:36 -0500
commit85cf9438506374a354f34bff68a61f9d7c3db1b3 (patch)
tree368a34a4210baa34a35306afe4394c48789dacd6 /youtube/static/js/av-merge.js
parent4a45a699ae3bc92224fab1042e1c1377fb6a7c54 (diff)
downloadyt-local-85cf9438506374a354f34bff68a61f9d7c3db1b3.tar.lz
yt-local-85cf9438506374a354f34bff68a61f9d7c3db1b3.tar.xz
yt-local-85cf9438506374a354f34bff68a61f9d7c3db1b3.zip
av-merge: Fix handling of QuotaExceededError
Many things fixed: - Delete from end of video in addition to from beginning. Firefox automatically deletes from the beginning already. - Increment i in the while loop (oops) - Calling .remove takes time for the sourceBuffer to perform, and it will be in the updating=true state. Continuing to delete more would give an error. Waits until the updateend event is fired before deleting more segments. - Retry appendBuffer if the quota was exceeded during a seek append Signed-off-by: Jesús <heckyel@hyperbola.info>
Diffstat (limited to 'youtube/static/js/av-merge.js')
-rw-r--r--youtube/static/js/av-merge.js90
1 files changed, 75 insertions, 15 deletions
diff --git a/youtube/static/js/av-merge.js b/youtube/static/js/av-merge.js
index d09fa0b..3198857 100644
--- a/youtube/static/js/av-merge.js
+++ b/youtube/static/js/av-merge.js
@@ -120,6 +120,8 @@ AVMerge.prototype.printDebuggingInfo = function() {
reportDebug('audioSource:', this.videoSource);
reportDebug('video sidx:', this.videoStream.sidx);
reportDebug('audio sidx:', this.audioStream.sidx);
+ reportDebug('video updating', this.videoStream.sourceBuffer.updating);
+ reportDebug('audio updating', this.audioStream.sourceBuffer.updating);
reportDebug('video duration:', this.video.duration);
reportDebug('video current time:', this.video.currentTime);
reportDebug('mediaSource.readyState:', this.mediaSource.readyState);
@@ -154,7 +156,7 @@ function Stream(avMerge, source, startTime, avRatio) {
this.mediaSource = avMerge.mediaSource;
this.sidx = null;
this.appendRetries = 0;
- this.appendQueue = []; // list of [segmentIdx, data]
+ this.appendQueue = []; // list of [segmentIdx, forSeek, data]
this.sourceBuffer = this.mediaSource.addSourceBuffer(this.mimeCodec);
this.sourceBuffer.mode = 'segments';
this.sourceBuffer.addEventListener('error', (e) => {
@@ -177,7 +179,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, buffer.slice(0, init_end));
+ this.appendSegment(null, false, buffer.slice(0, init_end));
this.setupSegments(buffer.slice(index_start, index_end));
}
)
@@ -187,7 +189,7 @@ Stream.prototype.setup = async function(){
this.url,
this.initRange.start,
this.initRange.end,
- this.appendSegment.bind(this, null),
+ this.appendSegment.bind(this, false, null),
);
// sidx (segment index) table
fetchRange(
@@ -212,7 +214,7 @@ Stream.prototype.close = function() {
this.mediaSource.removeSourceBuffer(this.sourceBuffer);
this.updateendEvt.remove();
}
-Stream.prototype.appendSegment = function(segmentIdx, chunk) {
+Stream.prototype.appendSegment = function(segmentIdx, forSeek, chunk) {
if (this.closed)
return;
@@ -221,7 +223,7 @@ Stream.prototype.appendSegment = function(segmentIdx, chunk) {
// cannot append right now, schedule for updateend
if (this.sourceBuffer.updating) {
this.reportDebug('sourceBuffer updating, queueing for later');
- this.appendQueue.push([segmentIdx, chunk]);
+ this.appendQueue.push([segmentIdx, forSeek, chunk]);
if (this.appendQueue.length > 2){
this.reportWarning('appendQueue length:', this.appendQueue.length);
}
@@ -236,22 +238,80 @@ Stream.prototype.appendSegment = function(segmentIdx, chunk) {
if (e.name !== 'QuotaExceededError') {
throw e;
}
- // Delete 3 segments (arbitrary) from beginning of buffer, making sure
+ this.reportWarning('QuotaExceededError.');
+
+ // Count how many bytes are in buffer to update buffering target,
+ // updating .have as well for when we need to delete segments
+ var bytesInBuffer = 0;
+ for (var i = 0; i < this.sidx.entries.length; i++) {
+ if (this.segmentInBuffer(i))
+ bytesInBuffer += this.sidx.entries[i].referencedSize;
+ else if (this.sidx.entries[i].have) {
+ this.sidx.entries[i].have = false;
+ this.sidx.entries[i].requested = false;
+ }
+ }
+ bytesInBuffer = Math.floor(4/5*bytesInBuffer);
+ if (bytesInBuffer < this.bufferTarget) {
+ this.bufferTarget = bytesInBuffer;
+ this.reportDebug('New buffer target:', this.bufferTarget);
+ }
+
+ // Delete 3 segments (arbitrary) from buffer, making sure
// not to delete current one
var currentSegment = this.getSegmentIdx(this.video.currentTime);
- this.reportWarning('QuotaExceededError. Deleting segments.');
var numDeleted = 0;
var i = 0;
+ var toDelete = []; // See below for why we have to schedule it
+ this.reportDebug('Deleting segments from beginning of buffer.');
while (numDeleted < 3 && i < currentSegment) {
- let entry = this.sidx.entries[i];
- let start = entry.tickStart/this.sidx.timeScale;
- let end = (entry.tickEnd+1)/this.sidx.timeScale;
- if (entry.have) {
- this.reportWarning('Deleting segment', i);
- this.sourceBuffer.remove(start, end);
+ if (this.sidx.entries[i].have) {
+ toDelete.push(i)
numDeleted++;
}
+ i++;
+ }
+ if (numDeleted < 3)
+ this.reportDebug('Deleting segments from end of buffer.');
+
+ i = this.sidx.entries.length - 1;
+ while (numDeleted < 3 && i > currentSegment) {
+ if (this.sidx.entries[i].have) {
+ toDelete.push(i)
+ numDeleted++;
+ }
+ i--;
+ }
+
+ // When calling .remove, the sourceBuffer will go into updating=true
+ // 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 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);
+ }
+ return;
+ }
+ let idx = toDelete.shift();
+ let entry = this.sidx.entries[idx];
+ let start = entry.tickStart/this.sidx.timeScale;
+ let end = (entry.tickEnd+1)/this.sidx.timeScale;
+ this.reportDebug('Deleting segment', idx);
+ this.sourceBuffer.remove(start, end);
+ entry.have = false;
+ entry.requested = false;
}
+ removeFinishedEvent = addEvent(this.sourceBuffer, 'updateend',
+ deleteSegment);
+ if (!this.sourceBuffer.updating)
+ deleteSegment();
}
}
Stream.prototype.getSegmentIdx = function(videoTime) {
@@ -370,7 +430,7 @@ Stream.prototype.fetchSegment = function(segmentIdx) {
this.url,
entry.start,
entry.end,
- this.appendSegment.bind(this, segmentIdx),
+ this.appendSegment.bind(this, segmentIdx, this.avMerge.seeking),
);
}
Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) {
@@ -390,7 +450,7 @@ Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) {
this.fetchSegment(segmentIdx);
}
-Stream.prototype.handleSeek = async function() {
+Stream.prototype.handleSeek = function() {
var segmentIdx = this.getSegmentIdx(this.video.currentTime);
this.fetchSegmentIfNeeded(segmentIdx);
}