AetherEngine 1.2.0
Point release on top of 1.1.0. Five fixes against three PTS-domain
bugs the 1.1.0 producer / bridge architecture surfaced once real
content with non-AAC audio and embedded PGS subtitles hit it. Plus
the diagnostics that made those bugs visible in the first place.
Audio (FLAC bridge)
Gate target rescaled into source TB, not bridge encoder TB. The
producer's audio scan-forward gate computed its target dts by
rescaling firstActualVideoDts into audio.inputTimeBase. For
stream-copy that was fine (inputTimeBase equals the source audio
stream's TB), but for the FLAC bridge inputTimeBase is the encoder
TB (1/48000) while incoming packet.dts is in the demuxer's source
TB (matroska's 1/1000). Net effect on a typical DTS-HD MA source:
the gate target landed 48× too far into the source, audio started
44 s after video on cold start, and producer restarts amplified to
~490 s of A/V drift. Symptom was a confusing pair of reports
("audio asynchron" + "Track-Switch lädt unendlich") that turned out
to be the same wrong rescale.
MP3 routed through the FLAC bridge. AudioCodecCompat used to
classify MP3 as fMP4-legal stream-copy and emit mp4a.40.34. The
fMP4 sample entry is spec-correct, but AVPlayer reads any mp4a
entry as AAC and fails to decode the MP3 frames with
AVFoundationErrorDomain -11829 (CoreMedia -12848) within a few
hundred ms of load. Same shape as the existing Opus workaround:
spec-legal in fMP4, rejected by AVPlayer downstream. Route MP3
through the bridge so AVPlayer gets fLaC instead. Trade-off: MP3
loses zero-overhead stream-copy, bridge cost on a lossy mono/stereo
source is negligible. Reported in #6 by @hp561.
Encoder PTS rebased off packet pts, not frame pts. For codecs
with decoder priming samples (Opus preskip, AAC encoder delay),
libavcodec's discard-samples path trims leading samples from the
first decoded frame AND advances frame.pts by the same amount.
AudioBridge rebased the FLAC encoder timeline off frame.pts, so
the first encoded sample's pts ended up preskip-shifted relative to
source-PTS=0. The producer's audio gate opened ahead of the video
gate and AVPlayer stalled in waitingToPlay waiting for an audio
segment that never lined up. Capture packet.pts at feed() entry
and use it for the rebase. Reported in #7 by @hp561, verified on
Apple TV 4K (3rd gen) with Opus-in-MKV.
Subtitles
Embedded subtitle cue.startTime is absolute source PTS seconds
again. The decoder was subtracting AVStream.start_time from each
packet's PTS before emitting the cue, with a comment claiming the
subtraction produced "absolute source seconds matching AVPlayer's
clock". That assumption only held when the subtitle stream's
start_time equalled the video stream's. MKV doesn't behave that
way: PGS / SRT / SSA tracks have no continuous packet flow, so
AVStream.start_time on a subtitle stream is the PTS of the first
cue in the movie, often 19+ s in. Harry Potter 1: first PGS cue at
source PTS=19.186 s, streamStartTime=19186 → cue emitted with
startTime=0, displayed immediately at session-start, every
subsequent cue shifted forward by the same 19 s. Host filters cues
against engine.sourceTime already (= AVPlayer.currentTime + playlistShiftSeconds, both in absolute source PTS seconds), so the
decoder just needs to emit raw source PTS. Drop the offset entirely
and the parameter the caller no longer needs to plumb.
Diagnostics
Silent restart hangs in producer + reload path now surfaced via
EngineLog. Two gaps were letting "lädt unendlich" / "Scrubbing
reagiert nicht" bugs reach the user without a paper trail:
HLSSegmentProducer's pre-gate drop loop counts skipped packets
and emitsstill waiting for video keyframe: dropped=N target=X
every 200 drops. If no IDR ever turns up that satisfies the
restart target dts (open-GOP source with all CRAs sitting behind
the target, or an MKV whose Cues lied about a packet being a
keyframe), the failure mode is visible in Support → Logs instead
of a black screen.AetherEngine.reloadWithAudioOverridetags each phase with
elapsed-ms markers:stopInternal done,loadNative enter/
done,state=.playing total. The user-facing "frozen overlay
during track switch" case now maps to a concrete phase in the log.
The matching audio-gate drop loop gets the same treatment, which is
how the FLAC-bridge gate TB mismatch above was diagnosed inside a
single playback session.
Engine pin
For Sodalite hosts: bump Package.resolved to f0c4198 (or use the
1.2.0 tag).