diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java index 5fc84bd1aa..198b674dce 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/BaseRenderer.java @@ -418,6 +418,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { return lastResetPositionUs; } + /** + * Returns the offset added to timestamps of buffers read from the {@link SampleStream}. + * + *

Must only be called if the renderer is at least {@link #STATE_ENABLED}. + */ + protected final long getStreamOffsetUs() { + return streamOffsetUs; + } + /** Returns a clear {@link FormatHolder}. */ protected final FormatHolder getFormatHolder() { formatHolder.clear(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java index 29af0d4f7b..cc25bee7f7 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java @@ -119,7 +119,6 @@ public final class TextRenderer extends BaseRenderer implements Callback { private boolean inputStreamEnded; private boolean outputStreamEnded; @Nullable private Format streamFormat; - private long outputStreamOffsetUs; private long lastRendererPositionUs; private long finalStreamEndPositionUs; private boolean legacyDecodingEnabled; @@ -159,7 +158,6 @@ public final class TextRenderer extends BaseRenderer implements Callback { new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); formatHolder = new FormatHolder(); finalStreamEndPositionUs = C.TIME_UNSET; - outputStreamOffsetUs = C.TIME_UNSET; lastRendererPositionUs = C.TIME_UNSET; legacyDecodingEnabled = false; } @@ -206,7 +204,6 @@ public final class TextRenderer extends BaseRenderer implements Callback { long startPositionUs, long offsetUs, MediaSource.MediaPeriodId mediaPeriodId) { - outputStreamOffsetUs = offsetUs; streamFormat = formats[0]; if (!isCuesWithTiming(streamFormat)) { assertLegacyDecodingEnabledIfRequired(); @@ -462,7 +459,6 @@ public final class TextRenderer extends BaseRenderer implements Callback { streamFormat = null; finalStreamEndPositionUs = C.TIME_UNSET; clearOutput(); - outputStreamOffsetUs = C.TIME_UNSET; lastRendererPositionUs = C.TIME_UNSET; if (subtitleDecoder != null) { releaseSubtitleDecoder(); @@ -579,9 +575,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { @SideEffectFree private long getPresentationTimeUs(long positionUs) { checkState(positionUs != C.TIME_UNSET); - checkState(outputStreamOffsetUs != C.TIME_UNSET); - - return positionUs - outputStreamOffsetUs; + return positionUs - getStreamOffsetUs(); } @RequiresNonNull("streamFormat") diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DecoderVideoRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DecoderVideoRenderer.java index e128285879..5bb81f91ca 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DecoderVideoRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DecoderVideoRenderer.java @@ -153,7 +153,6 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { private int consecutiveDroppedFrameCount; private int buffersInCodecCount; private long lastRenderTimeUs; - private long outputStreamOffsetUs; /** Decoder event counters used for debugging purposes. */ protected DecoderCounters decoderCounters; @@ -342,10 +341,8 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { long offsetUs, MediaSource.MediaPeriodId mediaPeriodId) throws ExoPlaybackException { - // TODO: This shouldn't just update the output stream offset as long as there are still buffers - // of the previous stream in the decoder. It should also make sure to render the first frame of - // the next stream if the playback position reached the new stream. - outputStreamOffsetUs = offsetUs; + // TODO: This code should make sure to render the first frame of the next stream if the playback + // position reached the new stream. super.onStreamChanged(formats, startPositionUs, offsetUs, mediaPeriodId); } @@ -875,7 +872,9 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { outputFormat = formatQueue.pollFirst(); } - long presentationTimeUs = bufferTimeUs - outputStreamOffsetUs; + // TODO: This shouldn't just use the input stream offset and we should correctly track the + // output stream offset after decoding instead. + long presentationTimeUs = bufferTimeUs - getStreamOffsetUs(); if (shouldForceRender(earlyUs)) { renderOutputBuffer(outputBuffer, presentationTimeUs, checkNotNull(outputFormat)); return true; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/CameraMotionRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/CameraMotionRenderer.java index 7f02fdefe4..ffbdfe0db8 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/CameraMotionRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/spherical/CameraMotionRenderer.java @@ -28,7 +28,6 @@ import androidx.media3.exoplayer.ExoPlaybackException; import androidx.media3.exoplayer.FormatHolder; import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.RendererCapabilities; -import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.SampleStream.ReadDataResult; import java.nio.ByteBuffer; @@ -43,7 +42,6 @@ public final class CameraMotionRenderer extends BaseRenderer { private final DecoderInputBuffer buffer; private final ParsableByteArray scratch; - private long offsetUs; @Nullable private CameraMotionListener listener; private long lastTimestampUs; @@ -75,15 +73,6 @@ public final class CameraMotionRenderer extends BaseRenderer { } } - @Override - protected void onStreamChanged( - Format[] formats, - long startPositionUs, - long offsetUs, - MediaSource.MediaPeriodId mediaPeriodId) { - this.offsetUs = offsetUs; - } - @Override protected void onPositionReset(long positionUs, boolean joining) { lastTimestampUs = Long.MIN_VALUE; @@ -118,7 +107,7 @@ public final class CameraMotionRenderer extends BaseRenderer { continue; } - Util.castNonNull(listener).onCameraMotion(lastTimestampUs - offsetUs, rotation); + Util.castNonNull(listener).onCameraMotion(lastTimestampUs - getStreamOffsetUs(), rotation); } } diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java index 6df5063b58..4490c66329 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java @@ -43,7 +43,6 @@ public class FakeVideoRenderer extends FakeRenderer { private final AtomicReference videoSizeRef = new AtomicReference<>(); private @MonotonicNonNull Format format; @Nullable private Object output; - private long streamOffsetUs; private boolean renderedFirstFrameAfterReset; private boolean mayRenderFirstFrameAfterEnableIfNotStarted; private boolean renderedFirstFrameAfterEnable; @@ -73,7 +72,6 @@ public class FakeVideoRenderer extends FakeRenderer { MediaSource.MediaPeriodId mediaPeriodId) throws ExoPlaybackException { super.onStreamChanged(formats, startPositionUs, offsetUs, mediaPeriodId); - streamOffsetUs = offsetUs; renderedFirstFrameAfterReset = false; } @@ -150,7 +148,7 @@ public class FakeVideoRenderer extends FakeRenderer { ? (getState() == Renderer.STATE_STARTED || mayRenderFirstFrameAfterEnableIfNotStarted) : !renderedFirstFrameAfterReset); - shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs; + shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= getStreamOffsetUs(); @Nullable Object output = this.output; if (shouldProcess && !renderedFirstFrameAfterReset && output != null) { @MonotonicNonNull Format format = Assertions.checkNotNull(this.format); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java index 0a16123fe2..2b442986fd 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java @@ -42,7 +42,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; /* package */ abstract class ExoAssetLoaderBaseRenderer extends BaseRenderer { protected long streamStartPositionUs; - protected long streamOffsetUs; protected @MonotonicNonNull SampleConsumer sampleConsumer; protected @MonotonicNonNull Codec decoder; protected boolean isEnded; @@ -131,7 +130,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; long offsetUs, MediaSource.MediaPeriodId mediaPeriodId) { this.streamStartPositionUs = startPositionUs; - this.streamOffsetUs = offsetUs; } @Override diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java index c7cf809ef3..08dae45b79 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java @@ -130,6 +130,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ByteBuffer inputBytes = checkNotNull(inputBuffer.data); if (sefVideoSlowMotionFlattener != null) { + long streamOffsetUs = getStreamOffsetUs(); long presentationTimeUs = inputBuffer.timeUs - streamOffsetUs; boolean shouldDropInputBuffer = sefVideoSlowMotionFlattener.dropOrTransformSample(inputBytes, presentationTimeUs); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java index d5ff997cb8..72ab98ce23 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java @@ -300,7 +300,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Nullable private ExoPlaybackException pendingExoPlaybackException; private boolean inputStreamPending; private long streamStartPositionUs; - private long streamOffsetUs; private boolean mayRenderStartOfStream; private long offsetToCompositionTimeUs; @@ -311,7 +310,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; videoSink = checkStateNotNull(sequencePlayerRenderersWrapper.videoSink); videoEffects = ImmutableList.of(); streamStartPositionUs = C.TIME_UNSET; - streamOffsetUs = C.TIME_UNSET; } // ImageRenderer methods @@ -397,7 +395,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; checkState(getTimeline().getWindowCount() == 1); super.onStreamChanged(formats, startPositionUs, offsetUs, mediaPeriodId); streamStartPositionUs = startPositionUs; - streamOffsetUs = offsetUs; int mediaItemIndex = getTimeline().getIndexOfPeriod(mediaPeriodId.periodUid); editedMediaItem = sequencePlayerRenderersWrapper.sequence.editedMediaItems.get(mediaItemIndex); @@ -429,11 +426,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; long positionUs, long elapsedRealtimeUs, Bitmap outputImage, long timeUs) { if (inputStreamPending) { checkState(streamStartPositionUs != C.TIME_UNSET); - checkState(streamOffsetUs != C.TIME_UNSET); videoSink.setPendingVideoEffects(videoEffects); videoSink.setStreamTimestampInfo( streamStartPositionUs, - streamOffsetUs, + getStreamOffsetUs(), /* bufferTimestampAdjustmentUs= */ offsetToCompositionTimeUs, getLastResetPositionUs()); videoSink.onInputStreamChanged( @@ -451,6 +447,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } private ConstantRateTimestampIterator createTimestampIterator(long positionUs) { + long streamOffsetUs = getStreamOffsetUs(); long imageBaseTimestampUs = streamOffsetUs + offsetToCompositionTimeUs; long positionWithinImage = positionUs - streamOffsetUs; long firstBitmapTimeUs = imageBaseTimestampUs + positionWithinImage;