This topic describes the design background, core concepts, built-in strategies, and usage of the AliPlayerKit Strategy System.
Background
The Strategy System was designed to address the conflict between business customization and framework evolution in large-scale AUI Kit projects.
As a low-code solution, AUI Kits must support UI and core process customization for different businesses. If business logic is embedded directly into the framework code, it creates high coupling. This can cause code conflicts, break business logic during framework upgrades, and hinder version iterations.
This type of logic is a business requirement, not a core framework feature. Without a proper mechanism to handle it, business-specific logic can creep into the framework. This threatens the system's stability and ability to evolve.
The Strategy System solves this problem:
It abstracts business-specific logic into independent, extensible strategy units.
It decouples business logic from the framework's core.
Businesses can extend the system at the strategy layer without modifying the framework.
This mechanism allows business capabilities to become a systematic and reusable foundation. It creates an iterative, reusable, and extensible base that supports the long-term evolution of AliPlayerKit.
Concepts
What is a strategy system?
The Strategy System is an architectural mechanism that unifies the management of strategy components. It is responsible for the strategy registration mechanism, lifecycle management, context injection, and event subscription. It also manages the creation, start, pause, reset, and destruction of strategies while the player is running.
Using this mechanism, developers can enable or disable strategies as needed, quickly implement custom policies, or completely replace default strategies to meet business customization needs.
What is a strategy?
A Strategy is an encapsulated unit of player business logic. It implements a specific business capability or custom logic, such as time-to-first-frame (TTFF) statistics, stuttering detection, traffic protection, or resume playback.
Core features:
Single responsibility: Focuses on an independent functional goal.
Event-driven: Responds to player status through the event bus.
Independent and isolated: Strategies do not interfere with each other.
Through a pluggable and composable design, non-core capabilities are abstracted into strategy units. This keeps the player core simple while providing flexible extension capabilities.
Features
Problems solved
The Strategy System addresses the following pain points:
Difficult maintenance: Scattered business logic is hard to maintain and reuse.
Lack of configuration: Different scenarios require different combinations of logic, but there is no flexible configuration mechanism.
Source code modification: Custom logic requires modifying the framework's source code.
Instance interference: In multi-player scenarios, business logic between instances can interfere with each other.
Core value
The Strategy System abstracts and encapsulates the player's monitoring and analysis logic. It allows customers to configure and combine strategies as needed.
Usage method | Description | Advantage |
Use default strategies | The player component uses the official default strategies. | Simplifies the integration process. It works out-of-the-box. |
Custom policy | Implement strategies for specific business requirements. | Meets various customized business needs. |
Use no strategies | Use only the playback feature. | For pure playback scenarios with no logic dependencies. |
Architectural advantages:
Decoupling: Strategies are separated from the player's core logic, creating clear responsibilities.
Reusability: Strategies can be reused across different player instances.
Extensibility: You can customize strategies without modifying the framework.
Isolation: Strategies are independent and do not interfere with one another.
Core capabilities
Capability | Description |
Event-driven | Strategies subscribe to player events through the event bus and execute responsively. |
Lifecycle management | Manages the start, stop, reset, and destruction of strategies. |
Context injection | Strategies can access player status and data through the context. |
Factory pattern | Supports global registration of strategy factories, which are automatically applied to all player instances. |
Built-in strategies
AliPlayerKit provides three out-of-the-box built-in strategies.
Time-to-first-frame (TTFF) policy (FirstFrameStrategy)
This strategy calculates the time it takes for the player to go from the PREPARING stage to the rendering of the first frame. It is divided into three phases:
Phase | Description |
Preparing | PREPARING → Prepared |
Rendering | Prepared → FirstFrameRendered |
Total time | PREPARING → FirstFrameRendered |
Scenarios: Performance monitoring, user experience optimization, and player tuning.
Stuttering detection policy (StutterDetectStrategy)
This strategy monitors stuttering during playback and collects stuttering statistics:
Metric | Description |
Stutter count | The number of times stuttering occurs. |
Stutter duration | The duration of each stuttering event. |
Total stutter duration | The total duration of stuttering for the entire playback session. |
Effective playback duration | The actual viewing time, excluding pauses and stuttering. |
Scenarios: Playback quality monitoring and user experience analysis.
Traffic Protection Policy (TrafficProtectionStrategy)
This strategy listens for network status changes and notifies users in specific scenarios:
Scenario | Behavior |
Switching from Wi-Fi to a mobile network | Notifies the user that playback is now using mobile data. |
Starting playback on a mobile network | Notifies the user that mobile data is being used. |
Scenarios: Traffic-sensitive scenarios and user-friendly notifications.
Basic usage
The Strategy System provides three ways to use strategies. Choose the one that best fits your needs:
Policy | Description | Scenario |
Use default strategies | The simplest method. The player component automatically registers default strategies. | Quick integration and standard playback scenarios. |
Customize some strategies | Register only specific strategies and do not use others. | Partial customization and enabling strategies as needed. |
Fully customize strategies | Customize all strategies to create a fully personalized monitoring system. | Deep customization and specific business requirements. |
Method 1: Use built-in strategies
This is the simplest method. The player component automatically registers the default strategies:
// 1. Create a player controller. Default strategies are registered automatically.
AliPlayerController controller = new AliPlayerController(this);
// 2. Prepare the playback data.
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
// 3. Configure the data model.
controller.configure(model);
// 4. Attach the controller to the view.
playerView.attach(controller);The default registered strategies include the following:
FirstFrameStrategy (TTFF)
StutterDetectStrategy (stuttering detection)
TrafficProtectionStrategy (traffic protection)
Method 2: Customize strategies
Register only the strategies you need and disable the rest:
// 1. Create a player controller.
AliPlayerController controller = new AliPlayerController(this);
// 2. Get the strategy manager and clear the default strategies.
StrategyManager strategyManager = controller.getStrategyManager();
// 3. Register only the required strategies.
strategyManager.register(new FirstFrameStrategy());
strategyManager.register(new StutterDetectStrategy());
// 4. Prepare the playback data.
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
controller.configure(model);
playerView.attach(controller);Method 3: Fully customize strategies
Implement custom policies to create a fully personalized monitoring system:
// 1. Create a player controller.
AliPlayerController controller = new AliPlayerController(this);
// 2. Get the strategy manager.
StrategyManager strategyManager = controller.getStrategyManager();
// 3. Register custom policies.
strategyManager.register(new MyCustomStrategy());
strategyManager.register(new MyAnalyticsStrategy());
// 4. Prepare the playback data.
AliPlayerModel model = new AliPlayerModel.Builder()
.videoSource(videoSource)
.build();
controller.configure(model);
playerView.attach(controller);Advanced usage
How to implement a custom policy
AliPlayerKit provides two implementation methods. Choose one based on your needs:
Method | Features | Scenario |
Inherit | Less code. The framework manages event subscriptions. | Most monitoring strategies. |
Implement the | High flexibility. Full autonomous control. | Requires special control logic. |
Method 1: Inherit from BaseStrategy (Recommended)
Inheriting from BaseStrategy is the simplest method because the framework has already encapsulated event subscription and lifecycle management.
Scenario: Most monitoring and analysis strategies, such as statistics, detection, and logging.
Create a strategy class.
Inherit from
BaseStrategyand implement the necessary methods:public class MyAnalyticsStrategy extends BaseStrategy { private static final String TAG = "MyAnalyticsStrategy"; @NonNull @Override public String getName() { return TAG; // Return the policy name for logging and debugging. } @Nullable @Override protected List<Class<? extends PlayerEvent>> observedEvents() { // Declare the event types to subscribe to. return Arrays.asList( PlayerEvents.StateChanged.class, PlayerEvents.Prepared.class, PlayerEvents.Info.class ); } @Override public void onEvent(@NonNull PlayerEvent event) { // Filter out events from other players. if (!isCurrentPlayer(event)) return; // Process the event. if (event instanceof PlayerEvents.StateChanged) { handleStateChanged((PlayerEvents.StateChanged) event); } else if (event instanceof PlayerEvents.Prepared) { handlePrepared((PlayerEvents.Prepared) event); } else if (event instanceof PlayerEvents.Info) { handleInfo((PlayerEvents.Info) event); } } @Override public void onReset() { super.onReset(); // Reset the internal state to prepare for a new video. } private void handleStateChanged(PlayerEvents.StateChanged event) { // Handle state changes. } private void handlePrepared(PlayerEvents.Prepared event) { // Handle preparation completion. } private void handleInfo(PlayerEvents.Info event) { // Handle playback information updates. } }Get started.
StrategyManager strategyManager = controller.getStrategyManager(); strategyManager.register(new MyAnalyticsStrategy());
Example: For more information, see strategies/FirstFrameStrategy.java.
Method 2: Implement the IStrategy interface
Directly implementing the IStrategy interface provides greater flexibility, but you need to handle event subscriptions yourself.
Scenario: You need full control over the strategy's behavior or require special initialization logic.
Create a strategy class.
Implement the
IStrategyinterface and manage event subscriptions yourself:public class MyCustomStrategy implements IStrategy, PlayerEventBus.EventListener<PlayerEvent> { private static final String TAG = "MyCustomStrategy"; private StrategyContext mContext; @NonNull @Override public String getName() { return TAG; } @Override public void onStart(@NonNull StrategyContext context) { mContext = context; // Manually subscribe to events. PlayerEventBus.getInstance().subscribe(PlayerEvents.StateChanged.class, this); } @Override public void onStop() { // Manually unsubscribe from events. PlayerEventBus.getInstance().unsubscribe(PlayerEvents.StateChanged.class, this); mContext = null; } @Override public void onReset() { // Reset the internal state. } @Override public void onEvent(@NonNull PlayerEvent event) { // Process the event. } }Register for an account.
StrategyManager strategyManager = controller.getStrategyManager(); strategyManager.register(new MyAnalyticsStrategy());
How to implement a strategy with callbacks
Strategies can use callback interfaces to notify the business layer of results:
public class MyAnalyticsStrategy extends BaseStrategy {
public interface Callback {
void onAnalysisComplete(AnalysisResult result);
}
@Nullable
private final Callback mCallback;
// Support a parameterless constructor.
public MyAnalyticsStrategy() {
this(null);
}
// Support passing a callback.
public MyAnalyticsStrategy(@Nullable Callback callback) {
mCallback = callback;
}
@Override
public void onEvent(@NonNull PlayerEvent event) {
// ... business logic
// Notify the result through the callback.
if (mCallback != null) {
mCallback.onAnalysisComplete(result);
}
}
}Usage:
strategyManager.register(new MyAnalyticsStrategy(result -> {
// Process the analysis result.
Log.d("Analytics", "Result: " + result);
}));Example: For more information, see strategies/FirstFrameStrategy.java.
How to access player status
Strategies can access player status and data through StrategyContext:
@Override
public void onEvent(@NonNull PlayerEvent event) {
if (!isCurrentPlayer(event)) return;
// Get the player ID.
String playerId = getPlayerId();
// Get the playback data model.
AliPlayerModel model = mContext.getModel();
if (model != null) {
String title = model.getVideoTitle();
VideoSource source = model.getVideoSource();
}
// Get the player state store.
IPlayerStateStore stateStore = mContext.getPlayerStateStore();
long position = stateStore.getCurrentPosition();
long duration = stateStore.getDuration();
PlayerState state = stateStore.getPlayState();
}mContext is null in the constructor. It is initialized only after onStart() is called.
How to register a global strategy
You can use StrategyRegistry to register a global strategy. It will be automatically applied to all newly created player instances:
// Register a global strategy.
StrategyRegistry.addGlobalStrategy(() -> new MyAnalyticsStrategy());
// All AliPlayerController instances created after this will automatically register this strategy.
AliPlayerController controller = new AliPlayerController(this); Restore default configurations:
StrategyRegistry.resetToDefault();Best practices
Lifecycle management
public class MyStrategy extends BaseStrategy {
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context); // Must be called.
// Initialize resources.
}
@Overridepublic void onStop() {
// Clean up resources.
super.onStop(); // Must be called.
}
@Overridepublic void onReset() {
super.onReset();
// Reset the internal state to prepare for a new video.
}
}Event filtering
In multi-player scenarios, always filter the event source:
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// Filter out events from other players to avoid interference.
if (!isCurrentPlayer(event)) return;
// Process events for the current player.
}Resource cleanup
If a strategy has asynchronous resources such as a Handler or Runnable, clean them up in onStop():
public class MyStrategy extends BaseStrategy {
private final Handler mHandler = new Handler();
private Runnable mRunnable;
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context);
mRunnable = () -> doSomething();
mHandler.postDelayed(mRunnable, 1000);
}
@Overridepublic void onStop() {
// Clean up asynchronous tasks to avoid memory leaks.
mHandler.removeCallbacks(mRunnable);
super.onStop();
}
}Notes
Scenario | Recommended practice | Reason |
Multi-player scenarios | Use | Avoid event interference. |
Long-running tasks | Cancel tasks in | Avoid memory leaks. |
Video switching | Reset the state in | Ensure the state is correct. |
Accessing Context | Obtain it through | Ensure the context is available. |
Example reference
The project provides a complete example located at playerkit-examples/example-strategy-system.
Example features
Feature | Description |
Resume playback strategy | Automatically records playback progress and resumes from that point next time. |
Strategy registration demo | Demonstrates how to register a custom policy. |
Run the example
In the demo app, select the Strategy System example to see it in action.
API reference
Core interfaces
Interface/Class | Description |
| The strategy interface. It defines lifecycle methods. |
| The base class for strategies. It encapsulates event subscription management. |
| The strategy manager. It manages the strategy lifecycle. |
| The strategy context. It provides read-only player information. |
| The service registry for strategies. It manages global strategies. |
BaseStrategy methods
Method | Description |
| Returns the policy name for logging and debugging. |
| Returns a list of event types to subscribe to. |
| The event callback. It processes subscribed events. |
| Checks if the event is from the current player. |
| Gets the current player ID. |
StrategyManager methods
Method | Description |
| Registers a strategy. |
| Unregisters a strategy. |
| Gets a strategy by name. |
| Starts all strategies. |
| Stops all strategies. |
| Resets all strategies. |
| Destroys the manager. |
StrategyContext methods
Method | Description |
| Gets the player ID. |
| Gets the playback data model. |
| Gets the player state store (read-only). |
Technical principles
Overall architecture
The Strategy System uses an event-driven and manager-scheduled architectural pattern.
Design points:
Strategies do not hold player references: Complete decoupling is achieved through an event-driven design.
Strategies only focus on events: They only need to declare what to subscribe to and how to handle it.
The manager handles scheduling: The manager controls the lifecycle and event dispatching.
Lifecycle
Strategies use a three-stage lifecycle:
Lifecycle | Trigger Condition | Description |
|
| The strategy starts, subscribes to events, and initializes resources. |
| Playback content switches | Resets the internal state to prepare for a new video. |
|
| The strategy stops, unsubscribes, and cleans up resources. |
Event-driven mechanism
Strategies subscribe to player events through an event bus, using a publish-subscribe pattern.
Event subscription flow:
The strategy declares the event types it needs to subscribe to in
observedEvents().BaseStrategyautomatically subscribes to these events inonStart().BaseStrategyautomatically unsubscribes inonStop().The strategy processes the received events in
onEvent().
Design philosophy: The event-driven mechanism completely decouples strategies from the player. Strategies do not need to hold a player reference. They only need to focus on event subscription and handling logic. This achieves a separation of concerns.
Multi-player isolation
In multi-player scenarios, the Strategy System ensures isolation through the following mechanisms:
Independent StrategyManager: Each
AliPlayerControllerhas its own independentStrategyManager.Independent StrategyContext: Each strategy context is bound to a specific player ID.
Event filtering: Strategies filter event sources using
isCurrentPlayer(event).
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// Events from each player instance carry a playerId.
// isCurrentPlayer() checks if the event is from the current player.
if (!isCurrentPlayer(event)) return;
// Process only events from the current player.
}FAQ
When does a strategy start?
All registered strategies start automatically when you call playerView.attach().
How do I get the current player status?
Use the getPlayerStateStore() method of StrategyContext:
IPlayerStateStore stateStore = mContext.getPlayerStateStore();
PlayerState state = stateStore.getPlayState();
long position = stateStore.getCurrentPosition();How do I debug a strategy?
Use LogHub to view logs. The TAG format is Policy Name.BaseStrategy or StrategyManager.
Common mistakes that cause crashes
The following are the most common issues reported by customers that lead to crashes. Avoid these mistakes.
Mistake 1: Accessing mContext in the constructor
Incorrect code:
public class MyStrategy extends BaseStrategy {
public MyStrategy() {
super();
// mContext is null in the constructor.
String playerId = getPlayerId(); // Returns an empty string.
AliPlayerModel model = mContext.getModel(); // NullPointerException!
}
}
Correct code:
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context); // ✅ Must be called first.
// mContext can be accessed after onStart.
String playerId = getPlayerId(); // Gets the ID normally.
AliPlayerModel model = mContext.getModel(); // Gets the model normally.
}
Reason for crash: mContext is assigned after onStart() is called. The strategy has not started yet when the constructor executes.
Mistake 2: Forgetting to call super.onStart() causes event subscription to fail
Incorrect code:
public class MyStrategy extends BaseStrategy {
@Overridepublic void onStart(@NonNull StrategyContext context) {
// Forgot to call super.onStart(context).
// mContext will be null, and event subscription will not execute.
initMyResources();
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// This will never be called because event subscription did not execute.
}
}
Correct code:
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context); // ✅ Must be called first.
initMyResources();
}
Reason for crash: super.onStart(context) initializes mContext and subscribes to the events returned by observedEvents(). Not calling it will result in a null mContext and invalid event subscriptions.
Mistake 3: Not filtering events causes interference in multi-player scenarios
Incorrect code:
public class MyStrategy extends BaseStrategy {
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// The event source is not filtered, which causes crosstalk in multi-player scenarios.
if (event instanceof PlayerEvents.StateChanged) {
// State changes from all players will trigger this logic.
updateUI(); // This may update the UI of the wrong player.
}
}
}
Correct code:
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// First, filter out events that are not from the current player. if (!isCurrentPlayer(event)) return;
if (event instanceof PlayerEvents.StateChanged) {
updateUI(); // Only process events from the current player.
}
}
Cause of crash: In a multi-player scenario, events from all players are dispatched to all policies. Failure to filter causes policies to process events from other players.
Mistake 4: Not cleaning up asynchronous tasks in onStop() causes a memory leak
Incorrect code:
public class MyStrategy extends BaseStrategy {
private Handler handler = new Handler();
@Overridepublic void onStart(@NonNull StrategyContext context) {
super.onStart(context);
handler.postDelayed(() -> updateProgress(), 1000); // Delayed task.
}
@Overridepublic void onStop() {
// Forgot to remove the delayed task.
super.onStop();
}
}
Correct code:
@Overridepublic void onStop() {
handler.removeCallbacksAndMessages(null); // Clear all delayed tasks
super.onStop();
}
Cause of crash: A delayed task holds a reference to a policy instance. After the policy is stopped, the instance cannot be garbage collected, which causes a memory leak. When the task executes, it may access resources that have already been destroyed.
Mistake 5: observedEvents returns null, causing event subscription to fail
Incorrect code:
public class MyStrategy extends BaseStrategy {
@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
// Returning null will not cause a crash, but the event subscription will not be executed.
return null;
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// Will never be called.
}
}
Correct code:
@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class, // Declare the events to subscribe to
PlayerEvents.Prepared.class
);
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// You can now receive events.
}
Reason for crash: BaseStrategy checks the return value of observedEvents() when subscribing to events. If the return value is `null` or an empty list, it does not subscribe to any events.
Mistake 6: Not resetting the internal state in onReset() leads to incorrect data
Incorrect code:
public class MyStrategy extends BaseStrategy {
private int mPlayCount = 0;
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
if (!isCurrentPlayer(event)) return;
if (event instanceof PlayerEvents.Prepared) {
mPlayCount++; // The count is not reset when the video is switched, so it accumulates.
Log.d(TAG, "Play count: " + mPlayCount);
}
}
@Overridepublic void onReset() {
super.onReset();
// Forgot to reset mPlayCount.
}
}
Correct code:
@Overridepublic void onReset() {
super.onReset();
mPlayCount = 0; // Reset the internal state.
}Cause of crash: onReset() is called during video switching to reset the internal state of the policy. If the state is not reset, it accumulates and affects statistical accuracy.