Skip to content

fix: recover from QuotaExceededError by evicting back buffer and retr…#7749

Open
alchemyyy wants to merge 1 commit intovideo-dev:masterfrom
alchemyyy:fix/quota-exceeded-back-buffer-eviction
Open

fix: recover from QuotaExceededError by evicting back buffer and retr…#7749
alchemyyy wants to merge 1 commit intovideo-dev:masterfrom
alchemyyy:fix/quota-exceeded-back-buffer-eviction

Conversation

@alchemyyy
Copy link

@alchemyyy alchemyyy commented Mar 12, 2026

This PR will...

Recover gracefully from QuotaExceededError during SourceBuffer.appendBuffer() by intercepting the error, evicting the minimum number of back buffer segments needed to make room, and retrying the append with the original data still in memory — eliminating the segment re-download loop.

Why is this Pull Request needed?

The current BUFFER_FULL_ERROR recovery path reduces maxMaxBufferLength (a time-based knob) but never evicts back buffer when backBufferLength is Infinity (the default). This causes a loop where the same segment is re-downloaded repeatedly because the SourceBuffer is still full — the back buffer is the problem but nothing removes it. This is documented in #6776 and #6711, both tagged Revisit-at-later-release-cycle.

This PR fixes the root cause: when appendBuffer throws QuotaExceededError, the buffer-controller now queries the fragment tracker for actual byte sizes of back buffer segments, calculates exactly how many oldest segments to remove to fit the new data, queues a SourceBuffer.remove() followed by a retry of the same append, and does all of this without emitting an error event or discarding the segment data. If the retry also fails (e.g. too early in playback with no back buffer to evict), it falls through to the existing BUFFER_FULL_ERROR path unchanged.

Are there any points in the code the reviewer needs to double check?

  • The insertNext method on BufferOperationQueue splices operations after the current (failed) one. After onError returns, shiftAndExecuteNext removes the failed op, so the remove operation executes next, followed by the retry append. Worth verifying this sequencing holds in all edge cases (e.g. concurrent audio/video queues).
  • getBackBufferEvictionEnd uses stats.loaded (actual bytes received, always populated after fragment completion) as the primary byte measure, falling back to frag.byteLength (from stats.total / Content-Length). If neither is available for a fragment (e.g. preloaded or side-loaded), that fragment is skipped in the byte calculation, which could underestimate freeable space.
  • The quotaEvictionAttempted flag is scoped per append operation closure, so each new segment gets one eviction attempt. A class-level _quotaEvictionPending flag per SourceBuffer type prevents progressive loading chunks from each independently triggering eviction or emitting BUFFER_FULL_ERROR — subsequent QuotaExceededErrors while an eviction is in flight piggyback by queuing retries behind the pending remove rather than starting new evictions.

Resolves issues:

Fixes #6776
Fixes #6711

Checklist

  • changes have been done against master branch, and PR does not conflict
  • new unit / functional tests have been added (whenever applicable)
  • API or design changes are documented in API.md

…ying append

When a SourceBuffer append throws QuotaExceededError, intercept the error
before it propagates, evict the minimum number of back buffer segments
needed to fit the new data, and retry the append with the original data
still in memory — avoiding the re-download loop that plagues hls.js
upstream (video-dev#6776, video-dev#6711).

Eviction uses stats.loaded (actual bytes received, always populated after
fragment completion) rather than frag.byteLength (which depends on
stats.total from Content-Length and may be 0 during progressive loading
or with chunked transfer), so the calculation is reliable regardless of
how segments are served.

A class-level _quotaEvictionPending flag per SourceBuffer type prevents
progressive loading chunks from each independently triggering eviction
or emitting BUFFER_FULL_ERROR. The first QuotaExceededError triggers
eviction; subsequent errors on the same type piggyback by queuing retries
behind the pending remove. If eviction + retry fails, falls through to
the existing BUFFER_FULL_ERROR path.

Changes:
- buffer-controller: on QuotaExceededError, calculate eviction target
  and queue a remove + retry append instead of emitting an error
- buffer-controller: add _quotaEvictionPending flag gating per type
- buffer-controller: add getQuotaEvictionFlushOp that clears the
  pending flag on complete/error
- fragment-tracker: add getBackBufferEvictionEnd() which walks buffered
  fragments oldest-first accumulating byte sizes to find the minimum
  eviction point
- buffer-operation-queue: add insertNext() to queue operations after
  the current one for remove-then-retry sequencing
@alchemyyy alchemyyy force-pushed the fix/quota-exceeded-back-buffer-eviction branch from b0d1280 to 8661a00 Compare March 12, 2026 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fragments keeps reloading in a loop There is a segment in the hls video that loads many times (QuotaExceededError)

1 participant