Dual Player Activity
The **Dual Player Activity** module implements a specialized player activity that manages two separate ExoPlayer instances simultaneously: one dedicated to content playback and the other dedicated to advertisement playback. This design supports seamless integration and concurrent control of video content and ads, enabling advanced playback features such as preroll, midroll, and postroll ads with smooth transitions.
Overview
The core class for this module is `DoubleViewTubiPlayerActivity`, which extends a base player activity and implements interfaces for dual player control and auto-play behavior. This activity encapsulates the logic for initializing, managing, and releasing two distinct media players, coordinating them through a finite state machine (FSM) player (`FsmPlayer`) that orchestrates playback states and transitions.
Key features include:
Dual ExoPlayer Setup: Separate player instances for content and ads with independent track selection and bandwidth monitoring.
FSM Integration: The FSM player manages playback states involving content and ads, using injected controllers and interfaces.
Rich User Interaction: Custom UI controllers tied to playback state and media models, supporting play/pause, seek, subtitles, and ad interactions.
Dependency Injection: Uses Dagger components and modules to inject dependencies like FSM player, controllers, monitors, and ad retrievers.
WebView VPAID Support: Integrates a WebView for interactive VPAID ads alongside ExoPlayer instances.
Dual Player Setup
Purpose
Managing two separate ExoPlayer instances allows the system to:
Play content and ads independently without interrupting each other's player lifecycle.
Optimize resource usage and buffering strategies for ads and content separately.
Support complex ad formats, including VPAID ads played via WebView while maintaining content playback state.
How It Works
Content Player Initialization
The content player (
mMoviePlayerinherited from base class) is initialized in the usual manner for video playback.Ad Player Initialization
The ad player (
adPlayer) is initialized with its own track selector and bandwidth meter:private void setupAdPlayer() { TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER_AD); trackSelector_ad = new DefaultTrackSelector(adaptiveTrackSelectionFactory); adPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector_ad); }This separate initialization ensures that ad playback can be controlled independently.
Media Source Preparation
Both content and ad media models have their media sources prepared separately via:
protected void createMediaSource(MediaModel videoMediaModel) { videoMediaModel.setMediaSource(buildMediaSource(videoMediaModel)); } @Override public void onPrepareAds(@Nullable AdMediaModel adMediaModel) { for (MediaModel singleMedia : adMediaModel.getListOfAds()) { MediaSource adMediaSource = buildMediaSource(singleMedia); singleMedia.setMediaSource(adMediaSource); } }Player Release and Resume Position Tracking
Both players track their playback positions and save resume information when released, allowing seamless resumption:
private void updateAdResumePosition() { if (adPlayer != null && playerUIController != null) { int adResumeWindow = adPlayer.getCurrentWindowIndex(); long adResumePosition = adPlayer.isCurrentWindowSeekable() ? Math.max(0, adPlayer.getCurrentPosition()) : C.TIME_UNSET; playerUIController.setAdResumeInfo(adResumeWindow, adResumePosition); } }The content player resumes similarly in
updateResumePosition().
Dependency Injection Integration
The activity leverages Dagger 2 to inject dependencies essential for playback and ad management:
Injected Components:
FsmPlayer: Manages player states and orchestrates content and ad playback.PlayerUIController: Handles UI updates and player view bindings.AdPlayingMonitor&CuePointMonitor: Observe playback progress and trigger state changes.AdRetriever&CuePointsRetriever: Fetch ad and cue point data.AdInterface: Manages communication with the ad server.PlayerAdLogicController: Coordinates ad playback logic.VpaidClient: Manages VPAID ad playback in WebView.
Injection Process:
Dependencies are injected in the
injectDependency()method, which subclasses may override to provide custom modules:protected void injectDependency() { DaggerFsmComonent.builder().playerModuleDefault(new PlayerModuleDefault()).build() .inject(this); }For example,
RealActivityoverrides this to inject a real FSM module with custom parameters:@Override protected void injectDependency() { DaggerFsmComonentReal.builder().fSMModuleReal(new FSMModuleReal(vpaidWebView, mTubiPlayerView)).build() .inject(this); }Preparation After Injection:
The
dependencyPrepare()method can be overridden for additional setup after injection.
Media Selection Interface
The `SelectionActivity` provides a simple UI for selecting media content to play using the dual player activity. It demonstrates how media models are passed to the player activity via intents:
Intent intent = new Intent(SelectionActivity.this, DoubleViewTubiPlayerActivity.class);
intent.putExtra(TubiPlayerActivity.TUBI_MEDIA_KEY, MediaModel.video(name, VIDEO_URL, artwork, null));
startActivity(intent);
This interface enables launching the dual player with different media content, showcasing the modularity and reusability of the dual player activity.
Key Workflows and Interactions
FSM Preparation and Playback Coordination
The `prepareFSM()` method ties together the FSM player and UI controllers with the dual players:
@Override
public void prepareFSM() {
playerUIController.setContentPlayer(mMoviePlayer);
if (!PlayerDeviceUtils.useSinglePlayer()) {
playerUIController.setAdPlayer(adPlayer);
}
playerUIController.setExoPlayerView(mTubiPlayerView);
playerUIController.setVpaidWebView(vpaidWebView);
fsmPlayer.setController(playerUIController);
fsmPlayer.setMovieMedia(mediaModel);
fsmPlayer.setAdRetriever(adRetriever);
fsmPlayer.setCuePointsRetriever(cuePointsRetriever);
fsmPlayer.setAdServerInterface(adInterface);
playerComponentController.setAdPlayingMonitor(adPlayingMonitor);
playerComponentController.setTubiPlaybackInterface(this);
playerComponentController.setDoublePlayerInterface(this);
playerComponentController.setCuePointMonitor(cuePointMonitor);
playerComponentController.setVpaidClient(vpaidClient);
fsmPlayer.setPlayerComponentController(playerComponentController);
fsmPlayer.setLifecycle(getLifecycle());
if (fsmPlayer.isInitialized()) {
fsmPlayer.updateSelf();
Utils.hideSystemUI(this, true);
} else {
fsmPlayer.transit(Input.INITIALIZE);
}
}
Content and ad players are assigned to the UI controller.
The FSM player is configured with media, retrievers, and the ad server interface.
The player component controller is configured with monitors and clients.
Lifecycle awareness is set for proper resource management.
FSM state initialization or resumption is triggered.
UI Interaction and Playback Events
The activity implements callbacks like `onProgress()`, `onSeek()`, `onPlayToggle()`, and `onLearnMoreClick()` to handle user interactions and playback updates, forwarding these events to monitors and the FSM as needed.
For example, cue points are displayed via a UI indicator:
@Override
public void onCuePointReceived(long[] cuePoints) {
cuePointIndictor.setText(printCuePoints(cuePoints));
}
Design Patterns and Concepts
Finite State Machine (FSM):
The FSM player (FsmPlayer) encapsulates playback states and manages transitions triggered by playback events or user input, ensuring clear and maintainable control flow.Dependency Injection:
Dagger 2 is used extensively to inject dependencies with activity scope, improving modularity and facilitating testing.Separation of Concerns:
Dual ExoPlayer instances separate content and ad playback responsibilities, allowing distinct lifecycle and buffering management.Lifecycle Awareness:
The FSM player and controllers are lifecycle-aware, reducing resource leaks and enabling smooth activity lifecycle handling.Interface-driven Design:
The activity implementsDoublePlayerInterfaceandAutoPlayto standardize behavior and enable interaction with FSM and UI controllers.
Mermaid Flowchart: Dual Player Activity Workflow
flowchart TD
Start[Activity Created] --> Inject[Inject Dependencies]
Inject --> PrepareDep[Prepare Dependencies]
PrepareDep --> InitContent[Initialize Content Player]
InitContent --> CheckDevice{Use Single Player?}
CheckDevice -->|No| InitAd[Initialize Ad Player]
CheckDevice -->|Yes| SkipAd[Skip Ad Player Init]
InitAd --> PrepareFSM[Prepare FSM Player]
SkipAd --> PrepareFSM
PrepareFSM --> LoadMedia[Load Content Media Source]
LoadMedia --> StartPlayback[Start Playback]
StartPlayback -->|Ad Cue Point Triggered| AdFetch[Fetch Ad Media Source]
AdFetch --> AdPlay[Play Ad on Ad Player]
AdPlay --> AdEnd[Ad Playback Finished]
AdEnd --> ResumeContent[Resume Content Playback]
ResumeContent --> PlaybackProgress[Monitor Playback Progress]
PlaybackProgress -->|Midroll Cue Point| AdFetch
PlaybackProgress -->|Content Ended| End[Playback Finished]
End --> Release[Release Players and Resources]
This flowchart illustrates the lifecycle of the dual player activity from creation through ad and content playback cycles until release.
Summary
The Dual Player Activity is a critical module enabling simultaneous management of content and advertisement playback through separate ExoPlayer instances. It leverages FSM-driven playback control, dependency injection, and rich UI integration to deliver a robust user experience with advanced ad insertion capabilities. The design supports modularity, extensibility, and clear separation of responsibilities, accommodating complex playback scenarios including VPAID ads.