Skip to content

ForwardingSimpleBasePlayer throws exception due to invalid isLoading state during stop() #2133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
samaudi opened this issue Feb 12, 2025 · 3 comments
Closed
1 task done
Assignees
Labels

Comments

@samaudi
Copy link

samaudi commented Feb 12, 2025

Version

Media3 1.5.1

More version details

No response

Devices that reproduce the issue

Samsung Galaxy Note20 Ultra running Android 13
Samsung S23 Ultra running Android 14

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

  1. Integrate the library to play video streams using an ExoPlayer instance (which works correctly on its own).
  2. Replace the direct ExoPlayer usage with a ForwardingSimpleBasePlayer without any overrides.
  3. Start playback of a online streaming media item.
  4. Call the stop() method on the player.
  5. Observe the state transitions logged by ExoPlayer via Player.Listener.onEvents(), which report the following sequence:

ExoPlayer State changed. playbackState=STATE_IDLE, isLoading=true
ExoPlayer State changed. playbackState=STATE_IDLE, isLoading=true
ExoPlayer State changed. playbackState=STATE_IDLE, isLoading=false

  1. During these transitions, ForwardingSimpleBasePlayer.getState() passes the playback state and the isLoading flag directly to a SimpleBasePlayer.StateBuilder. When StateBuilder.build() is called, the constructor from SimpleBasePlayer.State() runs a check:
if (builder.playbackState == Player.STATE_IDLE || builder.playbackState == Player.STATE_ENDED) {
    checkArgument(!builder.isLoading, "isLoading only allowed when not in STATE_IDLE or STATE_ENDED");
}
  1. Because the builder receives isLoading=true while the playback state is STATE_IDLE, the check fails and an exception is thrown.

Expected result

The player should update its state without throwing an exception. In particular, when transitioning (e.g., after a stop() call on a stream), the state reported by ForwardingSimpleBasePlayer should correctly handle the isLoading flag—either by ensuring it is false when in STATE_IDLE/STATE_ENDED or by adjusting the check logic—so that no exception occurs.

Actual result

An exception is thrown with the message:
"isLoading only allowed when not in STATE_IDLE or STATE_ENDED"
This occurs because ForwardingSimpleBasePlayer.getState() transfers the playback state and isLoading flag directly to the StateBuilder, and when the underlying ExoPlayer reports STATE_IDLE with isLoading=true during a stop transition, the check in StateBuilder’s constructor fails. Consequently, the ForwardingSimpleBasePlayer becomes unusable in this scenario.

Stacktrace after Player.stop() call:
java.lang.IllegalArgumentException: isLoading only allowed when not in STATE_IDLE or STATE_ENDED
at androidx.media3.common.util.Assertions.checkArgument(Assertions.java:55)
at androidx.media3.common.SimpleBasePlayer$State.(SimpleBasePlayer.java:1027)
at androidx.media3.common.SimpleBasePlayer$State.(SimpleBasePlayer.java:101)
at androidx.media3.common.SimpleBasePlayer$State$Builder.build(SimpleBasePlayer.java:809)
at xxx.xxx.xxx.xxx.xxx.core.player.media3.video.ForwardingSimpleBasePlayer.getState(ForwardingSimpleBasePlayer.java:172)
at androidx.media3.common.SimpleBasePlayer.invalidateState(SimpleBasePlayer.java:3036)
at xxx.xxx.xxx.xxx.xxx.core.player.media3.video.ForwardingSimpleBasePlayer.access$000(ForwardingSimpleBasePlayer.java:27)
at xxx.xxx.xxx.xxx.xxx.core.player.media3.video.ForwardingSimpleBasePlayer$1.onEvents(ForwardingSimpleBasePlayer.java:89)
at androidx.media3.exoplayer.ExoPlayerImpl.lambda$new$0$androidx-media3-exoplayer-ExoPlayerImpl(ExoPlayerImpl.java:296)
at androidx.media3.exoplayer.ExoPlayerImpl$$ExternalSyntheticLambda15.invoke(D8$$SyntheticClass:0)
at androidx.media3.common.util.ListenerSet$ListenerHolder.iterationFinished(ListenerSet.java:353)
at androidx.media3.common.util.ListenerSet.handleMessage(ListenerSet.java:297)
at androidx.media3.common.util.ListenerSet.$r8$lambda$rFcF5Pkb99AL585p5-2u78YfNkY(Unknown Source:0)
at androidx.media3.common.util.ListenerSet$$ExternalSyntheticLambda0.handleMessage(D8$$SyntheticClass:0)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8762)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

Media

Not applicable

Bug Report

  • You will email the zip file produced by adb bugreport to [email protected] after filing this issue.
@tianyif
Copy link
Contributor

tianyif commented Feb 12, 2025

Hi @samaudi,

Thanks for reporting! When the stop() is called, ExoPlayer will immediately update the state to IDLE on the application thread and propagate it to the ForwardingSimpleBasePlayer:

playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
if (error != null) {
playbackInfo = playbackInfo.copyWithPlaybackError(error);
}
pendingOperationAcks++;
internalPlayer.stop();
updatePlaybackInfo(
playbackInfo,
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ C.TIME_UNSET,
/* ignored */ C.INDEX_UNSET,
/* repeatCurrentMediaItem= */ false);

However, the isLoading is updated asynchronously on the playback thread, and it is possible that the update comes late.

We will be providing a solution for this!

@samaudi
Copy link
Author

samaudi commented Feb 12, 2025

Hi @tianyif,

I have now implemented it in ForwardingSimpleBasePlayer.geState() as follows, and it seems to work so far:

int playbackState = player.getPlaybackState();
if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
    state.setIsLoading(false);
} else {
    state.setIsLoading(player.isLoading());
}

However, I can't quite see what side effects this solution has.

copybara-service bot pushed a commit that referenced this issue Feb 19, 2025
In some cases, the ExoPlayer immediately transitions to `STATE_IDLE` or `STATE_ENDED` on application thread, while `isLoading` can still remain as `true` before it is finally updated from playback thread.

Issue: #2133

#cherrypick

PiperOrigin-RevId: 728724157
oceanjules pushed a commit that referenced this issue Mar 3, 2025
In some cases, the ExoPlayer immediately transitions to `STATE_IDLE` or `STATE_ENDED` on application thread, while `isLoading` can still remain as `true` before it is finally updated from playback thread.

Issue: #2133

#cherrypick

PiperOrigin-RevId: 728724157
(cherry picked from commit daf8f9f)
@tianyif
Copy link
Contributor

tianyif commented Mar 21, 2025

Fix is available in the latest 1.6.0 prerelease, and will be 1.6.0 stable soon.

@tianyif tianyif closed this as completed Mar 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants