Skip to content

AirPlay LAN-swap reload destroys host subtitle session state (mid-session external tracks, active selection, native-rendition pick) #170

Description

@dlev02

Summary

When wireless AirPlay engages or ends, the engine reloads itself to move the
loopback playlist onto the device's LAN IP and back
(handleExternalPlaybackChangereloadAtCurrentPosition(), the #86 path).
That reload is a full load(), and load()'s stopInternal wipes three
pieces of session state the host set up — none of which the engine restores:

  1. Mid-session external subtitle registrations (addExternalSubtitleTrack,
    Select external subtitles to make them appear in engine.$subtitleTracks. #88). load() clears externalSubtitleRegistry / subtitleTracks /
    nextExternalSubtitleOrdinal and re-registers only
    LoadOptions.externalSubtitles. Every track the host added after load is
    permanently gone — the host's menu still lists them, but selecting one
    targets a synthetic id that no longer exists.
  2. The active audio/subtitle selection. The reload re-runs
    preferred-language auto-selection, which can silently override the user's
    explicit picks (including subtitles they turned off).
  3. nativeSubtitleReapplyOrdinal. This is the sharpest edge: the host
    reacts to AirPlay starting by calling setNativeSubtitleRendering(true)
    (per the documented contract), which sets the reapply ordinal — and the
    engine's own AirPlay reload, triggered by the same KVO flip, then nils it
    in stopInternal. Net effect: the receiver renders no subtitles at
    all
    , even for plain embedded text tracks, unless the host knows to
    re-assert after an engine-initiated reload it never asked for.

Notably the #65/#93 stall-recovery path already handles this correctly for
in-place item swaps — after host.load(url:startPosition:inPlaceSwap: true)
it replays nativeSubtitleReapplyOrdinal onto the fresh item. The AirPlay
reload deserves the same courtesy, extended to all three items above.

Surfaced by a Prism beta report: streaming iOS → tvOS via AirPlay, "the
subtitle selection dropdown is tripping out" (the wipe/republish churn
re-rendering an open menu) "and no data seems to be sent to the AirPlay
server side" (items 2 and 3, plus overlay-only mid-session tracks).

Repro

  1. Host app: load() a stream with prepareNativeSubtitles = true, an
    embedded text subtitle track active, and one track added mid-session via
    addExternalSubtitleTrack.
  2. Start playback, then AirPlay the video to an Apple TV (wireless — the
    wired-HDMI branch doesn't reload and is unaffected).
  3. Call setNativeSubtitleRendering(true) on the external-playback flip (as
    documented for PiP/AirPlay).
  4. Observe: [AirPlay] external playback active (wireless) -> LAN media reload, then:
    • subtitleTracks republishes without the mid-session external track;
    • activeSubtitleTrackIndex is nil (or the preferred-language auto-pick,
      not the user's track);
    • no legible rendition is selected on the receiver — the Sodalite#38
      force-deselect loop runs because nativeSubtitleReapplyOrdinal is nil
      again.

Asks

  1. Carry session state across engine-initiated reloads (AirPlay LAN swap
    today; anything else that calls reloadAtCurrentPosition() internally):
    re-register mid-session external tracks (preserving their synthetic ids if
    possible), re-select the previously active audio/subtitle tracks instead
    of re-running auto-selection, and replay nativeSubtitleReapplyOrdinal
    the way the Presented frame drifts ~6 s ahead of currentTime/sourceTime after a rapid bidirectional seek burst (loopback-HLS native path) — frameAhead/producerShift/hostShift all report 0 #65 recovery already does.
  2. (Feature, separable) Native-rendition eligibility for
    addExternalSubtitleTrack: append a WebVTT rendition + cue store to the
    live loopback master/media playlists so mid-session tracks can survive
    PiP/AirPlay without the host performing a full re-declared reload. Today
    the host's only route is a position-preserving load() with the track in
    LoadOptions.externalSubtitles, which costs a visible rebuffer — during
    AirPlay, exactly when the user is most likely to be adding subtitles.

Host-side workaround (shipping in Prism meanwhile)

On every isExternalPlaybackActive flip: freeze the host's track/selection
mirrors against the reload's transient wipe publishes, wait for the reload to
settle, re-addExternalSubtitleTrack the dropped tracks, re-apply the
session's picks, promote an overlay-only active pick via a re-declared reload,
and re-call setNativeSubtitleRendering. It works, but it's ~150 lines of
host code compensating for state the engine could preserve in one place.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions