21.2 review remediation: pause-spin, OQ7 comment, rename, C2 cross-check
Skip the back-pressure interop poll while paused (UC5). Document complete() draining the stash in full by design. Rename scheduler isProductionPaused to evaluateProductionPause (latch-advancing); window exposure name unchanged.
This commit is contained in:
@@ -46,9 +46,10 @@ const DEFAULT_BACK_RETAIN_SECONDS = 10;
|
||||
*
|
||||
* Provisional time-based defaults (OQ1 — 21.4 tunes them):
|
||||
* - HIGH (30 s): the most decoded lookahead we hold ahead of the playhead before throttling.
|
||||
* Comfortably above the playback-start minimum (6 buffers ≈ a second or two), so C2 holds —
|
||||
* first audio never waits on a throttle (the high-water is reached only well after playback
|
||||
* is already running).
|
||||
* Comfortably above the playback-start minimum (`AudioPlayer.minBuffersForPlayback = 6`
|
||||
* buffers, each typically 0.06 – 1 s depending on format/chunk size; at most a few seconds
|
||||
* even at the high end), so C2 holds — first audio never waits on a throttle (the high-water
|
||||
* is reached only well after playback is already running).
|
||||
* - LOW (15 s): resume producing here. Kept generous so the forward fill never drains to the
|
||||
* ~500 ms scheduler lookahead under normal network jitter (AC3 — no starvation).
|
||||
*
|
||||
@@ -93,7 +94,7 @@ export class PlaybackScheduler {
|
||||
private backRetainSeconds: number = DEFAULT_BACK_RETAIN_SECONDS;
|
||||
|
||||
// Forward back-pressure water-marks + the OQ3 hard byte ceiling (Phase 21.2). This is the
|
||||
// single shared window policy (OQ6): both producers read isProductionPaused() and honor it
|
||||
// single shared window policy (OQ6): both producers call evaluateProductionPause() and honor it
|
||||
// in their own way — the C# read loop stops ReadAsync, the Opus feed stops demux/decode.
|
||||
private forwardHighWaterSeconds: number = DEFAULT_FORWARD_HIGH_WATER_SECONDS;
|
||||
private forwardLowWaterSeconds: number = DEFAULT_FORWARD_LOW_WATER_SECONDS;
|
||||
@@ -103,6 +104,7 @@ export class PlaybackScheduler {
|
||||
// stay paused until it drains below the low-water mark, so the two producers do not flap
|
||||
// on/off around a single threshold (and a paused producer does not resume for one chunk only
|
||||
// to re-pause immediately). False until first crossing; flips on the band edges.
|
||||
// Mutated by evaluateProductionPause() — named to signal the state-advance on each call.
|
||||
private productionPaused_: boolean = false;
|
||||
|
||||
// Callbacks
|
||||
@@ -215,8 +217,13 @@ export class PlaybackScheduler {
|
||||
* drained below the low-water mark AND the byte estimate is back under the ceiling. The
|
||||
* byte-ceiling test has no separate low-water band — it is the hard guard rail, so it releases
|
||||
* as soon as eviction brings the footprint back under the cap.
|
||||
*
|
||||
* Named `evaluateProductionPause` (not `isProductionPaused`) because each call may ADVANCE the
|
||||
* hysteresis latch (`productionPaused_`), making it a state-advancing evaluation, not a pure
|
||||
* read. `AudioPlayer.isProductionPaused()` is the pure-predicate wrapper exposed to callers
|
||||
* outside the scheduler.
|
||||
*/
|
||||
isProductionPaused(): boolean {
|
||||
evaluateProductionPause(): boolean {
|
||||
const lookahead = this.getForwardLookaheadSeconds();
|
||||
const overByteCeiling = this.maxDecodedBytes > 0 && this.getDecodedByteEstimate() > this.maxDecodedBytes;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user