Releases: superuser404notfound/AetherEngine
Release list
7.22.0 - The remote-HLS bypass publishes what a stats panel reads
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.liveTelemetryruns 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.
networkThroughputMbpsis the access log'sobservedBitrate.networkTransferredBytes,droppedFrameCountandforwardBufferSecondscome from the access log and the loaded ranges.avSyncGapMsandobservedFpsare nil on this route, and the loopback counters read 0.- (#664)
audioTrackson the bypass. OneTrackInfoper audio track AVPlayer built, read from its format description: codec (eac3,ac3,aac), channels, sample rate, profile, Atmos and language. Ids start at 400000.selectAudioTrackis 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,sourceVideoHeightandsourceVideoStreamFormat(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-hlsprints 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
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
volumeto a new host right after building it, beforeload().SoftwarePlaybackHost(dav1d / libavcodec) andAudioPlaybackHost(FFmpeg audio-only) forwarded that write only to theirAudioOutput, whichload()builds later, so it was dropped and every software session started at full volume.volumealso 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
A minor release with additive public API for a host's stats panel. No behaviour change on any playback route.
Added
sourceVideoStreamFormatandSourceProbe.videoStreamFormat. AVideoStreamFormatwith the source video stream'spixelFormat(yuv420p10le),bitDepth,colorPrimaries,transfer,matrix,rangeandprofile(Main 10), in libav's names as the container and the probe's decoder declared them.colorPrimariesLabel,transferLabel,matrixLabelandrangeLabelgive 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. ADecodedVideoFormat: what the engine's own decoder actually produced (frame, aVideoStreamFormat) and the CoreVideo buffer it was displayed from (pixelBufferFormat,420v/x420, withpixelBufferLabelreading "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 showsourceVideoStreamFormatand name AVPlayer as the decoder rather than infer a pixel format nobody observed. (#658)TrackInfoaudio 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)) andprofile. The profile is where DTS:X ("DTS-HD MA + DTS:X") and TrueHD Atmos ("Dolby TrueHD + Dolby Atmos") show up;isAtmosstill covers E-AC-3 JOC only. All new initializer parameters have defaults, so existing call sites compile unchanged. (#658)
Tooling
aetherctl probeprints the pixel format, depth, profile and colour description of the video stream, and rate, depth, sample format, layout and profile per audio track.aetherctl playprints aDECODEDline wheneverdecodedVideoFormatchanges: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
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.
NativeAVPlayerHoststopped observingtimeControlStatusbefore 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.pausedthrough as a real pause:loading, playing, paused, playingwithin 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 reloadnextreloads the URL in place at t=4 and prints every published state asSTATE <state> t=<s>. Before the fix the seam readplaying, 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
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
Rangeand send no length; the one reported serves a remote MKV atfilesamples.comover 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'spreferredDecodePath = .software.loadedOptionsreads the promotion back assequentialOrigin = truewith 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
FrameExtractorstill 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 (kCGColorSpaceCoreMedia709for 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
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.ColorDescriptionkeeps 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.
ColorAttachmentshad 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
FrameExtractorstill is converted with the picture's own matrix and range. The SDR still path never calledsws_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 noyuvjvariant 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.firstFrameColorreports the colour tags the first picture reaches the display layer with, asprimaries / transfer / matrix.aetherctl swdecodeprints it.
Full changelog: CHANGELOG.md · 7.18.2...7.19.0
7.18.2 - An escalation reports the position its rebuild resumes at
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#561line and published it asSoftwarePathEscalationEvent.positionSeconds, while the rebuild itself resumed atpositionForSessionRebuild: 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] #561line 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
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 BROKENin 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 PARKline carries a newidle=field next tostuck=.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 seektestcounts a park as behind a live consumer onidle=0s, seedocs/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
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.idvalues (stream indices) come before video and audio. A host that selects tracks by language, codec orav_find_best_streamis 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.prewarmis called on an.iso/.img/.udfURL, the disc reader now actually uses the warm. It takes the size from the warm instead of probing it withbytes=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).
mpegpssetsAVFMTCTX_NOHEADERand never clears it, sofind_stream_infonever 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
dvdsubparser only to streams it created itself, so the engine joins each subpicture's PES fragments itself, using the same rule asdvdsub_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
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
softwarePathEscalationswith the domainAetherEngine.LiveJoin; a host that setescalatesToSoftwarePath = falsegetsliveSourceResetat that point instead of about a minute later. - A live channel whose audio cannot be decoded plays video-only and
audioDeliveryreads.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.errorthat contradicted what
aload()following the rebuild threw. The absorbed failure stays onsoftwarePathEscalations
(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_cnt0) 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 toliveSourceResetwhen 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 andaudioDeliveryreads.droppedNoPipeline(AE#641). - A VOD audio track the bridge cannot decode surfaces
.audioBridgeProducedNoOutputon 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
audioDeliveryread.bridged, and a host ladder never heard of it. The verdict reaches the host
once, whichever arm finds it first (AE#641). audioDeliveryreads.droppedNoPipelinefor a source whose audio stream could not be picked,
not.noAudioInSource.av_find_best_streampasses 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
andloasdemuxers 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