diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7d789eb7de..6909025ec1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -62,6 +62,9 @@ * Parse `scte214:supplementalCodecs` attribute from DASH manifest to detect Dolby Vision formats ([#1785](https://github.com/androidx/media/pull/1785)). + * Improve handling of period transitions in live streams where the period + contains media samples beyond the declared period duration + ([#1698](https://github.com/androidx/media/issues/1698)). * Smooth Streaming Extension: * RTSP Extension: * Decoder Extensions (FFmpeg, VP9, AV1, etc.): diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java index 1b83ab70d2..392dcb4fc8 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/ChunkSampleStream.java @@ -722,6 +722,39 @@ public class ChunkSampleStream } } + /** + * Discards upstream samples that exceed the given clipped duration of the stream. + * + * @param clippedDurationUs The clipped duration of the stream in microseconds, or {@link + * C#TIME_UNSET} if not known. + */ + public void discardUpstreamSamplesForClippedDuration(long clippedDurationUs) { + Assertions.checkState(!loader.isLoading()); + if (isPendingReset() || clippedDurationUs == C.TIME_UNSET || mediaChunks.isEmpty()) { + return; + } + BaseMediaChunk lastMediaChunk = getLastMediaChunk(); + long lastMediaChunkEndTimeUs = + lastMediaChunk.clippedEndTimeUs != C.TIME_UNSET + ? lastMediaChunk.clippedEndTimeUs + : lastMediaChunk.endTimeUs; + if (lastMediaChunkEndTimeUs <= clippedDurationUs) { + // Last chunk doesn't need to be clipped further. + return; + } + long largestQueuedTimestampUs = primarySampleQueue.getLargestQueuedTimestampUs(); + if (largestQueuedTimestampUs <= clippedDurationUs) { + // No data beyond new duration that needs to be clipped. + return; + } + primarySampleQueue.discardUpstreamFrom(clippedDurationUs); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.discardUpstreamFrom(clippedDurationUs); + } + mediaSourceEventDispatcher.upstreamDiscarded( + primaryTrackType, clippedDurationUs, largestQueuedTimestampUs); + } + private void discardUpstream(int preferredQueueSize) { Assertions.checkState(!loader.isLoading()); diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java index 11640b171f..8ae8dbc367 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java @@ -324,6 +324,12 @@ import java.util.regex.Pattern; @Override public void reevaluateBuffer(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + if (!sampleStream.isLoading()) { + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + sampleStream.discardUpstreamSamplesForClippedDuration(periodDurationUs); + } + } compositeSequenceableLoader.reevaluateBuffer(positionUs); }