flutter / flutter/flutter

[video_player] `_playerWith` throws unrecoverable StateError when native player is destroyed by OS during CONFIGURATION_CHANGED / LOW_MEMORY

Open Beginner friendly
#190,739 8 comments 0 reactions 0 assignees View on GitHub
p: video_player P2 package team-ecosystem triaged-ecosystem
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

### Steps to reproduce

1. Open a video player in fullscreen (landscape)
2. Background the app (lock screen / home button)
3. Wait for Android to trigger LOW_MEMORY (~40%) or iOS to reclaim AVPlayer resources
4. Return to app → CONFIGURATION_CHANGED fires (orientation change)
5. `_VideoPlayerState.build()` → `buildViewWithOptions(playerId)` → `_playerWith(id)` throws `StateError`

### Expected results

`buildViewWithOptions` should handle missing players gracefully (return empty widget) instead of throwing.

### Actual results

```
StateError: Bad state: No active player with ID 4.
```

**iOS stack trace:**
```
avfoundation_video_player.dart:327 in AVFoundationVideoPlayer._playerWith
avfoundation_video_player.dart:304 in AVFoundationVideoPlayer.buildViewWithOptions
video_player.dart:1143 in _VideoPlayerState.build
```

**Android stack trace:**
```
android_video_player.dart:298 in AndroidVideoPlayer._playerWith
```

### Impact

- **3,400+ events** (2.2K iOS + 1.2K Android) from **~500 users** in 30 days
- Devices under memory pressure: OPPO Reno3 (MediaTek), Samsung A13, iPad Air 5th gen
- Level: `Error` — app doesn't crash but video widget becomes unrecoverable

### Root Cause

`_playerWith` throws when player ID is not in `_players` map:

```dart
// android_video_player.dart:296-298 / avfoundation_video_player.dart:325-327
_PlayerInstance _playerWith({required int id}) {
final _PlayerInstance? player = _players[id];
return player ?? (throw StateError('No active player with ID $id.'));
}
```

The race: `dispose()` removes from `_players`, but the `VideoPlayer` widget is still mounted (AnimatedSwitcher transition / rebuild during CONFIGURATION_CHANGED). `build()` calls `buildViewWithOptions` → throws.

### Proposed Fix

Guard `buildViewWithOptions` to return empty widget when player is gone:

**android_video_player.dart:**
```dart
@override
Widget buildViewWithOptions(VideoViewOptions options) {
final int playerId = options.playerId;
final _PlayerInstance? player = _players[playerId];
if (player == null) return const SizedBox.shrink();
return _buildExoPlayerView(playerId);
}
```

**avfoundation_video_player.dart:**
```dart
@override
Widget buildViewWithOptions(VideoViewOptions options) {
final int playerId = options.playerId;
final _PlayerInstance? player = _players[playerId];
if (player == null) return const SizedBox.shrink();
final VideoPlayerViewState viewState = player.viewState;
return switch (viewState) {
VideoPlayerTextureViewState(:final int textureId) => Texture(textureId: textureId),
VideoPlayerPlatformViewState() => _buildPlatformView(playerId),
};
}
```

### Environment

```
Flutter 3.44.0-3.44.6 (stable), Dart 3.12.0-3.12.2
video_player: 2.13.0, video_player_android: 2.11.0, video_player_avfoundation: 2.11.0
```

Tested on: OPPO Reno3 (Android 12, MediaTek MT6779), iPad Air 5th gen (iOS 26.6), iPhone 13 (iOS 26.5.2)

Contributor guide

Open the contributing guide

Research direction

Start with buildViewWithOptions in android_video_player.dart and avfoundation_video_player.dart, alongside the _playerWith implementations and the reported _VideoPlayerState.build call path. Verify the missing-player case during rebuilds after disposal or configuration changes. Done means both platforms return an empty widget instead of throwing when the player ID is absent.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart
Domain
mobile
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.