Skip to content

[video_player] Add adaptive bitrate streaming support#11325

Draft
sheershtehri7 wants to merge 1 commit intoflutter:mainfrom
sheershtehri7:abr-app-facing
Draft

[video_player] Add adaptive bitrate streaming support#11325
sheershtehri7 wants to merge 1 commit intoflutter:mainfrom
sheershtehri7:abr-app-facing

Conversation

@sheershtehri7
Copy link

@sheershtehri7 sheershtehri7 commented Mar 22, 2026

Adds adaptive bitrate streaming (ABR) support to the app-facing video_player package.

Changes

  • setBandwidthLimit(int maxBandwidthBps) on VideoPlayerController — Exposes manual quality control for HLS/DASH streams. Delegates to the platform interface.
  • AdaptiveBitrateManager (internal) — Automatically adjusts streaming quality based on buffering events:
    • Monitors buffering on a 3-second interval
    • Steps down quality as buffering increases: unlimited → 1080p (2.5 Mbps) → 720p (1.2 Mbps) → 480p (800 kbps) → 360p (500 kbps)
    • 0.7x decay per cycle — buffering count decays so quality recovers when conditions improve
    • 5-second cooldown between quality changes to prevent oscillation
    • Only activates for network data sources (not file/asset/contentUri)

Design

The AdaptiveBitrateManager is 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:

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

Footnotes

  1. 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

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +110 to +121
int _selectOptimalBandwidth() {
if (_bufferingCount > 5) {
return quality360p;
}
if (_bufferingCount > 2) {
return quality480p;
}
if (_bufferingCount > 0) {
return quality720p;
}
return qualityUnlimited;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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;
  }

Comment on lines +15 to +129
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;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
@stuartmorgan-g
Copy link
Collaborator

stuartmorgan-g commented Mar 22, 2026

This PR needs to start with all of the changes; see #11322 (comment).

(Marking as a draft pending both that, and an approved design.)

@stuartmorgan-g stuartmorgan-g marked this pull request as draft March 22, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[video_player] Support adaptive bitrate streaming (ABR / dynamic quality switching)

2 participants