Skip to content

DefaultPreloadManager NPE in maybeClearPreloadMediaSource when TargetPreloadStatusControl returns null #3155

@arkevo

Description

@arkevo

Description

DefaultPreloadManager.setCurrentPlayingIndex() throws a NullPointerException when TargetPreloadStatusControl.getTargetPreloadStatus() returns null for any item in the preload window.

The NPE occurs on every call to setCurrentPlayingIndex(), making the preload system non-functional despite null being a documented valid return value ("don't preload this item").

Version

  • Media3 1.9.2 (verified)
  • Also verified present in 1.10.0 source and main branch

Stack trace

java.lang.NullPointerException: Attempt to read from field 'int
  androidx.media3.exoplayer.source.preload.DefaultPreloadManager$PreloadStatus.stage'
  on a null object reference in method 'void
  androidx.media3.exoplayer.source.preload.DefaultPreloadManager.maybeClearPreloadMediaSource(
    androidx.media3.exoplayer.source.preload.PreloadMediaSource,
    androidx.media3.exoplayer.source.preload.DefaultPreloadManager$PreloadStatus)'

  at DefaultPreloadManager.maybeClearPreloadMediaSource(DefaultPreloadManager.java:589)
  at DefaultPreloadManager.preloadMediaSourceHolderInternal(DefaultPreloadManager.java:567)
  at DefaultPreloadManager.preloadMediaSourceHolderInternal(DefaultPreloadManager.java:70)
  at BasePreloadManager.maybeStartPreloadingNextSourceHolder(BasePreloadManager.java:586)
  at BasePreloadManager.invalidate(BasePreloadManager.java:209)
  at DefaultPreloadManager$SimpleRankingDataComparator.setCurrentPlayingIndex(DefaultPreloadManager.java:661)
  at DefaultPreloadManager.setCurrentPlayingIndex(DefaultPreloadManager.java:544)

Root cause

BasePreloadManager.maybeStartPreloadingNextSourceHolder() calls targetPreloadStatusControl.getTargetPreloadStatus(rankingData), which may return null per its contract. The null result is passed directly to DefaultPreloadManager.preloadMediaSourceHolderInternal(holder, targetPreloadStatus), then to maybeClearPreloadMediaSource(preloadMediaSource, targetPreloadStatus), which accesses targetPreloadStatus.stage without a null check.

Reproduction

  1. Create a DefaultPreloadManager with a TargetPreloadStatusControl that returns null for some items (e.g., the currently playing item at distance 0)
  2. Add multiple items via add()
  3. Call setCurrentPlayingIndex() — NPE every time

Suggested fix

Add a null guard in BasePreloadManager.maybeStartPreloadingNextSourceHolder():

this.targetPreloadStatusOfCurrentPreloadingSource =
    targetPreloadStatusControl.getTargetPreloadStatus(preloadingHolder.rankingData);
if (this.targetPreloadStatusOfCurrentPreloadingSource == null) {
    return false;  // skip this source
}
preloadMediaSourceHolderInternal(
    preloadingHolder, targetPreloadStatusOfCurrentPreloadingSource);

Workaround

Never return null from getTargetPreloadStatus(). Use PRELOAD_STATUS_SOURCE_PREPARED (lightest tier) for items that should not be preloaded. The preload manager handles this status correctly.

Device info

  • Pixel 7a, Android 15 (SDK 36)
  • HLS content with fMP4/CMAF segments
  • 4-tier preload strategy (specifiedRangeLoaded → TRACKS_SELECTED → SOURCE_PREPARED)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions