Skip to content

Commit

Permalink
Fix some issues that may arise on BUFFER_FULL situations
Browse files Browse the repository at this point in the history
I'm working on heuristics to determine adaptively how much the current
device can hold in its audio and video buffers.

To quickly iterate on this, I'm setting a very high (or even `Infinity`)
`wantedBufferAhead` to quickly fill-up memory until the device has to
perform a strategy to free it.

While doing this, I saw a collection of issues:

  1. When calling the MSE `SourceBuffer.prototype.remove` API to remove
     buffer (which we already do in some clean-up strategies), putting
     an end timestamp equal to the start timestamp leads to an Error (as
     defined by MSE).

     A long-term fix would be to just avoid doing the MSE `remove` call
     as close as possible to our MSE abstraction, but for now I also
     added a check at the initial call (which also makes sense).

     I'm thinking of also adding the long-term fix, but not in this PR
     as I want it to have the less risks possible.

  2. When a `QuotaExceededError` is received after a push, we trigger a
     `BUFFER_FULL_ERROR` error, which is then handled by, waiting a
     little, then reducing the `wantedBufferAhead` value progressively
     through a ratio and retrying.

     If after either the `wantedBufferAhead` is too low (less than 2
     seconds) or the ratio is too low (less or equal to 0.05), we
     trigger the error through the API.

     Turns out that last part was broken. We never triggered the error,
     leading to possibilities such as infinite rebuffering (in extreme
     cases hopefully never really encountered).

  3. The logic in (2) never considered that `wantedBufferAhead` could be
     set to `Infinity`, and dividing `Infinity` is not a very bright
     idea here. To make it work I decided that when there's a ratio set
     to less than `1`, a `wantedBufferAhead` set to `Infinity` would be
     equal to 5 minutes.
  • Loading branch information
peaBerberian committed Sep 18, 2024
1 parent f71ac51 commit fd26e84
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 9 deletions.
25 changes: 18 additions & 7 deletions src/core/stream/adaptation/adaptation_stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ export default function AdaptationStream(
const bufferGoal = createMappedReference(
wantedBufferAhead,
(prev) => {
return prev * getBufferGoalRatio(representation);
return getBufferGoal(representation, prev);
},
bufferGoalCanceller.signal,
);
Expand All @@ -406,10 +406,11 @@ export default function AdaptationStream(
const lastBufferGoalRatio = bufferGoalRatioMap.get(representation.id) ?? 1;
// 70%, 49%, 34.3%, 24%, 16.81%, 11.76%, 8.24% and 5.76%
const newBufferGoalRatio = lastBufferGoalRatio * 0.7;
if (newBufferGoalRatio <= 0.05 || wba * newBufferGoalRatio <= 2) {
throw formattedError;
}
bufferGoalRatioMap.set(representation.id, newBufferGoalRatio);
if (newBufferGoalRatio <= 0.05 || getBufferGoal(representation, wba) <= 2) {
representationStreamCallbacks.error(formattedError);
return;
}

// We wait 4 seconds to let the situation evolve by itself before
// retrying loading segments with a lower buffer goal
Expand Down Expand Up @@ -481,15 +482,25 @@ export default function AdaptationStream(
}

/**
* @param {Object} representation
* Returns how much media data should be pre-buffered for this
* `Representation`, according to the `wantedBufferAhead` setting and previous
* issues encountered with that `Representation`.
* @param {Object} representation - The `Representation` you want to buffer.
* @param {number} wba - The value of `wantedBufferAhead` set by the user.
* @returns {number}
*/
function getBufferGoalRatio(representation: IRepresentation): number {
function getBufferGoal(representation: IRepresentation, wba: number): number {
const oldBufferGoalRatio = bufferGoalRatioMap.get(representation.id);
const bufferGoalRatio = oldBufferGoalRatio !== undefined ? oldBufferGoalRatio : 1;
if (oldBufferGoalRatio === undefined) {
bufferGoalRatioMap.set(representation.id, bufferGoalRatio);
}
return bufferGoalRatio;
if (bufferGoalRatio < 1 && wba === Infinity) {
// When `wba` is equal to `Infinity`, dividing it will still make it equal
// to `Infinity`. To make the `bufferGoalRatio` still have an effect, we
// just starts from a `wba` set to the high value of 5 minutes.
return 5 * 60 * 1000 * bufferGoalRatio;
}
return wba * bufferGoalRatio;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ export default async function appendSegmentToBuffer<T>(
log.warn("Stream: Running garbage collector");
const start = Math.max(currentPos - 5, 0);
const end = currentPos + bufferGoal.getValue() + 12;
await segmentSink.removeBuffer(0, start);
await segmentSink.removeBuffer(end, Number.MAX_VALUE);
if (start > 0) {
await segmentSink.removeBuffer(0, start);
}
if (end < Number.MAX_VALUE) {
await segmentSink.removeBuffer(end, Number.MAX_VALUE);
}
await sleep(200);
if (cancellationSignal.cancellationError !== null) {
throw cancellationSignal.cancellationError;
Expand Down

0 comments on commit fd26e84

Please sign in to comment.