UserController.java
Overview
`UserController` is a core class that manages and mediates user interactions with the media player, specifically an ExoPlayer instance. It acts as the bridge between the player UI and the underlying playback engine, handling commands such as play/pause toggling, seeking, subtitle toggling, and ad interaction. The class uses Android's data binding (`BaseObservable`) to expose observable playback states and media metadata to the UI layer, ensuring real-time synchronization of UI components with the player's current status.
Primarily, it serves as a **playback control interface** and listens to player events to update UI-bound properties, while also processing user input events from UI controls (e.g., seek bar interactions). It supports specialized control states to represent different user interaction modes such as normal playback, custom seeking, and options menu navigation.
By encapsulating playback control logic and state management, `UserController` enables a clean separation between UI components and business logic, facilitating maintainable and testable code within a media playback application.
Class: UserController
**Package:** `com.tubitv.media.bindings`
**Extends:** `BaseObservable` **Implements:**
TubiPlaybackControlInterface— playback control contractExoPlayer.EventListener— listen to ExoPlayer playback eventsSeekBar.OnSeekBarChangeListener— listen to UI seek bar changes
Constants
Name | Description |
|---|---|
`NORMAL_CONTROL_STATE` | State representing normal playback controls (value `1`). |
`CUSTOM_SEEK_CONTROL_STATE` | State for custom seek control entered by long-press seek (value `2`). |
`EDIT_CUSTOM_SEEK_CONTROL_STATE` | State for editing custom seek during long press (value `3`). |
State when UI focuses on options like captions (value `4`). | |
`DEFAULT_FREQUENCY` | Frequency in milliseconds for progress update timer (1000 ms). |
Observable Fields (Data Binding Properties)
These fields expose playback and media metadata state to the UI layer:
Field Name | Type | Description |
|---|---|---|
`playerPlaybackState` | `ObservableInt` | Current playback state from ExoPlayer (idle, ready, ended, buffering). |
`isVideoPlayWhenReady` | `ObservableBoolean` | Whether playback is currently playing (playWhenReady). |
`videoName` | `ObservableField` | Title or name of the currently playing video or ad. |
`videoPoster` | `ObservableField` | URI of the video poster image (thumbnail/artwork). |
`videoMetaData` | `ObservableField` | Additional metadata string (unused in current code). |
`videoDuration` | `ObservableField` | Total duration of the video in milliseconds. |
`videoCurrentTime` | `ObservableField` | Current playback position in milliseconds. |
`videoBufferedPosition` | `ObservableField` | Current buffered position in milliseconds. |
`videoRemainInString` | `ObservableField` | Formatted string representing remaining playback time. |
`videoPositionInString` | `ObservableField` | Formatted string for current playback time. |
`videoHasSubtitle` | `ObservableField` | Whether the current video has subtitle available. |
`adClickUrl` | `ObservableField` | URL for the current ad's click-through link. |
`adMetaData` | `ObservableField` | Metadata string related to the ad (unused in current code). |
`numberOfAdsLeft` | `ObservableInt` | Number of ads left in the playback queue. |
`isCurrentAd` | `ObservableField` | Flag indicating if the current playback is an ad. |
`isSubtitleEnabled` | `ObservableField` | Whether subtitles are currently enabled. |
`isDraggingSeekBar` | `ObservableField` | Whether the user is currently dragging the seek bar. |
Member Variables
Name | Type | Description |
|---|---|---|
`SimpleExoPlayer` | The ExoPlayer instance controlled by this UserController. | |
`MediaModel` | The current media (video or ad) being played. | |
`PlaybackActionCallback` | Callback interface for notifying playback-related user actions. | |
`TubiExoPlayerView` | Reference to the custom player view hosting video and subtitles. | |
`int` | Current control state (one of the constants). | |
`mProgressUpdateHandler` | `Handler` | Handler for scheduling periodic progress updates. |
`updateProgressAction` | `Runnable` | Runnable task that updates playback progress and UI. |
`Runnable` | Optional callback to run when control state changes. |
Public Methods
void setMediaModel(@NonNull MediaModel mediaModel, Context context)
Sets the current media model (video or ad) and updates observable metadata fields accordingly.
Parameters:
mediaModel: The media object currently being played (cannot be null).context: Android Context for resource access and device checks.
Behavior:
Updates
isCurrentAdflag based on whether the media is an ad.For ads:
Sets
adClickUrlif available (for non-TV devices).Sets video name to a localized "commercial" string.
Disables subtitles.
For content videos:
Sets video name from media.
Sets poster artwork if available and device is not TV.
Enables subtitle flag if subtitles URL is present.
Example:
MediaModel video = ...;
userController.setMediaModel(video, context);
void setPlayer(@NonNull SimpleExoPlayer player, @NonNull PlaybackActionCallback playbackActionCallback, @NonNull TubiExoPlayerView tubiExoPlayerView)
Associates an ExoPlayer instance, playback callback, and player view with this controller.
Parameters:
player: ExoPlayer instance to control.playbackActionCallback: Callback interface for playback events.tubiExoPlayerView: Custom player view containing video and subtitles.
Behavior:
Removes any previous player listener.
Sets the new player and adds this controller as an event listener.
Initializes playback state observables.
Starts progress updates.
Example:
userController.setPlayer(exoPlayer, playbackActionCallback, tubiExoPlayerView);
void setAvailableAdLeft(int count)
Updates the number of ads left to play.
Parameters:
count: Number of ads remaining.
void updateTimeTextViews(long position, long duration)
Updates the formatted time strings for the current playback position and remaining time.
Parameters:
position: Current playback position in milliseconds.duration: Total duration in milliseconds.
void togglePlayPause()
Toggles playback between play and pause states.
Behavior:
Inverts current
isVideoPlayWhenReadyflag and triggers play/pause accordingly.
void fastForward()
Seeks forward by a predefined fast seek interval.
void fastRewind()
Seeks backward by a predefined rewind interval.
int getState()
Returns the current control state.
Returns: Integer representing control state (
NORMAL_CONTROL_STATE, etc.)
void setState(int state)
Sets the control state and triggers any registered state change callback.
Parameters:
state: New control state.
boolean isDuringCustomSeek()
Checks if the controller is currently in a custom seek mode.
Returns:
trueif inCUSTOM_SEEK_CONTROL_STATEorEDIT_CUSTOM_SEEK_CONTROL_STATE.
void setOnControlStateChange(Runnable onControlStateChange)
Registers a callback to be executed when the control state changes.
Parameters:
onControlStateChange: ARunnablecallback.
Playback Control Interface Implementations
The following methods implement `TubiPlaybackControlInterface`:
void triggerSubtitlesToggle(boolean enabled): Shows or hides subtitles and notifies callbacks.void seekBy(long millisecond): Seeks forward or backward relative to current position.void seekTo(long millisecond): Seeks to an absolute position.void triggerPlayOrPause(boolean setPlay): Starts or pauses playback.void clickCurrentAd(): Handles "Learn More" ad click event.String getCurrentVideoName(): Returns current video name.boolean isPlayWhenReady(): Returns if video is playing.boolean isCurrentVideoAd(): Returns if current video is an ad.long currentDuration(): Returns video duration.long currentProgressPosition(): Returns current playback position.long currentBufferPosition(): Returns current buffered position.
SeekBar Listener Implementations
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser): Updates time display when user drags seek bar.onStartTrackingTouch(SeekBar seekBar): Sets flag indicating user started dragging.onStopTrackingTouch(SeekBar seekBar): Performs seek to new position and clears dragging flag.
Player Event Listener Methods
The class listens to ExoPlayer events to keep observables updated:
onTimelineChanged(...): Updates playback state and progress.onPositionDiscontinuity(...): Updates playback state and progress.onPlayerStateChanged(boolean playWhenReady, int playbackState): Updates playback state observables and triggers progress update.onRepeatModeChanged(int repeatMode): No operation.onShuffleModeEnabledChanged(boolean shuffleModeEnabled): No operation.onTracksChanged(...): Logs track changes.onLoadingChanged(boolean isLoading): Logs loading status.onPlayerError(ExoPlaybackException error): Logs errors.onPlaybackParametersChanged(PlaybackParameters playbackParameters): No operation.onSeekProcessed(): No operation.
Private Helper Methods
void setPlaybackState(): Reads player playback state and updates observable.void seekToPosition(long positionMs): Seeks the player to a specified position.void updateProgress(): Periodically updates current play position, buffered position, and notifies UI and callbacks.void updateSeekBar(long position, long duration, long bufferedPosition): Updates observable seek bar properties.
Important Implementation Details
Data Binding: The class exposes many playback and media state fields as
ObservableFieldorObservableIntso that UI components can bind directly and update automatically on changes.Control States: The controller tracks different UI states (normal, custom seek, options) which affect how user inputs are interpreted.
Progress Updates: Uses Android
Handlerfor scheduled updates every second to refresh seek bar and playback times, but suspends updates if user is dragging the seek bar or in custom seek mode to avoid UI conflicts.Player Event Handling: Reacts to ExoPlayer lifecycle and playback changes, ensuring the UI is always consistent with actual player state.
Ad Handling: Differentiates ads from regular content, exposing ad-specific metadata and click URLs for UI and callbacks.
Subtitle Toggle: Directly controls subtitle view visibility in
TubiExoPlayerViewand informs callbacks about subtitle state changes.Callback Integration: Uses
PlaybackActionCallbackinterface to notify higher-level components (e.g., FSM player) about user-driven actions like seek, play toggle, and ad clicks.
Usage Example
// Initialize UserController and bind with player and UI
UserController userController = new UserController();
// Set player instance and playback callbacks
userController.setPlayer(simpleExoPlayer, playbackCallback, tubiExoPlayerView);
// Set current media (video or ad)
userController.setMediaModel(mediaModel, context);
// Bind this controller to UI controls, e.g., seek bar listeners
seekBar.setOnSeekBarChangeListener(userController);
// Toggle play/pause on user button click
playPauseButton.setOnClickListener(v -> userController.togglePlayPause());
// Enable or disable subtitles
subtitleToggle.setOnCheckedChangeListener((buttonView, isChecked) -> userController.triggerSubtitlesToggle(isChecked));
Interactions with Other Components
TubiExoPlayerView: The
UserControllerkeeps a reference to this view to directly control subtitle visibility.SimpleExoPlayer: The controller listens to this player’s events and sends commands (play, pause, seek).
PlaybackActionCallback: External interface receiving user action notifications to coordinate with business logic (e.g., FSM player states).
UI components: Binds observable fields to views (TextViews, SeekBars, Buttons) via Android Data Binding, enabling auto UI updates.
MediaModel: Provides media metadata and ad information to update UI accordingly.
Mermaid Class Diagram
classDiagram
class UserController {
+ObservableInt playerPlaybackState
+ObservableBoolean isVideoPlayWhenReady
+ObservableField<String> videoName
+ObservableField<Uri> videoPoster
+ObservableField<Long> videoDuration
+ObservableField<Long> videoCurrentTime
+ObservableField<Long> videoBufferedPosition
+ObservableField<String> videoRemainInString
+ObservableField<String> videoPositionInString
+ObservableField<Boolean> videoHasSubtitle
+ObservableField<String> adClickUrl
+ObservableInt numberOfAdsLeft
+ObservableField<Boolean> isCurrentAd
+ObservableField<Boolean> isSubtitleEnabled
+ObservableField<Boolean> isDraggingSeekBar
+void setMediaModel(MediaModel mediaModel, Context context)
+void setPlayer(SimpleExoPlayer player, PlaybackActionCallback playbackActionCallback, TubiExoPlayerView tubiExoPlayerView)
+void togglePlayPause()
+void fastForward()
+void fastRewind()
+void seekTo(long millisecond)
+void seekBy(long millisecond)
+void triggerSubtitlesToggle(boolean enabled)
+void clickCurrentAd()
+int getState()
+void setState(int state)
+boolean isDuringCustomSeek()
+void setOnControlStateChange(Runnable onControlStateChange)
+void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
+void onStartTrackingTouch(SeekBar seekBar)
+void onStopTrackingTouch(SeekBar seekBar)
+void onPlayerStateChanged(boolean playWhenReady, int playbackState)
+void onPlayerError(ExoPlaybackException error)
}
UserController --|> TubiPlaybackControlInterface
UserController --|> ExoPlayer.EventListener
UserController --|> SeekBar.OnSeekBarChangeListener
Summary
`UserController` is a pivotal component in the media playback architecture that abstracts playback control logic and state management from UI concerns. It exposes rich observable properties for data binding, handles user interaction events, listens to ExoPlayer events, and manages playback states including ads and subtitles. Its design enables a responsive and synchronized user experience while keeping UI code clean and maintainable.
This class integrates tightly with the `TubiExoPlayerView` for subtitle display, ExoPlayer for playback, and higher-level playback callbacks to coordinate user actions with business logic layers such as FSM state machines.