[video_player] Add adaptive bitrate streaming support#11325
[video_player] Add adaptive bitrate streaming support#11325sheershtehri7 wants to merge 1 commit intoflutter:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces adaptive bitrate (ABR) streaming support. It adds a new internal AdaptiveBitrateManager class that automatically adjusts video quality by monitoring buffering events and setting bandwidth limits. A new public method, setBandwidthLimit, is also added to VideoPlayerController for manual control over the maximum bandwidth. The changes are applied only for network data sources. My review includes a suggestion to refine the ABR logic to be more gradual and to add unit tests for the new manager class to ensure its correctness.
| int _selectOptimalBandwidth() { | ||
| if (_bufferingCount > 5) { | ||
| return quality360p; | ||
| } | ||
| if (_bufferingCount > 2) { | ||
| return quality480p; | ||
| } | ||
| if (_bufferingCount > 0) { | ||
| return quality720p; | ||
| } | ||
| return qualityUnlimited; | ||
| } |
There was a problem hiding this comment.
The _selectOptimalBandwidth method doesn't use the quality1080p constant. This causes the adaptive bitrate logic to step down from unlimited quality directly to 720p after a single buffering event, which is more aggressive than the gradual step-down described in the PR description ("unlimited → 1080p → 720p ...") and may lead to a suboptimal user experience.
I suggest adjusting the thresholds to include a step for 1080p to provide a smoother quality transition.
int _selectOptimalBandwidth() {
if (_bufferingCount > 5) {
return quality360p;
}
if (_bufferingCount > 3) {
return quality480p;
}
if (_bufferingCount > 1) {
return quality720p;
}
if (_bufferingCount > 0) {
return quality1080p;
}
return qualityUnlimited;
}| class AdaptiveBitrateManager { | ||
| /// Creates an [AdaptiveBitrateManager] for the given [playerId]. | ||
| AdaptiveBitrateManager({ | ||
| required this.playerId, | ||
| required VideoPlayerPlatform platform, | ||
| }) : _platform = platform; | ||
|
|
||
| /// The player ID this manager controls. | ||
| final int playerId; | ||
| final VideoPlayerPlatform _platform; | ||
|
|
||
| Timer? _monitoringTimer; | ||
| int _bufferingCount = 0; | ||
| int _currentBandwidthLimit = qualityUnlimited; | ||
| DateTime _lastQualityChange = DateTime.now(); | ||
| bool _isMonitoring = false; | ||
|
|
||
| /// Bandwidth cap for 360p quality (~500 kbps). | ||
| static const int quality360p = 500000; | ||
|
|
||
| /// Bandwidth cap for 480p quality (~800 kbps). | ||
| static const int quality480p = 800000; | ||
|
|
||
| /// Bandwidth cap for 720p quality (~1.2 Mbps). | ||
| static const int quality720p = 1200000; | ||
|
|
||
| /// Bandwidth cap for 1080p quality (~2.5 Mbps). | ||
| static const int quality1080p = 2500000; | ||
|
|
||
| /// No bandwidth limit — lets the native player choose freely. | ||
| static const int qualityUnlimited = 0; | ||
|
|
||
| static const Duration _monitorInterval = Duration(seconds: 3); | ||
| static const Duration _qualityChangeCooldown = Duration(seconds: 5); | ||
|
|
||
| /// Buffering count decay factor applied each monitoring cycle. | ||
| /// | ||
| /// This allows recovery to higher quality after transient buffering. | ||
| static const double _bufferingDecayFactor = 0.7; | ||
|
|
||
| /// Starts automatic quality monitoring and adjustment. | ||
| /// | ||
| /// Removes any existing bandwidth limit and begins periodic analysis. | ||
| /// Safe to call multiple times — subsequent calls are no-ops. | ||
| Future<void> startAutoAdaptiveQuality() async { | ||
| if (_isMonitoring) { | ||
| return; | ||
| } | ||
| _isMonitoring = true; | ||
|
|
||
| try { | ||
| await _platform.setBandwidthLimit(playerId, qualityUnlimited); | ||
| } catch (e) { | ||
| _isMonitoring = false; | ||
| return; | ||
| } | ||
|
|
||
| _monitoringTimer = Timer.periodic(_monitorInterval, (_) { | ||
| _analyzeAndAdjust(); | ||
| }); | ||
| } | ||
|
|
||
| /// Records a buffering event from the player. | ||
| /// | ||
| /// Called by [VideoPlayerController] when a [bufferingStart] event occurs. | ||
| void recordBufferingEvent() { | ||
| if (_isMonitoring) { | ||
| _bufferingCount++; | ||
| } | ||
| } | ||
|
|
||
| /// Analyzes recent buffering history and adjusts the bandwidth limit. | ||
| Future<void> _analyzeAndAdjust() async { | ||
| if (DateTime.now().difference(_lastQualityChange) < | ||
| _qualityChangeCooldown) { | ||
| return; | ||
| } | ||
|
|
||
| final int newLimit = _selectOptimalBandwidth(); | ||
|
|
||
| // Apply decay so transient buffering doesn't permanently pin quality low. | ||
| _bufferingCount = (_bufferingCount * _bufferingDecayFactor).floor(); | ||
|
|
||
| if (newLimit != _currentBandwidthLimit) { | ||
| try { | ||
| await _platform.setBandwidthLimit(playerId, newLimit); | ||
| _currentBandwidthLimit = newLimit; | ||
| _lastQualityChange = DateTime.now(); | ||
| } catch (e) { | ||
| // Silently ignore errors during auto-adjustment. | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Selects optimal bandwidth based on recent buffering frequency. | ||
| int _selectOptimalBandwidth() { | ||
| if (_bufferingCount > 5) { | ||
| return quality360p; | ||
| } | ||
| if (_bufferingCount > 2) { | ||
| return quality480p; | ||
| } | ||
| if (_bufferingCount > 0) { | ||
| return quality720p; | ||
| } | ||
| return qualityUnlimited; | ||
| } | ||
|
|
||
| /// Stops monitoring and releases resources. | ||
| void dispose() { | ||
| _isMonitoring = false; | ||
| _monitoringTimer?.cancel(); | ||
| _monitoringTimer = null; | ||
| } | ||
| } |
There was a problem hiding this comment.
The new AdaptiveBitrateManager class contains significant logic for automatic quality adjustment, including state management, timers, and quality selection based on buffering events. However, there are no unit tests for this class. Adding unit tests would help ensure the correctness of this complex logic and prevent future regressions.
You could use the fake_async package to test the timer-based behavior and mock VideoPlayerPlatform to verify interactions.
Adds setBandwidthLimit() to VideoPlayerController and an internal AdaptiveBitrateManager that automatically adjusts quality based on buffering events during HLS/DASH playback. The AdaptiveBitrateManager: - Monitors buffering events on a 3-second interval - Steps down quality (1080p -> 720p -> 480p -> 360p) as buffering increases - Recovers quality when buffering subsides (0.7x decay per cycle) - Enforces a 5-second cooldown between quality changes - Only activates for network data sources Requires video_player_platform_interface ^6.7.0, video_player_android ^2.10.0, video_player_avfoundation ^2.10.0. Fixes flutter/flutter#183941
b25faa4 to
2db2f5f
Compare
|
This PR needs to start with all of the changes; see #11322 (comment). (Marking as a draft pending both that, and an approved design.) |
Adds adaptive bitrate streaming (ABR) support to the app-facing
video_playerpackage.Changes
setBandwidthLimit(int maxBandwidthBps)onVideoPlayerController— Exposes manual quality control for HLS/DASH streams. Delegates to the platform interface.AdaptiveBitrateManager(internal) — Automatically adjusts streaming quality based on buffering events:Design
The
AdaptiveBitrateManageris a supervisory layer on top of the native player's own ABR. Native ABR (ExoPlayer/AVFoundation) handles buffer and bandwidth estimation at the segment level. The Dart-level manager provides a coarser, user-experience-oriented response to sustained buffering by capping the maximum selectable bitrate. This two-tier approach ensures both fine-grained adaptation and protection against persistent poor conditions.Design doc: https://docs.google.com/document/d/1g3RSytVLgWtheZO1ZFKvSfpFjh2_GstLv4SIwJMmYdw/edit?usp=sharing
Versioned as 2.12.0 — minor version bump. Requires
video_player_platform_interface ^6.7.0,video_player_android ^2.10.0,video_player_avfoundation ^2.10.0.Depends on all three preceding PRs:
video_player_platform_interface)video_player_android)video_player_avfoundation)Fixes flutter/flutter#183941
AI Disclosure: This PR was developed with assistance from AI tools (GitHub Copilot / Claude). All code has been reviewed, tested, and validated by the author.
Pre-Review Checklist
[shared_preferences]///).Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2