BunnyLiveStreamPlayerViewModel

Backing view model for BunnyLiveStreamPlayer. Owns the polling loop, the play-data fetches, and the trailer fetch — exposes a single StateFlow of LiveStreamPlayerState for the UI to consume. Mirrors the behaviour of Bunny's web player.

Responsibilities, in order of execution:

  1. start kicks off an initial fetchPlayData (carries stream snapshot + playable URL) and, if the snapshot warrants it, an async fetchTrailerUrl. Starts the 5s poll loop.

  2. The poll loop calls LiveStreamRepository.pollLiveStream every 5 s — lightweight, returns only the stream snapshot, no play-data. In-flight guard prevents overlapping requests.

  3. Whenever the snapshot status transitions to a state that needs a URL we don't have yet (Running with no live URL; Ended/VodProcessing+recordVod with no VOD URL), the view model re-fetches play-data to pick up the new URL. We avoid re-fetching on every poll because play-data is the heavier endpoint.

  4. Lifecycle: onForeground fires one immediate poll then resumes the 5s cadence; onBackground cancels the loop. Terminal failure (401/403/404/410) stops polling permanently per the spec.

The class deliberately does not own any Android Context, ExoPlayer, or View — those live in the composable. This keeps it unit-testable on the JVM with a fake LiveStreamRepository.

Constructors

Link copied to clipboard
constructor()

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
val liveStream: StateFlow<LiveStream?>
Link copied to clipboard
val playData: StateFlow<LiveStreamPlayData?>
Link copied to clipboard
val playerRebuildToken: StateFlow<Int>
Link copied to clipboard
Link copied to clipboard
val terminalError: StateFlow<String?>

Functions

Link copied to clipboard
open fun addCloseable(closeable: AutoCloseable)
fun addCloseable(key: String, closeable: AutoCloseable)
Link copied to clipboard
Link copied to clipboard

Called by the UI on lifecycle pause. Cancels the poll loop. The current state is preserved so the UI keeps rendering what it had.

Link copied to clipboard

Called by the UI on lifecycle resume. Per spec: fire one immediate poll, then resume the 5 s cadence. If polling has already been permanently stopped (terminal failure), this is a no-op.

Link copied to clipboard
fun onPlaybackFailure(message: String? = null)

Called by the playback surface when the live player errors mid-play (network drop, falling behind the live window, stale segment URLs). Mirrors the iOS player's recovery: re-poll the status and refresh play-data immediately; if the stream is still live afterwards, bump playerRebuildToken so the surface rebuilds the player from the live edge even when the URL didn't change. If the status flipped (offline/ended), state re-routes the UI instead.

Link copied to clipboard
fun start(libraryId: Long, streamId: String, token: String? = null, expires: Long? = null)

Initialise the view model. Idempotent; subsequent calls with the same streamId are ignored. Must be called before onForeground / onBackground.

Link copied to clipboard

Forces a state recomputation. Used by the countdown UI on each tick so an expiring scheduledStartTime flips to "Starting soon…" without waiting for a poll.