What happened?
LiveTelemetrySampler.tick() (Sources/AetherEngine/Diagnostics/LiveTelemetrySampler.swift, ~line 207) computes the session-average bitrate as:
let elapsed = max(0.5, Date().timeIntervalSince(sessionStartTime))
let lifetimeBytes = max(0, demuxerBytes - sessionStartBytes)
averageBitrateMbps = Double(lifetimeBytes) * 8.0 / elapsed / 1_000_000.0
The denominator is pure wall-clock time since the session started. It never subtracts time the session spent paused.
While playback is paused, demuxerBytes stops advancing (once the forward buffer is full) but elapsed keeps growing. So the reported average falls steadily the longer the session sits paused, trending toward zero. When playback resumes it climbs back only slowly and never returns to the true average, because the paused seconds stay in the divisor permanently.
instantBitrateMbps is unaffected — it's windowed over recent ticks. Only the lifetime average is wrong.
The value is wrong at the source, in LiveTelemetry.averageBitrateMbps, so every consumer inherits the error — including the "divide file size by averageBitrateMbps" download-time estimate suggested in the doc-comment on LiveTelemetry itself. It's visible in Sodalite's Stats for Nerds "Bitrate" row (avg N Mbps), but that's just where it shows up, not where it originates.
Steps to reproduce
- Start any session that produces
LiveTelemetry (native or software path), e.g. a direct-play VOD file.
- Let it play ~30 s. Note the average bitrate — it sits near the media's real bitrate.
- Pause for 2–3 minutes.
- Observe the average has dropped well below the real bitrate, roughly in proportion to
playtime ÷ total elapsed.
- Resume. The average recovers only gradually and asymptotes below the true value — the paused interval is permanently in the denominator.
Expected: the average is taken over time the session was actually playing/transferring, so a pause leaves it flat rather than dragging it down.
AetherEngine version or commit SHA
6.73.0
Host app
Sodalite
Platform
tvOS
OS version
tvos 27
Device / chip
Apple TV 4K
Playback path
Not sure / not a playback bug
Source media (for playback bugs)
No response
Error codes / log lines
Anything else
Why this belongs in AetherEngine and can't just be fixed in Sodalite: averageBitrateMbps is computed in the sampler and shipped as a field of the public LiveTelemetry struct. Sodalite only reads it. A host-side workaround (freeze the last playing value while state == .paused before displaying it) hides the symptom in one row of one screen, but the field stays wrong for every other consumer — the download-time estimate the struct's own documentation points at, any future HUD, any logging. The divisor is the bug and only the engine owns it.
Suggested fix: accumulate an "active" duration — wall-clock minus time spent in .paused / .seeking — and divide by that instead of Date().timeIntervalSince(sessionStartTime). Alternatively, integrate bytes-over-time across the sampler's own tick windows and only advance the accumulator on ticks where the transport is playing.
What happened?
LiveTelemetrySampler.tick()(Sources/AetherEngine/Diagnostics/LiveTelemetrySampler.swift, ~line 207) computes the session-average bitrate as:The denominator is pure wall-clock time since the session started. It never subtracts time the session spent paused.
While playback is paused,
demuxerBytesstops advancing (once the forward buffer is full) butelapsedkeeps growing. So the reported average falls steadily the longer the session sits paused, trending toward zero. When playback resumes it climbs back only slowly and never returns to the true average, because the paused seconds stay in the divisor permanently.instantBitrateMbpsis unaffected — it's windowed over recent ticks. Only the lifetime average is wrong.The value is wrong at the source, in
LiveTelemetry.averageBitrateMbps, so every consumer inherits the error — including the "divide file size byaverageBitrateMbps" download-time estimate suggested in the doc-comment onLiveTelemetryitself. It's visible in Sodalite's Stats for Nerds "Bitrate" row (avg N Mbps), but that's just where it shows up, not where it originates.Steps to reproduce
LiveTelemetry(native or software path), e.g. a direct-play VOD file.playtime ÷ total elapsed.Expected: the average is taken over time the session was actually playing/transferring, so a pause leaves it flat rather than dragging it down.
AetherEngine version or commit SHA
6.73.0
Host app
Sodalite
Platform
tvOS
OS version
tvos 27
Device / chip
Apple TV 4K
Playback path
Not sure / not a playback bug
Source media (for playback bugs)
No response
Error codes / log lines
Anything else
Why this belongs in AetherEngine and can't just be fixed in Sodalite:
averageBitrateMbpsis computed in the sampler and shipped as a field of the publicLiveTelemetrystruct. Sodalite only reads it. A host-side workaround (freeze the last playing value whilestate == .pausedbefore displaying it) hides the symptom in one row of one screen, but the field stays wrong for every other consumer — the download-time estimate the struct's own documentation points at, any future HUD, any logging. The divisor is the bug and only the engine owns it.Suggested fix: accumulate an "active" duration — wall-clock minus time spent in
.paused/.seeking— and divide by that instead ofDate().timeIntervalSince(sessionStartTime). Alternatively, integrate bytes-over-time across the sampler's own tick windows and only advance the accumulator on ticks where the transport is playing.