Skip to content

Commit 78735b0

Browse files
Vincent Herbstclaude
andcommitted
chore(release): prep 6.55.0 - a live window is sized by what the session can hold
AE#443 is the headline: a DVR window deeper than the producer's resident cap filled to the cap before the playlist had slid once, parked the pump for the rest of the session, and then killed the origin behind it because a parked pump stops draining its socket. AE#441 gets both halves of its retest (the resume clamp now triggers on the bound it lands on, and eviction stops at the consumer's next fetch instead of unlinking a segment mid-serve), AE#446 serves a stalled window as the finite asset it is, AE#445 pools the seam every custom reader comes through, and AE#440's live-join cushion becomes the default after the device A/B. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WbciAqCSxpaiUpTumrCuA9
1 parent d24b7ef commit 78735b0

3 files changed

Lines changed: 55 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ the public-API contract.
1010

1111
## [Unreleased]
1212

13+
_Nothing yet._
14+
15+
## [6.55.0] - 2026-08-28
16+
1317
### Changed
1418

1519
- **A live join no longer holds its first frame still while AVPlayer second-guesses the cushion it
@@ -47,6 +51,53 @@ the public-API contract.
4751
"No segment finalized" has two causes that point in opposite directions, and only one of them is
4852
about the origin.
4953

54+
- **A live resume clamp is measured from the bound it lands on (AE#441).** Since 6.52.0 the clamp lands
55+
on the honest floor, the DVR window intersected with what the cache actually holds, but it still
56+
TRIGGERED on window arithmetic. Where retention runs short of the window for a session's whole life
57+
(the reporting strip: a 420 s window advertising ~405 s of depth), a resume between the real depth and
58+
the window therefore got no clamp at all, for a position the cache no longer held. Both ends read the
59+
same bound now. The margin applies only once the window is sliding: before it fills, the floor is the
60+
session's own start rather than an eviction frontier, and a margin there would only shove a resume near
61+
the start forward.
62+
63+
- **Eviction stops at the consumer's next fetch, so a segment cannot be unlinked while it is being served
64+
(AE#441).** `live window slid past the consumer` read the LAST fetch when the cost of a slide is decided
65+
by the NEXT one: the consumer walks indices forward, so everything below the declared target is already
66+
in AVPlayer's buffer and a viewer parked at the floor sits one segment below `firstVisible` for part of
67+
every slide with nothing lost. Chasing the four benign lines in the AE#441 retest turned up why that
68+
tolerance is not cosmetic. At exactly that off-by-one, eviction unlinked the segment currently being
69+
served, and a serve holds a URL rather than a file handle, so the gap is a 404 for an index the playlist
70+
offered when it was asked for. Eviction now stops at the fetch point, which is the bound `evictBelow`
71+
already documented for itself, and never trails `firstVisible` by more than that one segment, so a
72+
consumer that stopped fetching cannot pin retention behind it.
73+
74+
- **A live window whose source has stopped delivering is served as the finite asset it is (AE#446).** A
75+
live playlist whose tail stops moving stops being fetched: AVPlayer reloads it, finds it unchanged
76+
(-12888), and after a handful of those it stops polling AND stops requesting segments, including ones
77+
it has never downloaded that the playlist still lists and that are resident on disk. Measured with a
78+
viewer 147 s inside the window and the source frozen: six more segments at playback rate, then silence
79+
with 115 s of runway sitting on disk, and a self-directed rejoin at edge-minus-HOLD-BACK when the
80+
playlist finally moved again (forward step 117.76 s). While the source is not delivering and the viewer
81+
still has resident segments ahead, the window is now served with an ENDLIST, same numbering, every
82+
segment still listed, which carries the whole runway (166.75 s of playback, no item deaths, no -12888)
83+
where the sliding-window form managed 24 s. Two cheaper answers, a byte-distinct refresh tag and a
84+
clock-driven MEDIA-SEQUENCE slide, were built, measured and discarded, and are documented as dead ends:
85+
what AVPlayer watches is the tail. A source that comes back is picked up by an item swap, which is the
86+
AE#442 one and therefore keeps the place, and `didPlayToEndTime` at the end of such a window is not
87+
forwarded as `.ended`.
88+
89+
- **A host reader's autoreleased objects no longer strand for the length of a live session (AE#445).**
90+
`HLSSegmentProducer` pumps on a bare `Thread`, which has no autorelease pool of its own, and FFmpeg's
91+
read callback reaches the host's `IOReader` from inside that loop. Anything Foundation hands a custom
92+
reader back at +0 (an `NSData` out of `FileHandle`, for instance) was therefore held until the session
93+
ended, which on a live source that never EOFs is unbounded by construction. The engine had paid for this
94+
twice already and fixed it one reader down both times; the pool now sits at `CustomIOReaderBridge`, the
95+
single door every custom reader comes through, around read, seek, the size and seekability probes and
96+
cancel. Measured on the new `aetherctl customio --live` harness at a 0.95 MB/s mux rate: 0.95 MB/s of
97+
retention before, 0.00 after. A reader that preads into the buffer the bridge hands it allocates nothing
98+
per read and was never affected by this: that arm is the harness default now and measures flat (ratio
99+
-0.03 over 541 s, 0.00 with a 300 s DVR window) across 900 MB of source.
100+
50101
- **The join lever now reads how deep the buffer is, not merely that it is non-empty (AE#440).**
51102
`isPlaybackBufferEmpty` is the precondition `AVPlayer.h` documents, not a measure of safety: a single
52103
served fragment reads `false` exactly as a four-second cushion does. Sampling the real hold on hardware

Examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Three samples covering different audiences:
1818
```
1919
https://github.com/superuser404notfound/AetherEngine
2020
```
21-
Dependency Rule: Up to Next Major Version, starting from `6.54.0`. Add the `AetherEngine` library product to your app target.
21+
Dependency Rule: Up to Next Major Version, starting from `6.55.0`. Add the `AetherEngine` library product to your app target.
2222

2323
3. **Drop the file in.** Replace the Xcode template's default `App.swift` (or whatever the generated `@main` file is called) with the contents of [`MinimalPlayerApp.swift`](MinimalPlayer/MinimalPlayerApp.swift). The file is self-contained: it defines both the `@main App` struct and the `ContentView`.
2424

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ Subtitle cues land in raw source PTS; render the overlay against `player.sourceT
325325
Install via Swift Package Manager:
326326

327327
```swift
328-
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.54.0")
328+
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.55.0")
329329
```
330330

331331
Three samples ship in `Examples/`:
@@ -528,10 +528,10 @@ Browse all of this as a searchable site at **[aetherengine.superuser404.de](http
528528
AetherEngine uses [Semantic Versioning](https://semver.org). The public API surface, every `public` declaration in `Sources/AetherEngine/`, is the stability contract. **Major** removes / renames public symbols or breaks adopters; **Minor** adds public API or codec / format support; **Patch** fixes bugs with no public API change. `internal` types are not part of the contract.
529529

530530
```swift
531-
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.54.0")
531+
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.55.0")
532532
```
533533

534-
Pin to `.upToNextMinor(from: "6.54.0")` for stricter teams that prefer to opt into minor bumps explicitly.
534+
Pin to `.upToNextMinor(from: "6.55.0")` for stricter teams that prefer to opt into minor bumps explicitly.
535535

536536
## Requirements
537537

0 commit comments

Comments
 (0)