Skip to content

Scrubbing issue with HLS content #2598

Closed

Description

@StaehliJ

Version

Media3 pre-release (alpha, beta or RC not in this list)

More version details

1.8.0-rc01

Devices that reproduce the issue

  • Pixel 2 XL
  • Samsung A42,
  • Others

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Reproduction steps

I create a test inside my fork of media3 because it needs some changes into the compose demo. The idea is to implement the new scrubbing feature of ExoPlayer and try using key-frame if they are available.

I added a Slider to seek when slider value changes and it activate scrubbingMode the first time. When slider finishing we disable scrubbingMode. The player is also configured with SeekParameters.CLOSEST_SYNC when scrubbing.

Before this feature we also implemented something similar waiting seek end before seek to the next position to fast seek in the content. It was working fine until this version of the player. The player error is exactly the same.

Expected result

The HLS content seeking fast while scrubbing and when scrubbing ends, the playback continues.

Actual result

The playback stop with a player error when we stop scrubbing, some times is doesn't have error, but most of the time it has.

15:14:38.032 E playerFailed [eventTime=2647.10, mediaPos=342.00, window=1, period=1, errorCode=ERROR_CODE_UNSPECIFIED (Ask Gemini)
androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error
at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:910)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.getLastMediaChunk(HlsSampleStreamWrapper.java:1544)
at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.getNextLoadPositionUs(HlsSampleStreamWrapper.java:759)
at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.seekInsideBufferUs(HlsSampleStreamWrapper.java:1567)
at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.seekToUs(HlsSampleStreamWrapper.java:519)
at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.selectTracks(HlsSampleStreamWrapper.java:464)
at androidx.media3.exoplayer.hls.HlsMediaPeriod.selectTracks(HlsMediaPeriod.java:326)
at androidx.media3.exoplayer.source.MaskingMediaPeriod.selectTracks(MaskingMediaPeriod.java:189)
at androidx.media3.exoplayer.MediaPeriodHolder.applyTrackSelection(MediaPeriodHolder.java:339)
at androidx.media3.exoplayer.ExoPlayerImplInternal.reselectTracksInternal(ExoPlayerImplInternal.java:2235)
at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:742)
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:223) 
at android.os.HandlerThread.run(HandlerThread.java:67) 
]

The issue happens here, I suspect an race condition issue because it should not have an index=-1 when the list has items.

Media

The HLS media provide in videos.kt

Bug Report

Activity

  1. icbaker commented on Jul 10, 2025

    @icbaker
    Collaborator

    Before this feature we also implemented something similar waiting seek end before seek to the next position to fast seek in the content. It was working fine until this version of the player. The player error is exactly the same.

    Does this mean you see the same error both when using ExoPlayer.setScrubbingModeEnabled and when using your own hand-rolled scrubbing mode? i.e. it suggests that the change you're seeing is in the HLS stack, rather than being directly related to the new scrubbing mode?

  2. StaehliJ commented on Jul 10, 2025

    @StaehliJ
    Author

    Totally I didn't go through all changes introduced, but I assume some changes have been done somewhere when player is changing track selection and have seek operation or something like that related.

    In my previous implementation, we disable audio and metadata tracks via the track selector and restore them when user stop interacting with the slider. Of course we paused the player and setup the SeekParameters.

    The issue is not only related to only scrubbing mode indeed.

  3. self-assigned this
    on Jul 11, 2025
  4. tianyif commented on Jul 11, 2025

    @tianyif
    Contributor

    @StaehliJ,

    I can reproduce this issue on demo app with passing true to the PlayerControlView.setTimeBarScrubbingEnabled

    public void setTimeBarScrubbingEnabled(boolean timeBarScrubbingEnabled) {
    this.timeBarScrubbingEnabled = timeBarScrubbingEnabled;
    }

    which enables the scrubbingMode when I drags the scrubber.

    The scrubbingMode just makes the issue easily reproducible though, the problem is in the HlsSampleStreamWrapper itself. When scrubbingMode enabled, the audio disabled, so the HlsSampleStreamWrapper for audio clears the mediaChunks:

    if (enabledTrackGroupCount == 0) {
    chunkSource.reset();
    downstreamTrackFormat = null;
    pendingResetUpstreamFormats = true;
    mediaChunks.clear();
    if (loader.isLoading()) {
    if (sampleQueuesBuilt) {
    // Discard as much as we can synchronously.
    for (SampleQueue sampleQueue : sampleQueues) {
    sampleQueue.discardToEnd();
    }
    }
    loader.cancelLoading();
    } else {
    resetSampleQueues();
    }
    } else {

    But when audio is restored after scrubbing, its HlsSampleStreamWrapper attempts to find the next load position among from the last loaded chunk, but the mediaChunks has been emptied (else condition, where we already passed a null chunk due to no chunks available).

    private boolean seekInsideBufferUs(long positionUs, @Nullable HlsMediaChunk chunk) {
    int sampleQueueCount = sampleQueues.length;
    for (int i = 0; i < sampleQueueCount; i++) {
    SampleQueue sampleQueue = sampleQueues[i];
    boolean seekInsideQueue;
    if (chunk != null) {
    seekInsideQueue = sampleQueue.seekTo(chunk.getFirstSampleIndex(i));
    } else {
    long nextLoadPositionUs = getNextLoadPositionUs();
    boolean allowTimeBeyondBuffer =
    nextLoadPositionUs == C.TIME_END_OF_SOURCE || positionUs < nextLoadPositionUs;
    seekInsideQueue = sampleQueue.seekTo(positionUs, allowTimeBeyondBuffer);

    We will be providing a fix for it.

  5. locked and limited conversation to collaborators on Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions