Byte cap (96MB) unchanged as the hard OOM bound; the wider time window only lets sparse Opus use existing memory headroom to ride out decode jitter. Diag logs pin whether the block is back-pressure or decode throughput.
Tracks whose total audio falls below the playback-start threshold (Opus <1s lead, WAV <6 buffers) silently hung loaded-but-not-playing. After MarkStreamCompleteAsync, call TryStartPlaybackAsync when _streamingPlaybackStarted is still false so the scheduler drains its buffers and fires onPlaybackEnded exactly once.
pause() clears underrun_ so setStreamComplete can't fire TrackEnded while paused; resetToStart() resets streamComplete. Prior fix: underrun_ park + streamComplete discriminator prevent the Opus-startup false-end. Tests: 18 PlaybackScheduler cases including pause-during-underrun and underrun->resume->genuine-end-once.
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.
Shared scheduler fill signal (forward water-marks + hard byte cap) pauses
the C# read loop above high-water and, for Opus, stops the demux/decode
feed so WebCodecs queues stay near-empty. Routes through the existing
cancellation discipline; releases the latch on clear/seek.
The inclusive <= bound is correct; comments now say 'at or behind'. New
test drives eviction through the real onended trigger with a live mid-array
source pinning the frontier.
Drop already-played buffers from the front while advancing the time
anchor so position/index bookkeeping stays exact. Shared by both decode
paths, no format branch. Back-retain is a config seam for 21.2.