Skip to content

[BUG][Android]: NullPointerException in ReactExoplayerView.videoLoaded() — player.isPlayingAd() called without null-check #4902

@Osilos

Description

@Osilos

Version

6.19.2 (also affects 6.19.1; same code on support/6.x.x)

What platforms are you having the problem on?

Android

System Version

Reproduced on Android 13 → Android 16. Most reports are Samsung devices but the bug is platform-wide; observed across 260+ unique users in production.

On what device are you experiencing the issue?

Real device (Samsung SM-S936B / Galaxy S24 among many others)

Architecture

Old architecture

What happened?

Fatal NullPointerException thrown from the main looper:

NullPointerException: Attempt to invoke interface method
'boolean androidx.media3.exoplayer.ExoPlayer.isPlayingAd()' on a null object reference
  at com.brentvatne.exoplayer.ReactExoplayerView.videoLoaded(ReactExoplayerView.java:1486)
  at com.brentvatne.exoplayer.ReactExoplayerView.onEvents(ReactExoplayerView.java:1443)
  at androidx.media3.exoplayer.ExoPlayerImpl.lambda$new$0(ExoPlayerImpl.java:301)
  at androidx.media3.common.util.ListenerSet$ListenerHolder.iterationFinished(ListenerSet.java:353)
  at androidx.media3.common.util.ListenerSet.handleMessage(ListenerSet.java:297)
  at android.os.Handler.dispatchMessage(Handler.java:106)
  at android.os.Looper.loopOnce(...)

Root cause

videoLoaded() (line 1486 on support/6.x.x) calls player.isPlayingAd() directly on the field, with no null check:

// android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
private void videoLoaded() {
    if (!player.isPlayingAd() && loadVideoStarted) {   // ← NPE here
        ...
    }
}

This is a race condition between ListenerSet dispatch and the player being released:

  1. ExoPlayer's ListenerSet posts a Player.Listener.onEvents callback on the main looper.
  2. Before the message is dispatched, the React view is unmounted → cleanUpResources() / releasePlayer() runs and nulls player.
  3. The pending message fires → onPlaybackStateChanged(STATE_READY)videoLoaded()player.isPlayingAd() on a null reference.

This happens in feed-style UIs (vertical/horizontal lists of videos remounted on scroll, or apps that key-remount <Video> on source change) where the unmount window overlaps with STATE_READY events.

Already-existing helper that should be used

A private null-safe wrapper already exists in the same file (support/6.x.x line 345):

private boolean isPlayingAd() {
    return player != null && player.isPlayingAd();
}

…and is used elsewhere in the file (e.g. line 285). The call site at line 1486 was simply missed.

Suggested fix

Replace the raw field access at line 1486 with the existing helper:

 private void videoLoaded() {
-    if (!player.isPlayingAd() && loadVideoStarted) {
+    if (player == null) return;
+    if (!isPlayingAd() && loadVideoStarted) {

(The early player == null guard also protects the player.getVideoFormat() call a few lines below, which would NPE under the same race.)

Impact

In our production app: 392 crashes / 262 unique users over 6 months on a feed of looping background videos that key-remount on source change. Crash rate scales with how aggressively the app destroys/creates Video views.

Reproducer

Hard to nail down a deterministic repro because of the race, but it is easy to surface in a FlatList of <Video> items where the key prop changes on scroll (forces native release+create). The Sentry replays we have show the crash consistently happening right at the moment a video tile is swapped out.

Expected Behavior

videoLoaded() should be a no-op (or use the existing null-safe isPlayingAd() helper) when the player has already been released.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    To Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions