Media Playback and UI Controls

This module provides the core media playback capabilities paired with rich user interface controls to deliver an interactive video viewing experience. It integrates adaptive streaming playback using ExoPlayer and exposes user interactions such as play/pause toggling, seeking, subtitle management, and ad-related UI feedback through data-binding-enabled controllers.


Overview

The **Media Playback and UI Controls** module focuses on managing the media player lifecycle, rendering video and subtitles, and providing a responsive UI layer tightly coupled with playback state. It addresses the need for:

This module is designed to be extensible for various playback activities and UI designs, supporting both content and ad playback scenarios.


Key Components and Workflows

1. Base Playback Activity (TubiPlayerActivity)

The `TubiPlayerActivity` class is an abstract base activity that encapsulates the setup and lifecycle management of a single ExoPlayer instance intended for content playback. It provides foundational functionality, including:

This class also provides methods to access the playback controller interface (`TubiPlaybackControlInterface`) via the player view.

protected void initMoviePlayer() {
    mMainHandler = new Handler();
    TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
    mTrackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
    mMoviePlayer = ExoPlayerFactory.newSimpleInstance(this, mTrackSelector);
    mEventLogger = new EventLogger(mTrackSelector);
    mMoviePlayer.addAnalyticsListener(mEventLogger);
    mMoviePlayer.addMetadataOutput(mEventLogger);
    mTubiPlayerView.setPlayer(mMoviePlayer, this);
    mTubiPlayerView.setMediaModel(mediaModel);
}

2. Custom Player View (TubiExoPlayerView)

`TubiExoPlayerView` is a custom view component that hosts the ExoPlayer video surface, subtitles, and user interaction controls. It encapsulates:

This view acts as the primary UI container for video playback and controls.

public void setPlayer(SimpleExoPlayer player, @NonNull PlaybackActionCallback playbackActionCallback) {
    // Setup player listeners and assign video surface
    this.player = player;
    if (userController != null) {
        userController.setPlayer(player, playbackActionCallback, this);
    }
    // Additional setup omitted for brevity
}

3. User Interaction Controller (UserController)

`UserController` serves as the bridge between the ExoPlayer playback engine and the UI layer. It is a `BaseObservable` class enabling data binding with UI components. Its responsibilities include:

This class abstracts player control logic and maintains UI state consistency.

public void togglePlayPause() {
    triggerPlayOrPause(!isVideoPlayWhenReady.get());
}

@Override
public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
    playerPlaybackState.set(playbackState);
    isVideoPlayWhenReady.set(playWhenReady);
    updateProgress();
}

4. UI Playback Controller View (UIControllerView)

`UIControllerView` is a `FrameLayout` subclass that hosts the actual playback controls UI, which is bound to a `UserController` instance. Key features include:

This view is designed to be embedded into player layouts to provide a ready-to-use control interface.

@Override
public boolean onTouchEvent(final MotionEvent event) {
    countdownHandler.removeCallbacks(hideUIAction);
    // Toggle visibility on user touch
    if (binding.controllerPanel.getVisibility() == VISIBLE) {
        binding.controllerPanel.setVisibility(GONE);
    } else {
        if (userController.playerPlaybackState.get() != Player.STATE_IDLE) {
            binding.controllerPanel.setVisibility(VISIBLE);
            hideUiTimeout();
        }
    }
    return super.onTouchEvent(event);
}

5. Player UI Controller (PlayerUIController)

This class manages the relationship between the content player and ad player instances along with VPAID WebView integration and playback state history. It provides:

This controller enables smooth transitions and state preservation between content and ad playback.

public SimpleExoPlayer getAdPlayer() {
    if (PlayerDeviceUtils.useSinglePlayer()) {
        return contentPlayer;
    }
    return adPlayer;
}

public void setPlayFromHistory(long pos) {
    hasHistory = true;
    historyPosition = pos;
}

Interactions Between Components


Design Patterns and Concepts


Code Snippets Illustrating Core Concepts

Media Source Construction Based on Streaming Type

protected MediaSource buildMediaSource(MediaModel model) {
    int type = TextUtils.isEmpty(model.getMediaExtension()) ? Util.inferContentType(model.getVideoUrl())
            : Util.inferContentType("." + model.getMediaExtension());

    switch (type) {
        case C.TYPE_SS:
            return new SsMediaSource(model.getVideoUrl(), buildDataSourceFactory(false),
                    new DefaultSsChunkSource.Factory(mMediaDataSourceFactory), mMainHandler, mEventLogger);
        case C.TYPE_DASH:
            return new DashMediaSource(model.getVideoUrl(), buildDataSourceFactory(false),
                    new DefaultDashChunkSource.Factory(mMediaDataSourceFactory), mMainHandler, mEventLogger);
        case C.TYPE_HLS:
            return new HlsMediaSource(model.getVideoUrl(), mMediaDataSourceFactory, mMainHandler,
                    mEventLogger);
        case C.TYPE_OTHER:
            return new ExtractorMediaSource(model.getVideoUrl(), mMediaDataSourceFactory,
                    new DefaultExtractorsFactory(), mMainHandler, mEventLogger);
        default:
            throw new IllegalStateException("Unsupported type: " + type);
    }
}

Playback State Update and UI Synchronization in UserController

@Override
public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
    playerPlaybackState.set(playbackState);
    isVideoPlayWhenReady.set(playWhenReady);
    updateProgress();
}

User Interaction Handling for SeekBar

@Override
public void onStartTrackingTouch(final SeekBar seekBar) {
    isDraggingSeekBar.set(true);
}

@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
    if (mPlayer != null) {
        seekTo(Utils.progressToMilli(mPlayer.getDuration(), seekBar));
    }
    isDraggingSeekBar.set(false);
}

Mermaid Diagram: Media Playback and UI Interaction Flow

sequenceDiagram
    participant Activity as TubiPlayerActivity
    participant PlayerView as TubiExoPlayerView
    participant UserCtrl as UserController
    participant ExoPlayer as SimpleExoPlayer
    participant UI as UIControllerView (Data Binding)

    Activity->>PlayerView: setPlayer(ExoPlayer instance)
    PlayerView->>UserCtrl: setPlayer(ExoPlayer, PlaybackCallback)
    UserCtrl->>ExoPlayer: register event listeners
    ExoPlayer-->>UserCtrl: playback events (state changes, position)
    UserCtrl->>UI: update observable fields (playback state, position)
    UI->>UserCtrl: user interactions (play/pause, seek, toggle subtitles)
    UserCtrl->>ExoPlayer: control playback accordingly
    UserCtrl->>Activity: notify user actions via PlaybackActionCallback

This detailed explanation clarifies how media playback and user interface controls are structured and operate cohesively within the module, highlighting their roles, interactions, and design principles. The integration of adaptive streaming, data binding, and player event handling enables a responsive and maintainable playback experience.