3.3.1 — Reliability: two-pass full-codebase audit
·
142 commits
to main
since this release
Reliability release. Every file in the package was reviewed twice in a two-pass audit (the second pass adversarially re-verifying the first pass's fixes against the actual code, git history, and the vendored FFmpeg sources), fixing roughly 60 defects across the engine, removing ~350 lines of dead code, and splitting the three largest files into focused units. No public API changes.
Fixed
Audio-only path (music, podcasts)
- A
CMSampleBuffertiming bug made every coalesced buffer reportsampleCount^2 / sampleRateas its duration (~22 s for a 21 ms buffer), wedging the buffer-ahead gate after the first packet: ~20 ms of audio, seconds of silence, repeating. The FFmpeg-decoded music path now paces correctly. play()afterpause()never resumed the synchronizer (frozen clock); seeks never reset the enqueue high-water mark (a backward seek meant minutes of silence); a seek landing in the EOF drain window played silence and then skipped the track.
Resource leaks
- Every demuxer open leaked its 256 KB AVIO buffer:
avio_context_freedoes not freectx->buffer(verified against the FFmpeg sources). Probe, session, subtitle side-demuxer, and FrameExtractor opens all add up in a long-lived process. - Closing a chunked (no-Content-Length) stream left the download running: one leaked connection, URLSession, and parked thread per closed session.
- The same streaming mode gained backpressure: a paused consumer no longer buffers the rest of the file into memory at line rate (the transfer suspends above a 64 MB watermark and resumes on drain).
AVChannelLayoutcopies are now uninitialized after use (per-session leak on custom-order layouts).
Teardown and supersession races
stop()(the player-dismiss path, often on the main thread) no longer blocks for seconds behind a producer restart's wait + network seek.- A scheduled audio-track switch could resurrect a dismissed session (pipeline rebuilt, audio playing after dismiss) or kill a newer load and play the old URL; it now validates the load generation captured at scheduling time.
- A seek racing
stop()no longer publishes clock state and.playinginto the torn-down engine. - Subtitle track switches: a superseded decode task could overwrite the successor's cues, clear its loading spinner, or hijack its side-demuxer abort handle.
- The shared audio AVPlayer host no longer has a reorder window where a superseded track load could put its item back after the successor finished.
Stale state
- Live TV started after an HDR10 film kept publishing
.hdr10for the whole live session. - Switching from video to music left the old video AVPlayer alive and published.
- The public
stop()now clears the session identity, so a background-return reload hook can no longer revive a stopped session. - The volume setter wrote into every host including the persistent music host (changing the NEXT session's volume); it now routes to the active host only, and a volume set before any session exists is remembered and applied on activation.
Correctness
- Plain-HLG sources signaled
VIDEO-RANGE=PQon the H.264 and HEVC routes; they now signalHLGso the panel negotiates the right EOTF. - Live-variant selection read
AVERAGE-BANDWIDTHasBANDWIDTH(substring match) and could be misled by commas inside quoted attribute values; the attribute parser is anchored and quote-aware (regression-tested). - 8-channel AAC from MPEG-TS was declared stereo in the synthesized AudioSpecificConfig (decoder garbage); 6.1 was declared 7.1.
- Two simultaneous ASS speaker lines with identical timing no longer collapse into one (the dedupe key now includes content).
- A force-unwrap race in the VideoToolbox decode callback could crash on seek; several decoder, renderer, and telemetry data races are now lock-guarded (including a writer-side locking pass over state that readers already locked).
- Keep-alive framing on the loopback server survives a segment file changing size between stat and send (the body now matches the declared Content-Length exactly).
- A failed fragment-cut staging-file open is detected at the failure point instead of silently discarding a full segment.
- The DVR rewind reseed survives eviction races without starting decode on a non-keyframe, and seeks landing in the demux loop's back-pressure wait no longer flash a fast-forward burst of pre-seek frames.
Diagnostics and tooling
- FFmpeg log repeat-collapsing actually works under the custom log callback (the
AV_LOG_SKIP_REPEATEDflag is a no-op there), and multi-part log lines are no longer re-prefixed. - The AVPacket leak counter no longer drifts permanently upward on Dolby Vision Profile 5 sources.
aetherctl:validateno longer deadlocks on reports larger than the pipe buffer; out-of-range and non-finite flag values error instead of crashing; the--drop-afterlive fixture no longer races itself into killing the very reconnect it exists to test, and reconnects now see a monotonic broadcast clock.
Changed
- The three largest files were split into focused units (engine core 4070 to 1851 lines, video session orchestrator 3960 to 1838,
aetherctlmain 1845 to 329) as pure mechanical moves, and duplicated logic with a history of drift (muxer setup vs its probe, transport dispatch, color metadata mapping, ASS text parsing, redirect handling, FFmpeg error constants) was consolidated to single sources of truth. HLSVideoEngine.extendVisibleWindow(toCoverSeconds:)was removed. Technically a public symbol (which SemVer reserves for a major), but the method had been a complete no-op for several releases and has no known consumer; treating its removal as a patch keeps the version meaningful. If you somehow called it: it did nothing, and deleting the call changes nothing.- README accuracy pass: the AVIOReader mode table reflects that live sources ride the persistent reconnect-capable connection, the architecture tree covers the new file layout, and
aetherctl audiodocuments--seconds.
Notes
The full audit covered concurrency (locking conventions, supersession generations, teardown ordering), FFmpeg resource lifecycle on error paths, protocol/timing math (timebases, channel layouts, HLS attributes), and the dev tooling. Known accepted residuals are documented in the source where they live.