diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTimestampPoller.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTimestampPoller.java index cfc8982b57..89aa9c5d53 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTimestampPoller.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTimestampPoller.java @@ -236,6 +236,16 @@ import java.lang.annotation.Target; return audioTimestamp != null ? audioTimestamp.getTimestampPositionFrames() : C.INDEX_UNSET; } + /** + * Sets up the poller to expect a reset in audio track frame position due to an impending track + * transition and reusing of the {@link AudioTrack}. + */ + public void expectTimestampFramePositionReset() { + if (audioTimestamp != null) { + audioTimestamp.expectTimestampFramePositionReset(); + } + } + private void updateState(@State int state) { this.state = state; switch (state) { @@ -270,6 +280,18 @@ import java.lang.annotation.Target; private long lastTimestampRawPositionFrames; private long lastTimestampPositionFrames; + /** + * Whether to expect a raw playback head reset. + * + *

When an {@link AudioTrack} is reused during offloaded playback, the {@link + * AudioTimestamp#framePosition} is reset upon track transition. {@link AudioTimestampWrapper} + * must be notified of the impending reset and keep track of total accumulated {@code + * AudioTimestamp.framePosition}. + */ + private boolean expectTimestampFramePositionReset; + + private long accumulatedRawTimestampFramePosition; + /** * Creates a new {@link AudioTimestamp} wrapper. * @@ -291,12 +313,19 @@ import java.lang.annotation.Target; if (updated) { long rawPositionFrames = audioTimestamp.framePosition; if (lastTimestampRawPositionFrames > rawPositionFrames) { - // The value must have wrapped around. - rawTimestampFramePositionWrapCount++; + if (expectTimestampFramePositionReset) { + accumulatedRawTimestampFramePosition += lastTimestampRawPositionFrames; + expectTimestampFramePositionReset = false; + } else { + // The value must have wrapped around. + rawTimestampFramePositionWrapCount++; + } } lastTimestampRawPositionFrames = rawPositionFrames; lastTimestampPositionFrames = - rawPositionFrames + (rawTimestampFramePositionWrapCount << 32); + rawPositionFrames + + accumulatedRawTimestampFramePosition + + (rawTimestampFramePositionWrapCount << 32); } return updated; } @@ -308,5 +337,9 @@ import java.lang.annotation.Target; public long getTimestampPositionFrames() { return lastTimestampPositionFrames; } + + public void expectTimestampFramePositionReset() { + expectTimestampFramePositionReset = true; + } } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTrackPositionTracker.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTrackPositionTracker.java index 3a8ca36465..e2b372591d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTrackPositionTracker.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTrackPositionTracker.java @@ -476,6 +476,9 @@ import java.lang.reflect.Method; */ public void expectRawPlaybackHeadReset() { expectRawPlaybackHeadReset = true; + if (audioTimestampPoller != null) { + audioTimestampPoller.expectTimestampFramePositionReset(); + } } /**