Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,43 @@ the public-API contract.

## [Unreleased]

_Nothing yet._
### Fixed

- **A live source is no longer stuttered on a steady cycle by the reader's own backpressure.**
The 16 MB high-water end (#310) had no live branch, and live connections are open-ended by
design, so ending at high water was the only thing that ever terminated a healthy live
connection. Each end drained ~8 MB to low water and re-requested "at the frontier" — a byte
offset that means nothing to a live origin — so everything broadcast during the drain was lost
and the demuxer rejoined on a corrupt TS packet. And it never happened once: IPTV panels serve
their ring buffer as a join burst at line rate on every (re)connect, so the burst refilled the
window immediately and each reconnect caused the next one, forever (a field trace against an
Xtream panel cycled every ~9.5 MB with a `Packet corrupt` and an h264 decode error per cycle;
the loopback repro accepts 17 MB of a 24 MB burst, parks at 16.9 MB and holds no connection).
Live readers now run a 64 MB high water (matching `streamHighWater`, the bound the engine
already accepts for the other reader that cannot bound by range request): the join burst is
absorbed once, steady state plateaus at burst size with the connection never voluntarily
ended, and the end-and-refill survives unchanged as the memory backstop for a "live" source
that sustainedly outruns realtime.
- **A live reconnect asks for the stream the way a join does, instead of at a byte frontier.**
The reconnect request carried `Range: bytes=<frontier>-`, but the frontier is reader
bookkeeping — the window position delivered bytes are appended at — not a server-side byte
address, because a live origin has none. Panels that ignore the offset and serve "from now"
masked this; a panel that answers 416 to every offset it cannot satisfy turned each reconnect
into an unrecoverable rejection loop (field trace: a panel that cleanly completes every
response after its ~14 MB ring burst then 416'd the same frontier 35 generations in a row,
~1/s, while the runway drained from 8 MB to zero and the session starved). Live requests are
now always `bytes=0-` — the one shape every origin serves, and the shape the join already
uses — and the append anchors the bytes at the frontier exactly as it always has.
- **HTTP 509 from a pinned redirect target is treated as metering, not as a dead pin.**
509 "Bandwidth Limit Exceeded" is what a connection-capped IPTV panel answers while the slot
the reader is replacing has not been torn down server-side yet. It classified as a hard 5xx,
so every attempt dropped the pinned post-redirect URL and re-resolved through the portal —
latency per attempt, plus the second request against the very origin that has no room for it,
which is the 519ae26e reasoning left incomplete (a permanent 509 ground through 13 attempts
with 12 portal re-resolves at zero backoff, because ~8 MB of progress per cycle reset the
unproductive streak every time). 509 now keeps the pin and pays the rate-limit streak and
backoff alongside 429/503, honouring Retry-After when sent, with the same bounded give-up
(#307 follow-up).

## [6.15.1] - 2026-08-08

Expand Down
8 changes: 5 additions & 3 deletions Sources/AetherEngine/AetherEngine+Diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ extension AetherEngine {
}

// #220: the two readers of a subtitled VOD session, separately attributable.
// `ahead` far above winHighWater (16 MB) with `parked=0` is backpressure that
// never engaged; the pump's own window is the control.
// `ahead` far above winHighWater (16 MB VOD / 64 MB live) with `parked=0` is
// backpressure that never engaged; the pump's own window is the control. A live
// reader plateauing between the two marks is healthy: that is the join burst,
// absorbed once and held.
//
// Both paths, not just software. On a direct-play source the native path runs
// the HLS loopback, so `HLSVideoEngine` demuxes from the origin through an
Expand Down Expand Up @@ -268,7 +270,7 @@ extension AetherEngine {

/// #220: one memprobe fragment per live `AVIOReader` window. `win` is the whole buffer,
/// `ahead` the undrained forward extent that `appendPersistentData` gates the backpressure
/// end on. `ahead` far above winHighWater (16 MB) while `parked=0` means the backpressure
/// end on. `ahead` far above winHighWater (16 MB VOD / 64 MB live) while `parked=0` means the backpressure
/// never engaged, which is a different defect from a transport overshoot past an end that
/// fired (#310: the end replaced the suspend, so the overshoot is bounded by one
/// delivery's in-flight amount rather than by whatever a suspended task lets through).
Expand Down
Loading
Loading