Skip to content

ArrayIndexOutOfBoundsException in PlaylistTimeline.getFirstWindowIndexByChildIndex during player release after pre-roll ad completes #3142

@anshupatel25

Description

@anshupatel25

When using MediaItem with AdsConfiguration and ImaAdsLoader registered via
DefaultMediaSourceFactory.setLocalAdInsertionComponents() inside a MediaLibraryService,
releasing the player after a pre-roll ad finishes and content begins playing causes a crash:

java.lang.ArrayIndexOutOfBoundsException: length=1; index=-1
at androidx.media3.exoplayer.PlaylistTimeline.getFirstWindowIndexByChildIndex(PlaylistTimeline.java:104)
at androidx.media3.exoplayer.AbstractConcatenatedTimeline.getPeriod(AbstractConcatenatedTimeline.java:230)
at androidx.media3.common.Timeline.getPeriod(Timeline.java:1332)

## Reproduction

The crash is state-dependent:

Scenario Result
No ad configured ✅ No crash
Ad currently playing when release() is called ✅ No crash
Ad failed / timed out, then release() called ❌ Crash
Ad finished, content playing, then release() called ❌ Crash
// ImaAdsLoader is created lazily and setPlayer() called inside the lambda                                                                                                                                                             
var imaAdsLoader: ImaAdsLoader? = null                                                                                                                                                                                                 
                                                                                                                                                                                                                                       
fun getClientSideAdsLoader(): AdsLoader? {                                                                                                                                                                                             
    if (imaAdsLoader == null) {                                                                                                                                                                                                        
        imaAdsLoader = ImaAdsLoader.Builder(context).build()
    }                                                                                                                                                                                                                                  
    imaAdsLoader?.setPlayer(player)
    return imaAdsLoader                                                                                                                                                                                                                
}                                                         

// Factory wires ImaAdsLoader via setLocalAdInsertionComponents                                                                                                                                                                        
val mediaSourceFactory = DefaultMediaSourceFactory(context)
    .setLocalAdInsertionComponents(                                                                                                                                                                                                    
        { _: MediaItem.AdsConfiguration? -> getClientSideAdsLoader() },
        playerView                                                                                                                                                                                                                     
    )                                                                                                                                                                                                                                  
 
// ExoPlayer built with the factory                                                                                                                                                                                                    
val player = ExoPlayer.Builder(context)                   
    .setMediaSourceFactory(mediaSourceFactory)
    .build()
                                                                                                                                                                                                                                       
// MediaItem carries the ad tag via AdsConfiguration
val mediaItem = MediaItem.Builder()                                                                                                                                                                                                    
    .setUri(contentUri)                                   
    .setAdsConfiguration(                                                                                                                                                                                                              
        MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUrl)).build()
    )                                                                                                                                                                                                                                  
    .build()                                              
                                                                                                                                                                                                                                       
player.setMediaItem(mediaItem)
player.prepare()                                                                                                                                                                                                                       
player.play()                                             

// After pre-roll finishes and content is playing, press back:                                                                                                                                                                         
imaAdsLoader?.release()
player.release() // ← crash here                                                                                                                                                                                                       

Root Cause

During ExoPlayer.release(), the player walks the timeline to finalize state.
AbstractConcatenatedTimeline.getPeriod(periodIndex) calls
getChildIndexByPeriodIndex(periodIndex), which returns -1 when the period index
is unresolved — left over from the ads → content transition in the MediaPeriodQueue.
That -1 is then passed directly into PlaylistTimeline.getFirstWindowIndexByChildIndex(-1),
producing the ArrayIndexOutOfBoundsException.

This happens because after a pre-roll completes and ExoPlayer transitions to the content
period, the internal currentPeriodIndex can be left in an unresolved state. When
release() is called at that moment the timeline walk hits this invalid index.

Relation to #3125

This crash is not fixed by the patch in #3125 (included in 1.10.0-rc03). That commit
added bounds checks in AdsMediaSource.java and ExoPlayerImplInternal.java for
durationsUs and states arrays. The crash here originates in PlaylistTimeline.java
and AbstractConcatenatedTimeline.java, neither of which were modified by #3125.

Conclusion

We have been tracking this crash across multiple Media3 versions and it remains
reproducible on 1.10.0-rc03. We initially assumed #3125 would resolve it since the
stack trace looks similar, but after analyzing the commit diff it is clear the affected
classes are different.

Happy to provide a test additional logs if that would help move this forward.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions