Skip to content

[Bug]: AVPlayer wedges ~28s on Dolby-Vision resume when the HDMI mode-switch resets the first (empty-buffer) segment fetch #10

Description

@thatcube

Summary

Resuming a Dolby-Vision MKV through the Plozzigen/AetherEngine hybrid engine intermittently freezes for ~28s (black screen, audio silent, last subtitle stuck on screen) before recovering. Root-caused with on-device instrumentation: AVPlayer wedges — after the async DV HDMI mode-switch resets AVPlayer's loopback HLS connection mid-fetch on the first resume segment (while its buffer is still empty), AVPlayer enters AVPlayerWaitingToMinimizeStallsReason and never re-requests the segment, so playback deadlocks until the app-level watchdog fires at ~30s. This is an AetherEngine-level issue (DV display switch × loopback HLS × AVPlayer), not SMB- or transport-specific — it will affect any DV source that routes through the hybrid engine.

Steps to reproduce

  1. Resume (not start-from-0) a 4K Dolby Vision (Profile 8.1, DOVIWithHDR10) MKV that routes to Plozzigen.
  2. Watch the first frame appear, then freeze.
  3. Intermittent — it's a timing race, but resuming the same file at the same position reproduces it often.

Expected behavior

Playback continues within a second or two of the first frame, every time.

Actual behavior

~28s freeze (black, no audio, stale subtitle), then the app watchdog tears down and recovers (or cross-source fails over to another server's copy).

Affected backend(s)

SMB share

Plozz version & build

Dev branch thatcube-assess-emby-support, AetherEngine diagnostic pin f1a3c56 (= d97fad24 + [LagDiag]-to-journal instrumentation).

tvOS version

tvOS (Apple TV 4K, current dev toolchain)

Apple TV model

Apple TV 4K

Logs / screenshots

Definitive proof (on-device [LagDiag] journal, 1 Hz, during the freeze):

GET /seg539.mp4                       ← first resume-segment fetch (buffer empty)
[DisplayCriteria] WARN switch ended (~1550ms) but EDR headroom still 1.0 (panel stayed SDR despite HDR criteria)
first-frame
[LagDiag] clk=2696.96 tcs=waiting wait=AVPlayerWaitingWhileEvaluatingBufferingRateReason req=4 dreq=0 empty=n
send failed for /seg539.mp4: errno=54  ← AVPlayer resets its own loopback connection (ECONNRESET) during the DV flip
[LagDiag] clk=2696.96 dclk=0.00 tcs=waiting wait=AVPlayerWaitingToMinimizeStallsReason req=4 dreq=0 empty=y
[LagDiag] ... req=4 dreq=0 empty=y ...   ← repeats UNCHANGED for 27 ticks (~28s)
engine RETRY_FRESH (app watchdog @ ~30s)

Key signals:

  • req=4 dreq=0 frozen for the entire freeze — AVPlayer stops issuing GETs entirely; it never re-requests the reset segment. Proves an AVPlayer-side wedge, not a server/producer/SMB fault (during the freeze the SMB producer keeps reading fine).
  • wait=AVPlayerWaitingToMinimizeStallsReason + empty=y — AVPlayer waits to minimize stalls with an empty buffer while fetching nothing to fill it.
  • errno=54 (ECONNRESET) lands ~200ms after the DV switch "ends" — the flip disrupts AVPlayer's in-flight fetch.

Differentiator (why it's sometimes instant): the same session captured a recovering case — when the reset landed with data already buffered (fwd=4.0), AVPlayer re-requested (req 5→9) and moved to the next segment. So: reset on the first segment with an empty buffer → wedge; reset with a non-empty buffer → survives. It's a race between the async, app-invisible DV HDMI flip and AVPlayer's first fetch.

Constraint on any fix: DV Profile 8.1 decode requires the display criteria set before decode starts (the load-bearing pre-play settle), and the flip is async + unobservable (currentEDRHeadroom stays 1.0), so the flip inherently lands after playback begins. The existing "settle DV on notifications" fix (8c1f979, already in d97fad24) only reduced first-frame latency; it does not prevent this wedge.

Fix direction (to design/upstream in AetherEngine, later): make the first post-seek fetch survive the flip — e.g. a tiny/pre-warmed first segment at seek so it's fully buffered before the flip can reset it (turning every case into the "recovering" path), or an engine-side detect-and-nudge (playImmediately/micro-seek) when tcs=waiting + empty=y + req frozen shortly after first-frame. Deprioritized for now; filing with proof so it isn't lost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions