The strategy system is a core architectural design of AliPlayerKit. It uses an event-driven mechanism to encapsulate player monitoring, analysis, and optimization logic into independent strategy components. This approach decouples the player's business logic, making it reusable and easy to extend.
Built-in strategies
The system includes the following recommended strategies out of the box:
Strategy name | Class name | Description | Optional callbacks |
First frame statistics |
| Calculates the time taken from |
|
Stutter detection |
| Monitors playback and loading states to calculate the number of stutters, total stutter duration, and stutter rate. |
|
Built-in strategies require no configuration and are enabled automatically after the player is created. To retrieve data from a strategy, register a new instance of it with the desired callbacks.
Usage
Using default strategies
Built-in strategies run automatically without any configuration:
final data = AliPlayerWidgetData(
videoSource: videoSource,
);
controller.configure(data);Replacing a built-in strategy
To retrieve runtime data, replace a default built-in strategy by registering a new instance with the same name using the register() method. This allows you to provide callbacks:
controller.strategyManager
..register(FirstFrameStrategy(
onPrepared: (cost) {
print('Time to prepare: ${cost.inMilliseconds}ms');
},
onFirstFrame: (cost) {
print('Time to first frame: ${cost.inMilliseconds}ms');
},
))
..register(StutterDetectStrategy(
onStutterDetected: (info) {
print('Stutters: ${info.stutterCount}, Stutter Rate: ${(info.stutterRate * 100).toStringAsFixed(1)}%');
},
));Registering a strategy with an existing name automatically replaces the previous instance. You do not need to remove it manually.
Registering a custom strategy
Register a custom strategy instance with a unique name using register() to extend the player's behavior:
controller.strategyManager.register(ResumePlayStrategy(
onPositionSaved: (position) {
print('Position saved: ${position.inSeconds}s');
},
));To learn how to develop a custom strategy, see the Custom strategy development section below.
Managing strategies
Use strategyManager to remove strategies at runtime:
// Remove a strategy by name
controller.strategyManager.unregister('MyCustomStrategy');
// Remove all strategies
controller.strategyManager.clear();Custom strategy development
Extending BasePlayerStrategy
Create a custom strategy by extending BasePlayerStrategy. The framework automatically manages the listener lifecycle:
class MyCustomStrategy extends BasePlayerStrategy {
@override
String get name => 'MyCustomStrategy';
@override
void onStart(AliPlayerWidgetController controller) {
// Subscribe to notifiers with listen(). The framework automatically cleans them up in onStop.
listen<int>(controller.playStateNotifier, (state) {
// Handle playback state changes
});
listen<Duration>(controller.currentPositionNotifier, (position) {
// Handle playback position changes
});
}
@override
void onReset() {
// Reset internal state when the video source changes (the strategy remains active)
}
}Lifecycle methods
Method | Trigger | Responsibility |
| When the strategy starts. | Subscribe to state signals and initialize internal state. |
| When the strategy stops (for example, when the controller is destroyed). | Release resources. |
| When the video source changes. | Reset internal counters or timestamps. The strategy remains active. |
Callback safety mechanism
Strategies typically use callbacks to report data externally, for example, to notify the UI of updates. When a strategy is stopped, the associated UI might have already been disposed. To prevent potential issues, the framework provides two layers of protection:
Mechanism | Scope | Description |
| Notifier-driven callbacks | After the strategy stops, the listener no longer triggers |
| Active callback in | Safely triggers external callbacks and automatically skips them if the strategy has stopped. |
class ResumePlayStrategy extends BasePlayerStrategy {
final void Function(Duration)? onPositionSaved;
@override
void onStop() {
super.onStop();
_savePosition();
}
void _savePosition() {
// Persistence always runs (for data safety)
storage.save(position);
// Safely trigger the external callback with notify() (skipped if stopped)
notify(onPositionSaved, position);
}
}Design Principle: Persistence (for data safety) and notification (for UI updates) are separate concerns. You can perform persistence operations in onStop(), but external notifications must be guarded by the notify() method.
Data sources and state signals
A strategy is essentially the basic organizational unit of business logic. Because it involves business logic, it inevitably involves data acquisition and processing. A strategy obtains a complete reference to the controller in onStart and can obtain the required data through various methods.
Observable state signals
Strategies subscribe to ValueNotifier objects on the controller by using the listen() method:
Notifier | Type | Description |
|
| Event for the |
|
| The data source is prepared. |
|
| The first frame is rendered. |
|
| Playback state change. |
|
| Current playback position. |
|
| Total video duration. |
|
| Loading state. |
Design Principle: A strategy observes the player's state but does not directly control its behavior.
Other data sources
Strategies can also get data from any public API on the controller:
Source | Description | Example |
ValueNotifier listener | Reactive, push-based approach suitable for logic driven by state changes. |
|
Configuration data | Read configuration information, such as the data source or scene type. |
|
Active query | Call asynchronous methods on the controller to get real-time data. |
|
Constructor injection | Pass external dependencies through the strategy's constructor parameters. |
|
If the data you need does not have a corresponding Notifier, you can:
class MyCustomStrategy extends BasePlayerStrategy {
late AliPlayerWidgetController _controller;
@override
String get name => 'MyCustomStrategy';
@override
void onStart(AliPlayerWidgetController controller) {
_controller = controller;
// Method 1: Use an existing Notifier to trigger an active query in the callback.
listen<int>(controller.playStateNotifier, (state) {
// When the playback state changes, actively query the current playback position.
controller.getCurrentPosition().then((pos) {
// ...handle business logic
});
});
// Method 2: Read configuration information directly.
final videoSource = controller.widgetData?.videoSource;
// ...execute different logic based on the video source type.
}
}Notifiers are the preferred data source because they are reactive and automatically cleaned up. However, strategies are not limited to them. Any public API on the controller is a valid way to retrieve data.
How the strategy system works
The strategy system operates through the following mechanisms:
Automatic registration: The
StrategyManagerregisters recommended built-in strategies by default upon creation.Lifecycle binding: Strategies are started with the controller's
configure()method and destroyed with thedestroy()method.Exception isolation: An exception in one strategy does not affect the operation of other strategies.
Callback safety: The framework uses an
isActiveflag to ensure that external callbacks are not triggered after a strategy has stopped.
Practical example
See example/lib/pages/strategy/strategy_demo_page.dart for a complete example of using the strategy system, including a full implementation of a custom strategy for resuming playback.
The strategy system lets you encapsulate business concerns into independent strategies, which you can combine flexibly for different scenarios.