Skip to content

Releases: superuser404notfound/AetherEngine

7.22.0 - The remote-HLS bypass publishes what a stats panel reads

Choose a tag to compare

@superuser404notfound superuser404notfound released this 28 Sep 15:01

A minor release. On the nativeRemoteHLS bypass (route .remoteBypass) the engine now publishes live telemetry, the audio tracks and the delivered video format. There is no new public API: existing properties now carry values on a route where they used to stay empty.

Changed

  • diagnostics.liveTelemetry runs on the bypass. It used to stay nil for the whole session there, because the sampler's bitrate halves read the loopback's demuxer counter.
    • It is now fed from AVPlayer's access log. The two bitrate fields are what the playing variant declares: BANDWIDTH, and AVERAGE-BANDWIDTH, or BANDWIDTH where the master omits it.
    • The bytes AVPlayer transferred are not used for the bitrate, because they measure buffer fill at link speed: 22.6 Mbps for a 3.7 Mbps Jellyfin transcode right after a start.
    • networkThroughputMbps is the access log's observedBitrate. networkTransferredBytes, droppedFrameCount and forwardBufferSeconds come from the access log and the loaded ranges.
    • avSyncGapMs and observedFps are nil on this route, and the loopback counters read 0.
    • (#664)
  • audioTracks on the bypass. One TrackInfo per audio track AVPlayer built, read from its format description: codec (eac3, ac3, aac), channels, sample rate, profile, Atmos and language. Ids start at 400000. selectAudioTrack is informational on this route and logs the call instead of acting on it, because the bypass has no audio override to reload with. (#664)
  • The delivered video format on the bypass. sourceVideoWidth, sourceVideoHeight and sourceVideoStreamFormat (colour, range, and for H.264/HEVC also profile, bit depth and pixel format) are read back from AVPlayer's item video track. Under a server-side transcode this is the transcode, not the library's file: a 1280x720 H.264 Jellyfin transcode used to read as "3840x2160, Main 10" in a host that fell back to the file's metadata. (#664)

Tooling

  • aetherctl play --native-hls prints the telemetry, the settled delivered video format and the audio tracks the bypass publishes.

Full changelog: CHANGELOG.md · 7.21.1...7.22.0

7.21.1 - A software session keeps the volume the host app set

Choose a tag to compare

@superuser404notfound superuser404notfound released this 27 Sep 19:42

A patch release for the software playback routes. No API change.

Fixed

  • A software session keeps the volume the host app set. The engine applies its stored volume to a new host right after building it, before load(). SoftwarePlaybackHost (dav1d / libavcodec) and AudioPlaybackHost (FFmpeg audio-only) forwarded that write only to their AudioOutput, which load() builds later, so it was dropped and every software session started at full volume. volume also read 1.0 in that window. Both hosts now hold the volume themselves and hand it to each output they build. The native AVPlayer routes were never affected. (#660)

Full changelog: CHANGELOG.md · 7.21.0...7.21.1

7.21.0 - The stream format a stats panel needs

Choose a tag to compare

@superuser404notfound superuser404notfound released this 27 Sep 17:16

A minor release with additive public API for a host's stats panel. No behaviour change on any playback route.

Added

  • sourceVideoStreamFormat and SourceProbe.videoStreamFormat. A VideoStreamFormat with the source video stream's pixelFormat (yuv420p10le), bitDepth, colorPrimaries, transfer, matrix, range and profile (Main 10), in libav's names as the container and the probe's decoder declared them. colorPrimariesLabel, transferLabel, matrixLabel and rangeLabel give the names a viewer reads ("BT.2020", "PQ (SMPTE ST 2084)", "BT.2020 NCL", "Limited"). A field the stream leaves unspecified stays nil instead of reading as BT.709. (#658)
  • decodedVideoFormat. A DecodedVideoFormat: what the engine's own decoder actually produced (frame, a VideoStreamFormat) and the CoreVideo buffer it was displayed from (pixelBufferFormat, 420v / x420, with pixelBufferLabel reading "P010 (x420)"). It is republished when either changes mid-stream. It is nil on every native route by design: AVPlayer decodes there and no frame passes through the engine, so a panel should show sourceVideoStreamFormat and name AVPlayer as the decoder rather than infer a pixel format nobody observed. (#658)
  • TrackInfo audio detail. sampleRate, bitsPerSample (0 where the codec has no fixed depth: AAC, AC-3, E-AC-3 and Opus decode to float), sampleFormat (fltp, s32), channelLayout (5.1(side)) and profile. The profile is where DTS:X ("DTS-HD MA + DTS:X") and TrueHD Atmos ("Dolby TrueHD + Dolby Atmos") show up; isAtmos still covers E-AC-3 JOC only. All new initializer parameters have defaults, so existing call sites compile unchanged. (#658)

Tooling

  • aetherctl probe prints the pixel format, depth, profile and colour description of the video stream, and rate, depth, sample format, layout and profile per audio track.
  • aetherctl play prints a DECODED line whenever decodedVideoFormat changes: DECODED yuv420p depth=8 ... -> NV12 (420v) on a VP9 source (software route), nothing on an HEVC source (loopback route).

Full changelog: CHANGELOG.md · 7.20.1...7.21.0

7.20.1 - No pause at an episode seam on a reused native host

Choose a tag to compare

@superuser404notfound superuser404notfound released this 27 Sep 16:38

A patch release for hosts that load the next item on the same engine without a stop() in between (auto-advance to the next episode). No API change.

Fixed

  • An in-place load on a reused native host no longer publishes a pause at the seam. NativeAVPlayerHost stopped observing timeControlStatus before it paused the outgoing item, so the pause was never published and the host kept reporting .playing. The next load replayed that value on subscribe, treated the transport as already rolled, and then let its own pre-roll .paused through as a real pause: loading, playing, paused, playing within one millisecond. A host that raises its transport on an external pause (Siri, Control Center) showed its controls over every auto-advanced episode, while the first episode started clean because a fresh host begins at .paused. The host now resets the published status with the rest of its per-item state. (#661)

Tooling

  • aetherctl play --host-calls reloadnext reloads the URL in place at t=4 and prints every published state as STATE <state> t=<s>. Before the fix the seam read playing, paused, playing; after it, loading, playing.

Full changelog: CHANGELOG.md · 7.20.0...7.20.1

7.20.0 - Forward-only URL sources play natively, and AirPlay gets the picture

Choose a tag to compare

@superuser404notfound superuser404notfound released this 27 Sep 15:44

A URL source that can only be read front to back now plays on the native path, so AirPlay gets the picture instead of the sound only. Fixing this brought four existing defects in the sequential-origin path to the surface; they are fixed here too. There is one additive API change: airPlayPictureStaysLocal.

Fixed

  • A URL source that can only be read front to back plays on the native path and AirPlays with a picture. Some origins ignore Range and send no length; the one reported serves a remote MKV at filesamples.com over HTTP/3. Such a source was forced onto the software host. That host cannot seek on the source either, and it never hands an AirPlay receiver its picture: only its audio renderer follows the route, so the TV played the sound and then gave the session up. When the container states a duration, the engine now serves such a source as a sequential origin: native path, hardware decode, AVPlayer's buffering, and a picture on the receiver. The engine does not promote custom readers, sources the routing already sent to software, or a host's preferredDecodePath = .software. loadedOptions reads the promotion back as sequentialOrigin = true with the container duration declared.
  • An AirPlay hop on a sequential origin swaps the item instead of reopening the source. A reopen can only start again from byte 0. The reload resumed at 0 instead of the playhead and then kept seeking, and on the remote origin it ended in "Source cannot be repositioned". The server already listens on every interface and keeps every segment it cut, so the hop now hands AVPlayer the LAN address and, on the way back, the loopback address, at the playhead. Measured on device, iPhone to Apple TV and back: the receiver fetched every segment itself, and playback resumed on the phone at 22.8 s.
  • A forward-only source keeps its opening. After the segment plan, the engine reset the cursor with a seek on a pb that cannot rewind. That drops the packets the probe had buffered and leaves the Matroska demuxer resyncing from wherever the stream has got to. The first GOP of a 30 s clip was lost that way; on the software path, the reported session started 30 s in.
  • A sequential origin whose GOP is longer than the segment stride lists all of its media. Audio opened segments by time, while the playlist is built from the video keyframe cuts. The video after an audio-opened boundary therefore landed in a file the playlist never listed, 4 to 11 s of an 11.3 s first GOP, and AVPlayer stalled at the end of seg0. Audio now follows the video cut, as it does on live. The finalize reports are also anchored on the pump's first segment, so an index skipped before seg0's capture no longer holds back every later report.
  • A backward jump on a sequential origin is served from the cache. The residency scan read the holes the cutter leaves as a gap and asked for a restart the origin cannot give. It then published "Source cannot be repositioned" over a session that held every segment it needed (seen when an AirPlay session returned to the phone).
  • A FrameExtractor still carries the colour space playback shows the picture in. SDR stills were tagged sRGB, although their pixels are in the source's own primaries and video transfer. They now carry the space CoreVideo builds from the displayed buffer's tags (kCGColorSpaceCoreMedia709 for BT.709). Against VideoToolbox's own conversion of the same frame, the max channel error went from 9 to 2 on BT.709, from 52 to 2 on NTSC SMPTE-C and from 57 to 2 on SDR BT.2020.

Added

  • airPlayPictureStaysLocal (iOS) is true while a wireless AirPlay receiver holds the audio route and the session runs on the software host (VP9, AV1 without hardware decode, deinterlaced MPEG-2 / VC-1 / H.264, preferredDecodePath = .software). In that state the picture stays on the device while the sound goes to the TV. Nothing fails, so without this signal a host had no way to tell the viewer why the receiver shows no picture.

Known limits

  • A forward-only MPEG-TS source still loses its first GOP to the Annex-B framing probe. This release does not change that path: TS carries no header duration, so such a source stays on the software path.

Full changelog: CHANGELOG.md · 7.19.0...7.20.0

7.19.0 - Software-decoded pictures and stills carry the colours playback shows

Choose a tag to compare

@superuser404notfound superuser404notfound released this 26 Sep 21:45

Two colour fixes on the software side of the engine (AE#654, reported by @1096bimu). There is one additive API change: SoftwareDecodeProbeResult.firstFrameColor.

Fixed

  • A software-decoded picture carries the colour tags VideoToolbox would give it (AE#654). Before, the software decoder attached a tag only where the frame declared one and CoreVideo had a mapping for it. An untagged source therefore reached the display layer with no primaries, transfer or matrix, while the same file through VideoToolbox arrives tagged BT.709 (ColorInfoGuessedBy: VideoToolbox). The software decoder now fills the gaps the way VideoToolbox does, measured on VideoToolbox's own output: nothing declared means BT.709 for all three, at any size and for any codec, and a lone BT.601 matrix gets SMPTE-C primaries. This affects the software route: MPEG-4 ASP, MPEG-2, VC-1, VP9, and AV1 without hardware decode. ColorDescription keeps its gaps, so the HDR gate and the tone mapper still see an untagged stream as untagged.
  • Tagged SD sources keep their tags on the software path. ColorAttachments had no mapping for BT.601 matrices or for SMPTE-C and EBU 3213 primaries, so a correctly tagged PAL or NTSC source lost all three tags. These mappings are added, together with DCI-P3, SMPTE 240M, sRGB and linear transfer.
  • A FrameExtractor still is converted with the picture's own matrix and range. The SDR still path never called sws_setColorspaceDetails. Every still therefore went through swscale's BT.601 default, with the range read from the pixel format alone. Measured against ffmpeg's correct conversion on colour bars, the max channel error went from 33 to 0 on 1080p BT.709, from 33 to 0 on 1080p untagged, and from 31 to 0 on 10-bit HEVC full range (neutral grey read 203 against 192 before, since no yuvj variant exists for 10 bit). BT.601 SD was already exact. The matrix now follows the rule the displayed buffer is tagged by, so an untagged still resolves exactly as playback does.

Added

  • SoftwareDecodeProbeResult.firstFrameColor reports the colour tags the first picture reaches the display layer with, as primaries / transfer / matrix. aetherctl swdecode prints it.

Full changelog: CHANGELOG.md · 7.18.2...7.19.0

7.18.2 - An escalation reports the position its rebuild resumes at

Choose a tag to compare

@superuser404notfound superuser404notfound released this 26 Sep 17:42

One fix to the AE#561 software-path escalation event (AE#629, measured by @cmcpherson274). No API changes.

Fixed

  • A software-path escalation reports the position its rebuild resumes at (AE#629). When AVPlayer refused an item that a host load() had mounted, the refusal could land before the zero-tolerance mount seek did. In that window AVPlayer's clock reads the start of the segment it decodes up from, not the position the load was handed. The native host passed that reading to the rung, which printed it on the engine's #561 line and published it as SoftwarePathEscalationEvent.positionSeconds, while the rebuild itself resumed at positionForSessionRebuild: 12.00 s in the event against 15.90 s in the rebuild on the reporter's Apple TV 4K. Both now carry the rebuild's position. The native host's own [NativeAVPlayerHost] #561 line keeps AVPlayer's raw reading. The in-place swap of the #93 revive was already covered by the clock hold in 7.17.1.

Full changelog: CHANGELOG.md · 7.18.1...7.18.2

7.18.1 - A quiet consumer that is still playing is not a backpressure wedge

Choose a tag to compare

@superuser404notfound superuser404notfound released this 26 Sep 15:54

One fix to the #65 VOD backpressure wedge breaker (AE#649, contributed by @tschuegy in #650). No API changes.

Fixed

  • A VOD consumer that goes quiet while it keeps playing no longer trips the #65 wedge breaker (AE#649). On a cellular iPhone, AVPlayer fetches segments in bursts and then fetches nothing for 30 to about 110 s while it plays out a forward buffer of up to 113 s. The slow path counted that silence alone and broke the park every few minutes. The #421 nudge seek that followed flushed the buffer on screen, a half-second freeze of picture and sound each time, and the re-anchor fetched source bytes the session already held. The slow path now counts only seconds in which the consumer neither fetched nor rendered: a poll whose rendered clock advanced holds the count instead of adding to it. A consumer that really stopped fetching plays its buffer dry and then breaks on the fast path (target and clock flat for 5 s). Measured by the reporter on an iPhone 16e over 5G: 2 WEDGE BROKEN in 7 min on 7.17.1, 0 in 9 min 22 s with the fix, and no stalls after the start.

Diagnostics

  • The #65 backpressure PARK line carries a new idle= field next to stuck=. stuck= keeps its AE#528 meaning (seconds since the consumer last declared a fetch target), idle= is what the breaker trips on. A park behind a consumer that is quiet but playing reads (consumer quiet, still playing). aetherctl seektest counts a park as behind a live consumer on idle=0s, see docs/cli.md.

Full changelog: CHANGELOG.md · 7.18.0...7.18.1

7.18.0 - Remote disc images adopt a prewarm, DVD titles open without the 50 MB probe

Choose a tag to compare

@superuser404notfound superuser404notfound released this 26 Sep 14:53

Two changes to disc-image startup: AE#647 (a prewarm is now used for remote .iso) and AE#651 (the DVD probe budget, plus subtitle streams that start late). No API changes.

Behaviour changes worth knowing before you bump

  • On a DVD title with a readable VTS IFO, the subtitle streams now come first in the stream order. They are created before the probe, so their TrackInfo.id values (stream indices) come before video and audio. A host that selects tracks by language, codec or av_find_best_stream is unaffected. A host that hard-codes an index on DVD sources is affected.
  • Every subpicture stream the IFO declares is a subtitle track from the open. Before, a stream showed up only if its first packet arrived within the probe window.

Changed

  • A remote disc image adopts a warm (AE#647). When AetherEngine.prewarm is called on an .iso / .img / .udf URL, the disc reader now actually uses the warm. It takes the size from the warm instead of probing it with bytes=0-0, serves the disc structure and the title's opening extents from memory, and sends its first request at the warm frontier. Forks (the subtitle side reader, the forward prefetcher) share the same bytes without a copy and no longer probe the size again. If a disc-image URL turns out not to be a disc, the warm is passed on to the streaming reader. The redirect target of the warm is not pinned, because this reader follows redirects per request.

Fixed

  • A DVD title opens without reading 50 MB first (AE#651). mpegps sets AVFMTCTX_NOHEADER and never clears it, so find_stream_info never finished early and every DVD open read the full playback budget (50 MB / 60 s). A title with a readable VTS IFO now probes 8 MB / 5 s. Measured on an authored DVD behind a 20 Mbit/s origin with 100 ms per response: the first frame appears at 6.5 s instead of 29.6 s, and at 0.54 s with a prewarm.
  • A DVD subtitle whose first packet arrives late is a track, and it decodes (AE#651). The subpicture streams the IFO declares are created before the probe. libavformat attaches its dvdsub parser only to streams it created itself, so the engine joins each subpicture's PES fragments itself, using the same rule as dvdsub_parser.c. On the test disc, a subtitle first shown at 100 s is listed from the open, and its 8 KB unit, split across several packets, decodes. On 7.17.1 that track did not exist.

Discs without a readable VTS IFO, and plain .mpg / .vob URLs, keep the previous probe budget.

Full changelog: CHANGELOG.md · 7.17.1...7.18.0

7.17.1 - Live joins on intra recovery points, undecodable audio no longer stalls

Choose a tag to compare

@superuser404notfound superuser404notfound released this 26 Sep 04:32

Fixes for AE#627 (live H.264 joins), AE#641 (audio the bridge cannot decode) and AE#629 (the software-path rebuild), plus FFmpegBuild 3.6.0. No API changes.

Behaviour changes worth knowing before you bump

  • A live join that finds no entry point the native route can open now goes to the software path after one 15 s wait, instead of three reopen cycles into the same bitstream. It shows up on softwarePathEscalations with the domain AetherEngine.LiveJoin; a host that set escalatesToSoftwarePath = false gets liveSourceReset at that point instead of about a minute later.
  • A live channel whose audio cannot be decoded plays video-only and audioDelivery reads .droppedNoPipeline, where it used to freeze on the first picture.

Fixed

  • A session revived by the #93 item swap keeps its playhead until the fresh item lands there.
    Until then AVPlayer reports the start of the segment it decodes up from, so the playhead stepped
    back by up to a segment, and a software-path rebuild raised in that window resumed there and
    replayed the gap (3.97 s on a 15.97 s revive) (AE#629).
  • A software-path rebuild that fails after its teardown surfaces one .error, its own. The
    rung then published the failure it had absorbed on top, a second .error that contradicted what
    a load() following the rebuild threw. The absorbed failure stays on softwarePathEscalations
    (AE#629).
  • A live H.264 join opens on an intra recovery point, not only on an IDR. Feeds whose encoder
    never sends an IDR (entry points are I-pictures behind a recovery point SEI with
    recovery_frame_cnt 0) never started on the native route; the join gate refused every entry
    point and gave up after three 15 s reopen cycles (AE#627).
  • A live join that finds no entry point at all goes to the software path after one wait, instead
    of three reopen cycles into the same bitstream, or to liveSourceReset when the host declined
    that rung (AE#627).
  • A live channel whose audio the bridge cannot decode plays video-only instead of stalling. A
    FLAC bridge builds its sample entry from the encoder's extradata, so a bridge that decoded nothing
    left segments with an audio track that never carried a sample, and AVPlayer showed the first
    picture and waited on it for the rest of the session. The engine now rebuilds the session without
    that track and audioDelivery reads .droppedNoPipeline (AE#641).
  • A VOD audio track the bridge cannot decode surfaces .audioBridgeProducedNoOutput on the FLAC
    route too.
    Only the E-AC-3 route reached that verdict, through its failed first cut; a FLAC
    bridge builds its sample entry from the encoder's extradata, so the session played silently while
    audioDelivery read .bridged, and a host ladder never heard of it. The verdict reaches the host
    once, whichever arm finds it first (AE#641).
  • audioDelivery reads .droppedNoPipeline for a source whose audio stream could not be picked,
    not .noAudioInSource. av_find_best_stream passes over a stream whose parameters the probe left
    empty and only live fell back to it, so on VOD (and on the software path) a source with an
    undecodable track read as one without audio (AE#641).
  • FFmpegBuild 3.6.0: an MPEG-TS audio PID is identified by its payload. The raw dts, truehd
    and loas demuxers were missing, so the mpegts content probe could never confirm DTS, TrueHD or
    LATM and the lenient mp3 probe named the track; a PID labelled 0x03 (MPEG-1 audio) was not probed
    at all. A DTS-HD IPTV channel opened as mp3, and every packet failed with "Header missing"
    (AE#641).
  • The keyframe wait log counts the keyframes it dropped (keyframes=N), so a gate refusing
    every entry point no longer reads as a feed without keyframes (AE#627).

Full changelog: 7.17.0...7.17.1