ffmpeg's reconnect loop deliberately retries 503 without bound (#1520), so a
server that keeps refusing the stream at open time left a silent black screen:
ExoPlayer fell back to MPV, MPV reconnected forever, and no error ever reached
the screen. A new open-phase watchdog arms on the first 503 seen before any
frame renders and, after 20s without one, synthesizes a server-http-503 error
that shows an actionable dialog. Mid-stream 503s and live TV keep their
existing ride-out paths.
close#1830
The 404 branch added in 16668be5 ran ahead of the live-TV fallback chain, so a
transient live 404 — an HLS segment rolled off the playlist, or a transcode
session restarting under us — showed "file unavailable" and killed a stream
the bounded ladder would have recovered. Only on-demand playback can read a
404 as terminal, where it really does mean the file is unreadable. 500 stays
terminal for both, since a limit rejection is not something a retry clears.
The dispatch lived in a private extension on the screen state, where no test
could reach the decision. Extract it as resolvePlaybackFailureAction next to
runLiveStreamRetry, which already sets that precedent, and cover both the live
and on-demand paths plus the ladder's rungs.