Skip to content

7.45.0 - Live edge cushion, session cache budget, broadcast HLG

Choose a tag to compare

@superuser404notfound superuser404notfound released this 21 Oct 14:42
· 55 commits to main since this release

Added

  • LoadOptions.liveEdgeCushionSeconds: a host chooses how far behind the live edge a live session plays. The live counterpart of a buffer-duration setting, for viewers whose link is slower or less steady than the channel. It only ever moves the playhead further back than the path's own distance and is clamped to 45 s. On the loopback it raises the served HOLD-BACK, the first-serve gate and its deadline, and floors .fastZap's shallow start; the live HLS ingest joins deep enough for a listed backlog to fill it at link speed. On the software path it is the join lead, the landing holdback and the cushion a rebuffer refills before resuming (without a DVR window it keeps a packet ring that deep, publishing no rewind window); on nativeRemoteHLS it is configuredTimeOffsetFromLive, preserved across stalls and never below the origin's own recommended offset. A session holding the cushion reads isAtLiveEdge. nil keeps today's behaviour. aetherctl play --live-cushion SECONDS drives it.

  • LoadOptions.sessionCacheByteBudget and EngineDiagnostics.sessionCacheStatus: a host can size the temporary VOD cache and see what it holds (#830, thanks @orut34iop). An explicit byte request goes through the same quarter-of-free-space clamp and RetentionClaims ledger as the automatic 2 GiB budget, and applies to the fMP4 segment cache and the software disk packet store. 0 keeps only the playback window. The snapshot reports requested and effective bytes, resident and forward bytes, producer parking, evictions, a protected window that exceeds the request, and the cleanup reason and result after stop, sampled off-main and never carried into the next session. nil keeps today's sizing; live DVR, the tvOS memory store and native remote HLS (reported as unsupported) are unchanged.

  • prepareExternalSubtitle / commitExternalSubtitle: a downloaded sidecar is decoded before it replaces the selection (#829, thanks @orut34iop). A host picking an external subtitle had to switch to it before knowing the file decodes. Preparation decodes a local SRT, WebVTT, ASS or SSA file within ExternalSubtitlePreparationPolicy limits and touches no track, cue or preference; the commit publishes the decoded cues once and reads nothing. A failed preparation keeps the old selection, and a newer selection on either channel, a removal, stop or a new load turns an older candidate stale. Ordinary selection is unchanged.

  • LoadOptions.softwarePacketCacheStorage: a host can choose where the software path keeps its packets (#827, thanks @orut34iop). Seekable software VOD caches compressed packets in purgeable memory on tvOS and in session files elsewhere (#776). .disk and .memory now select either store on any platform, with the same budgets, volume safeguards and eviction as the default; .automatic keeps the platform policy. A reload carries the choice over. Local files that bypass the cache, live DVR and native HLS are unchanged.

  • secondarySidecarASSHeader: a secondary ASS sidecar keeps its markup and script header (#826, thanks @orut34iop). The secondary sidecar decode ignored preserveASSMarkup and dropped the header, while the embedded secondary path already honoured the option. It now publishes raw event lines plus its own header, independent of the primary sidecarASSHeader (script resolution and styles can differ). The header clears on a new selection, on clearSecondarySubtitle() and on stop, and is decoded again on a session-preserving reload. SRT and WebVTT stay plain text, and output without the option is unchanged.

Fixed

  • Broadcast HLG plays as HLG (#839, thanks @shinier-lichen). UK broadcast HLG (BBC iPlayer and others) declares a backward-compatible BT.2020 10-bit or BT.709 transfer so an SDR set still shows a picture, and names HLG only in an alternative_transfer_characteristics SEI (payloadType 147). The engine read the declared transfer alone, so the display criteria, the route's VIDEO-RANGE and the colr in init.mp4 all said SDR. The demuxer now peeks up to 8 video packets after open, for HEVC and H.264 with BT.2020 primaries and such a transfer only, and an SEI naming HLG makes the stream HLG for every consumer: load, probe(url:) and a live reopen. Logged as [Demuxer] AE#839 ... the stream is HLG.

  • The Atmos output activator runs only on an HDMI route (#834, thanks @badrabbit). On tvOS it looped its silent Atmos stream beside an APAC programme on every route, AirPlay included. Over AirPlay to a HomePod stereo pair the programme stayed silent after a pause and resume, and after a second pause the picture froze too, until a seek. The activator now plays only while every output port is HDMI, follows route changes, and logs [AtmosOutputActivator] active|off on route when that verdict changes. HDMI playback is unchanged.

  • A live HLS channel on the software path no longer stalls once per segment (#813). The software live join placed the picture 4 s behind the newest media, which suits a continuous TS panel but ran dry before every segment of a source that delivers 10 s at a time, and the rebuffer resumed the moment the late segment landed, so the stall repeated. The holdback now follows the segment length (1.5 x segment + 1 s, 16 s on 10 s segments, at most 45 s): the join and Return to Live use it, the live-edge verdict allows it plus one segment, and a rebuffer resumes once a segment plus 2 s stands ahead of the clock. Continuous sources are unchanged.

  • A container chapter seek lands on the chapter when the source does not start at 0 (#815, thanks @pkajaba). mediaChapters carried the container's own chapter times, while seek(to:) and currentTime count from the container start, so on a source starting at 5 s every chapter seek landed 5 s late. The chapters are now rebased per backend onto the seek axis, a pre-roll chapter that ends before the content starts is dropped, and the software host predicts its session zero at load, so a seek before the first play() converts on the same axis.

  • The rewind after the load-time interlace sample is bounded (thanks @pkajaba, from #807). The sample that checks a declared interlaced field order (#232) moved the read position of the demuxer the session reuses and seeked back with the plain seek, which has no deadline, so a remote source that stalled there held the load. It now uses seekBounded with the same 5 s budget as the other load-time seeks, and a rewind that does not land makes the sample inconclusive, so the declared field order stands.

  • Native loopback buffered position keeps updating while paused, using the existing wall-clock sampler and the real contiguous segment frontier. Cache holes, eviction and display-axis changes remain visible without advancing playback.

  • External subtitles inherit media request headers only at the same HTTP scheme, host and effective port. Explicit subtitle headers remain authoritative, including an empty set. Primary/secondary overlays, native subtitle stores and remote-HLS sidecar renditions use the same initial-request rule; existing redirect and certificate policies are unchanged.

  • A registered secondary subtitle keeps its identity across an audio switch (#824, thanks @orut34iop). The audio-switch rebuild restored the secondary companion through its URL only, so it lost its external track id, its explicit httpHeaders and the sourceStreamIndex of a multi-stream subtitle container. It now restores through the registry id the way the primary track already did, and keeps the URL route for one-shot sidecars. Selecting an embedded secondary track clears the old sidecar URL, stopInternal clears the active secondary id, and a late secondary decode from an earlier load cannot publish cues or loading state into the next one.

  • Audio on a coarse time base no longer fragments the software packet cache (#828, thanks @orut34iop). A 2048-sample WMA frame at 44.1 kHz lands on 46 or 47 ms steps, and each packet's declared 46 ms left a 1 ms hole at every 47 ms step, so seeks into bytes already held refetched. Packets whose codec fixes the sample count (AC-3, MPEG audio, declared AAC/E-AC-3 frame sizes, non-reservoir WMA v1/v2, PCM) now carry one rational sample clock across those steps, for the read-ahead frontier, residentRanges and seek admission alike. A missing packet, a tick of drift per packet, a backward or missing timestamp and an unknown layout still leave a hole. Duration-less audio keeps its successor coverage, also when the declared frame size turns out wrong.

  • A fully prewarmed source no longer fetches its suffix again (#831, thanks @orut34iop). When the adopted head already covers the whole file, the open skipped the data connection but still started the speculative Range: bytes=-65536 tail request, so a source the warm held entirely was asked for again. That request is now skipped too; a partial warm keeps its tail prefetch.

Changed

  • The secondary subtitle documentation no longer says bitmap tracks are rejected (#825, thanks @orut34iop). selectSecondarySubtitleTrack(index:), secondarySubtitleCues and docs/formats.md still described the text-only side demuxer that #112 replaced. Embedded PGS, DVB and DVD tracks already decode on the secondary channel with their own decoder, cursor and PGS admission state over the shared packet store. A dual-PGS drain test now pins that: reused palette and object IDs, an authored clear, off and reselect, and reconstruction after a seek. No playback change.

  • aetherctl play rejects --no-fel and --no-dual-layer as stray flags. They were accepted as no-ops since 7.37.0 for scripts written when play turned the dual-layer route on by itself.

Tests and CI

  • The memory packet store's history tests no longer race the kernel (#819, thanks @yucelokan). Consumed chunks are volatile VM, so under a full suite's memory pressure the kernel could empty them between a test's pop() and its next line, failing a cursor restore, a still walk or a seek-back hit at random. An internal kernelMayPurge: false on SoftwarePacketMemoryFIFO keeps that history resident at the kernel level while every count and state transition still treats it as volatile, so only purgeHistoryForTesting empties it. The tests that re-read history use it, the purge and residency tests keep real kernel purging. No change to the store in playback.

  • SoftwarePacketMemoryFIFOTests blocks a thread and now runs off the cooperative pool, so the one-thread CI step cannot starve the other suites on it.

Full Changelog: 7.44.0...7.45.0