-
Notifications
You must be signed in to change notification settings - Fork 742
Description
Version
Media3 main branch
More version details
Media3 1.6.0
Devices that reproduce the issue
Multiple Android devices (tested on Android TV, Fire TV Stick and Android mobile)
Devices that do not reproduce the issue
NA
Reproducible in the demo app?
Yes
Reproduction steps
Issue description
When seeking across DASH periods in a multi-period live stream (e.g., from the start-over position in period 0 to the live edge in period 3), ExoPlayerImplInternal.getTotalBufferedDurationUs() can transiently report an enormously inflated value — as high as 31 minutes — despite virtually zero actual media data being loaded. This causes LoadControl.shouldContinueLoading() to receive a bufferedDurationUs parameter that far exceeds maxBufferUs, returning false and permanently stopping all loading. The player then stays stuck in STATE_BUFFERING indefinitely.
Reproduction scenario
- Stream type: Multi-period DASH live stream with DVR (type="dynamic", timeShiftBufferDepth="PT30M", 4 periods)
- Trigger: Seek from the start-over position (near the beginning, ~30s into the stream) to the live edge (~1946s) -- this crosses from an early period to the last period (period index 3)
- Result: Player enters STATE_BUFFERING and never transitions to STATE_READY
Expected result
After a cross-period seek, getTotalBufferedDurationUs() should reflect the actual amount of buffered media data. Since the buffer is flushed during the seek, it should report a value near 0 until real segments are loaded at the new position.
Actual result
getTotalBufferedDurationUs() reports 1,886,516,000µs (~31.4 minutes) with 0 bytes of actual data in the allocator, because loadingPeriodHolder.toPeriodTime(rendererPositionUs) converts the stale rendererPositionUs (still from the old period) using the new loading period's rendererOffset, producing a meaningless large negative period-time that inflates the result.
Media
Root cause analysis
The value passed as parameters.bufferedDurationUs to LoadControl.shouldContinueLoading() is computed by:
// ExoPlayerImplInternal.java
private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
if (loadingPeriodHolder == null) {
return 0;
}
long totalBufferedDurationUs =
bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
return max(0, totalBufferedDurationUs);
}
public long toPeriodTime(long rendererTimeUs) {
return rendererTimeUs - getRendererOffset();
}
The toPeriodTime(rendererPositionUs) method converts renderer-coordinate time to the loading period's local time:
periodTime = rendererPositionUs - loadingPeriodHolder.rendererOffset
So the full formula is:
totalBufferedDurationUs = bufferedPositionInLoadingPeriodUs - (rendererPositionUs - loadingPeriodHolder.rendererOffset)
During a cross-period seek there is a window where:
- The loading period has already transitioned to the target period (period 3), so
loadingPeriodHolder.getRendererOffset()is a very large value corresponding to the accumulated duration of prior periods. rendererPositionUs-- a field onExoPlayerImplInternal-- is stale, still reflecting the old playback position from the source period.toPeriodTime(rendererPositionUs)subtracts the new period's largerendererOffsetfrom the stale smallrendererPositionUs, producing a large negative period-time.bufferedPositionInLoadingPeriodUs(near 0, since loading just started in the new period) minus this large negative value produces a massive positive phantom duration.max(0, ...)does not catch this because the result is already positive.
Log evidence
The following logs were captured from a custom LoadControl that logs the parameters.bufferedDurationUs and allocator.totalBytesAllocated values received in shouldContinueLoading().
Before the seek — normal buffering:
19:04:54.372 shouldContinueLoading: result=true,
bufferedDurationUs=41,366,689, // ~41s — normal
totalBytesAllocated=12,255,232, // ~12MB — real data
totalTargetBufferBytes=144,310,272
Seek issued — seekTo(1946579):
19:04:54.365 seekTo: targetPositionMs=1946579, currentPosition=30101
19:04:54.425 seekTo completed: positionAfterSeek=1946579,
currentPeriodIndex=3, totalBufferedDurationAfterSeek=0
Immediately after seek — buffer correctly reset:
19:04:54.483 shouldContinueLoading: result=true,
bufferedDurationUs=0, // correct — buffer flushed
totalBytesAllocated=131,072 // ~128KB
19:04:54.486 shouldContinueLoading: result=true,
bufferedDurationUs=0, // still correct
totalBytesAllocated=131,072
16ms later — bufferedDurationUs explodes with no actual data loaded:
19:04:54.499 shouldContinueLoading: result=false,
bufferedDurationUs=1,886,516,000, // ~31.4 MINUTES!
totalBytesAllocated=0, // ZERO bytes!
totalTargetBufferBytes=144,310,272
Loading stops permanently — shouldContinueLoading keeps returning false:
19:04:54.500 shouldContinueLoading: result=false, bufferedDurationUs=1,886,516,000, totalBytesAllocated=0
19:04:54.509 shouldContinueLoading: result=false, bufferedDurationUs=1,886,516,000, totalBytesAllocated=0
19:04:54.519 shouldContinueLoading: result=false, bufferedDurationUs=1,886,516,000, totalBytesAllocated=0
... continues indefinitely ...
The currentPosition reported by ExoPlayer confirms the stale renderer position:
19:04:54.426 onEvents: currentPosition=1,946,579 // immediately after seek — correct
19:04:54.542 onEvents: currentPosition=60,063 // 116ms later — reverted to old value!
bufferedPosition=1,946,579 // bufferedPosition is at seek target
This directly shows the position inconsistency that drives the inflated calculation:
bufferedDurationUs ≈ bufferedPosition - currentPosition
= 1,946,579,000 - 60,063,000
= 1,886,516,000 µs
Suggested fix
In ExoPlayerImplInternal, ensure rendererPositionUs is updated before maybeContinueLoading() is called during seek processing, or add a consistency check in getTotalBufferedDurationUs() that validates the computed result is plausible (e.g., by cross-checking against allocator.totalBytesAllocated).
Bug Report
- You will email the zip file produced by
adb bugreportto android-media-github@google.com after filing this issue.