google/ExoPlayer · critical · IllegalStateException
Playback stuck buffering and not loading
Error message
Playback stuck buffering and not loading
What it means
ExoPlayerImplInternal's periodic doSomeWork pass monitors the internal playback thread. If playback is in STATE_BUFFERING, renderers cannot make progress, nothing new is being loaded (totalBufferedDurationUs not advancing), and this persists continuously for PLAYBACK_STUCK_AFTER_MS, the internal thread concludes the pipeline is deadlocked and throws an IllegalStateException('Playback stuck buffering and not loading'). The DRM-key-wait case and normal rebuffer are explicitly excluded, so this fires only when neither network loading nor rendering is making any progress.
Source
Thrown at library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java:1128
if (!playbackInfo.isLoading
&& playbackInfo.totalBufferedDurationUs < PLAYBACK_BUFFER_EMPTY_THRESHOLD_US
&& isLoadingPossible()) {
// The renderers are not ready, there is more media available to load, and the LoadControl
// is refusing to load it (indicated by !playbackInfo.isLoading). This could be because the
// renderers are still transitioning to their ready states, but it could also indicate a
// stuck playback. The playbackInfo.totalBufferedDurationUs check further isolates the
// cause to a lack of media for the renderers to consume, to avoid classifying playbacks as
// stuck when they're waiting for other reasons (in particular, loading DRM keys).
playbackMaybeStuck = true;
}
}
if (!playbackMaybeStuck) {
playbackMaybeBecameStuckAtMs = C.TIME_UNSET;
} else if (playbackMaybeBecameStuckAtMs == C.TIME_UNSET) {
playbackMaybeBecameStuckAtMs = clock.elapsedRealtime();
} else if (clock.elapsedRealtime() - playbackMaybeBecameStuckAtMs >= PLAYBACK_STUCK_AFTER_MS) {
throw new IllegalStateException("Playback stuck buffering and not loading");
}
boolean isPlaying = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY;
boolean sleepingForOffload = offloadSchedulingEnabled && requestForRendererSleep && isPlaying;
if (playbackInfo.sleepingForOffload != sleepingForOffload) {
playbackInfo = playbackInfo.copyWithSleepingForOffload(sleepingForOffload);
}
requestForRendererSleep = false; // A sleep request is only valid for the current doSomeWork.
if (sleepingForOffload || playbackInfo.playbackState == Player.STATE_ENDED) {
// No need to schedule next work.
} else if (isPlaying || playbackInfo.playbackState == Player.STATE_BUFFERING) {
// We are actively playing or waiting for data to be ready. Schedule next work quickly.
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
} else if (playbackInfo.playbackState == Player.STATE_READY && enabledRendererCount != 0) {
// We are ready, but not playing. Schedule next work less often to handle non-urgent updates.
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
}View on GitHub (pinned to dd430f7053)
Solutions
- Capture the full stack trace and the player's Loader/codec threads to find which component is blocked.
- Set explicit connect/read timeouts on your HttpDataSource (setConnectTimeoutMs/setReadTimeoutMs) so stalls surface as IOExceptions and trigger ExoPlayer's retry logic instead of a hang.
- Audit custom DataSource or Renderer implementations for unbounded blocking calls; make them interruptible.
- Reproduce with the same media on another device/network to rule out a pathological server or codec; if it is device-specific, report with the stack trace to the ExoPlayer/AndroidX Media issue tracker.
Example fix
// before — no timeouts, a stalled server wedges the loader thread
new DefaultHttpDataSource.Factory()
.setUserAgent("my-app")
// after
new DefaultHttpDataSource.Factory()
.setUserAgent("my-app")
.setConnectTimeoutMs(8_000)
.setReadTimeoutMs(8_000)
.setAllowCrossProtocolRedirects(true) Defensive patterns
Strategy: retry
Validate before calling
// give every HTTP data source hard deadlines so stalls become retries
DataSource.Factory http = new DefaultHttpDataSource.Factory()
.setConnectTimeoutMs(8_000)
.setReadTimeoutMs(8_000); Try / catch
player.addListener(new Player.Listener() {
@Override public void onPlayerError(PlaybackException error) {
if (error.getErrorCode()
== PlaybackException.ERROR_CODE_PLAYBACK_STUCK) { // internal watchdog fired
logDiagnostics(error); // full stack + player state
scheduleRecover(); // rebuild player and retry current item once
}
}
}); Prevention
- Set connect/read timeouts on all network DataSources
- Make custom DataSources interruptible; never block the loader thread indefinitely
- Keep a LoadErrorHandlingPolicy configured with sane retry windows
- Collect the stuck stack trace per device model and report codec/HAL-specific repros upstream
When it happens
Trigger: A renderer or loadable wedges: e.g. a custom DataSource blocking forever on a socket without timeout, a MediaCodec that never returns from dequeueOutputBuffer, a progressive source whose loader thread hangs, or a metered connection where the server stalls mid-stream while the buffer drains to empty.
Common situations: Custom DataSource implementations doing blocking I/O without timeouts; HTTP servers that accept the connection but never send bytes; bugs in experimental renderers; device codec drivers locking up. Note this is an internal watchdog crash — it indicates an infrastructure/media bug, not an app-level API misuse.
Related errors
- No license URL
- ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED
- ERROR_CODE_IO_BAD_HTTP_STATUS
- ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE
- Failed to load decoder native libraries.
AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14).
Data as JSON: /api/errors/01e196cb2f91e285.
Report an issue: GitHub.