ExoPlayerAdapter
Media3/ExoPlayer implementation of the portable PlayerAdapter (P4b-2).
Wraps an ExoPlayer the integrator owns (created and attached to their own PlayerView); the SDK only ever drives the content player through this adapter, keeping the brain player-agnostic. A single Player.Listener fans Media3 callbacks out to the adapter's PlayerAdapterEvent handlers.
Thread safety
ExoPlayer is thread-affine: every access must happen on its application thread (ExoPlayer.applicationLooper, normally the main thread) or it throws IllegalStateException: Player is accessed on the wrong thread.
The SDK cannot honour that from the caller's side. BreakScheduler.tick() reads currentTime on whatever thread drives the ticker, and both OptiViewAds's default scope and a plainly-constructed CoroutineSchedulerTicker use Dispatchers.Default — so ticking crashed on the very first tick unless the integrator happened to pass a Dispatchers.Main scope (which is the only reason the demo apps worked).
This adapter therefore makes itself safe from ANY thread:
Reads (currentTime, duration, paused, muted, volume, programDateTime) are served from a
@Volatilesnapshot refreshed ON the application thread — after every listener callback and on a periodic poll (position advances between callbacks, so events alone are not enough).Commands (pause, play, seek, load, and the muted/volume setters) run inline when already on the application thread, and are posted to it otherwise.
Properties
PlayerAdapter.currentTime from the application-thread snapshot, in seconds.
PlayerAdapter.duration from the snapshot, in seconds; the live window length for live streams.
PlayerAdapter.isLive from the snapshot: whether the current Media3 window is live.
PlayerAdapter.muted: reads the snapshot, writes on the application thread.
PlayerAdapter.paused from the snapshot.
Playback speed, read from the ENGINE rather than echoed back from the last write.
True on live: media3's getCurrentPosition() is relative to the current Timeline.Window, whose start advances as the live window slides. See the contract for what that breaks.
Wallclock of the current playhead (epoch ms) for HLS EXT-X-PROGRAM-DATE-TIME matching, derived from the live window's start time plus the position in the window. Null when the timeline carries no wallclock anchor (VOD/no PDT).
PlayerAdapter.volume (0.0 to 1.0): reads the snapshot, writes on the application thread.
Functions
ContentVideoSizeProvider: lets the renderer confine break layouts to the video.
PlayerAdapter.destroy: detaches from the player and stops the snapshot poll; the player itself is left to the app.
PlayerAdapter.load: sets url as the player's media item and prepares it, on the application thread.
PlayerAdapter.off: removes a handler added with on.
PlayerAdapter.on: subscribes handler to event.
PlayerAdapter.pause on the application thread.
PlayerAdapter.play on the application thread.
Pin to the cheapest rendition with setMaxVideoBitrate, not by selecting a track.