Skip to content

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

Description

@samehzidan-efham

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:

// 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:

@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:

@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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    p: video_playerThe Video Player pluginpackageflutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions