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>
This commit is contained in:
parent
4a45a699ae
commit
85cf943850
@ -120,6 +120,8 @@ AVMerge.prototype.printDebuggingInfo = function() {
|
|||||||
reportDebug('audioSource:', this.videoSource);
|
reportDebug('audioSource:', this.videoSource);
|
||||||
reportDebug('video sidx:', this.videoStream.sidx);
|
reportDebug('video sidx:', this.videoStream.sidx);
|
||||||
reportDebug('audio sidx:', this.audioStream.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 duration:', this.video.duration);
|
||||||
reportDebug('video current time:', this.video.currentTime);
|
reportDebug('video current time:', this.video.currentTime);
|
||||||
reportDebug('mediaSource.readyState:', this.mediaSource.readyState);
|
reportDebug('mediaSource.readyState:', this.mediaSource.readyState);
|
||||||
@ -154,7 +156,7 @@ function Stream(avMerge, source, startTime, avRatio) {
|
|||||||
this.mediaSource = avMerge.mediaSource;
|
this.mediaSource = avMerge.mediaSource;
|
||||||
this.sidx = null;
|
this.sidx = null;
|
||||||
this.appendRetries = 0;
|
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 = this.mediaSource.addSourceBuffer(this.mimeCodec);
|
||||||
this.sourceBuffer.mode = 'segments';
|
this.sourceBuffer.mode = 'segments';
|
||||||
this.sourceBuffer.addEventListener('error', (e) => {
|
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 init_end = this.initRange.end - this.initRange.start + 1;
|
||||||
var index_start = this.indexRange.start - this.initRange.start;
|
var index_start = this.indexRange.start - this.initRange.start;
|
||||||
var index_end = this.indexRange.end - this.initRange.start + 1;
|
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));
|
this.setupSegments(buffer.slice(index_start, index_end));
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -187,7 +189,7 @@ Stream.prototype.setup = async function(){
|
|||||||
this.url,
|
this.url,
|
||||||
this.initRange.start,
|
this.initRange.start,
|
||||||
this.initRange.end,
|
this.initRange.end,
|
||||||
this.appendSegment.bind(this, null),
|
this.appendSegment.bind(this, false, null),
|
||||||
);
|
);
|
||||||
// sidx (segment index) table
|
// sidx (segment index) table
|
||||||
fetchRange(
|
fetchRange(
|
||||||
@ -212,7 +214,7 @@ Stream.prototype.close = function() {
|
|||||||
this.mediaSource.removeSourceBuffer(this.sourceBuffer);
|
this.mediaSource.removeSourceBuffer(this.sourceBuffer);
|
||||||
this.updateendEvt.remove();
|
this.updateendEvt.remove();
|
||||||
}
|
}
|
||||||
Stream.prototype.appendSegment = function(segmentIdx, chunk) {
|
Stream.prototype.appendSegment = function(segmentIdx, forSeek, chunk) {
|
||||||
if (this.closed)
|
if (this.closed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -221,7 +223,7 @@ Stream.prototype.appendSegment = function(segmentIdx, chunk) {
|
|||||||
// cannot append right now, schedule for updateend
|
// cannot append right now, schedule for updateend
|
||||||
if (this.sourceBuffer.updating) {
|
if (this.sourceBuffer.updating) {
|
||||||
this.reportDebug('sourceBuffer updating, queueing for later');
|
this.reportDebug('sourceBuffer updating, queueing for later');
|
||||||
this.appendQueue.push([segmentIdx, chunk]);
|
this.appendQueue.push([segmentIdx, forSeek, chunk]);
|
||||||
if (this.appendQueue.length > 2){
|
if (this.appendQueue.length > 2){
|
||||||
this.reportWarning('appendQueue length:', this.appendQueue.length);
|
this.reportWarning('appendQueue length:', this.appendQueue.length);
|
||||||
}
|
}
|
||||||
@ -236,22 +238,80 @@ Stream.prototype.appendSegment = function(segmentIdx, chunk) {
|
|||||||
if (e.name !== 'QuotaExceededError') {
|
if (e.name !== 'QuotaExceededError') {
|
||||||
throw e;
|
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
|
// not to delete current one
|
||||||
var currentSegment = this.getSegmentIdx(this.video.currentTime);
|
var currentSegment = this.getSegmentIdx(this.video.currentTime);
|
||||||
this.reportWarning('QuotaExceededError. Deleting segments.');
|
|
||||||
var numDeleted = 0;
|
var numDeleted = 0;
|
||||||
var i = 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) {
|
while (numDeleted < 3 && i < currentSegment) {
|
||||||
let entry = this.sidx.entries[i];
|
if (this.sidx.entries[i].have) {
|
||||||
let start = entry.tickStart/this.sidx.timeScale;
|
toDelete.push(i)
|
||||||
let end = (entry.tickEnd+1)/this.sidx.timeScale;
|
|
||||||
if (entry.have) {
|
|
||||||
this.reportWarning('Deleting segment', i);
|
|
||||||
this.sourceBuffer.remove(start, end);
|
|
||||||
numDeleted++;
|
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) {
|
Stream.prototype.getSegmentIdx = function(videoTime) {
|
||||||
@ -370,7 +430,7 @@ Stream.prototype.fetchSegment = function(segmentIdx) {
|
|||||||
this.url,
|
this.url,
|
||||||
entry.start,
|
entry.start,
|
||||||
entry.end,
|
entry.end,
|
||||||
this.appendSegment.bind(this, segmentIdx),
|
this.appendSegment.bind(this, segmentIdx, this.avMerge.seeking),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) {
|
Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) {
|
||||||
@ -390,7 +450,7 @@ Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) {
|
|||||||
|
|
||||||
this.fetchSegment(segmentIdx);
|
this.fetchSegment(segmentIdx);
|
||||||
}
|
}
|
||||||
Stream.prototype.handleSeek = async function() {
|
Stream.prototype.handleSeek = function() {
|
||||||
var segmentIdx = this.getSegmentIdx(this.video.currentTime);
|
var segmentIdx = this.getSegmentIdx(this.video.currentTime);
|
||||||
this.fetchSegmentIfNeeded(segmentIdx);
|
this.fetchSegmentIfNeeded(segmentIdx);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user