diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java index 20d9f44562..824b8dedaf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java @@ -894,6 +894,11 @@ public class SampleQueue implements TrackOutput { if (!keyframe || (flags[searchIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { // We've found a suitable sample. sampleCountToTarget = i; + if (timesUs[searchIndex] == timeUs) { + // Stop the search if we found a sample at the specified time to avoid returning a later + // sample with the same exactly matching timestamp. + break; + } } searchIndex++; if (searchIndex == capacity) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java index 241834fab5..11a2204f81 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java @@ -861,6 +861,53 @@ public final class SampleQueueTest { assertAllocationCount(1); } + @Test + public void discardTo_withDuplicateTimestamps_discardsOnlyToFirstMatch() { + writeTestData( + DATA, + SAMPLE_SIZES, + SAMPLE_OFFSETS, + /* sampleTimestamps= */ new long[] {0, 1000, 1000, 1000, 2000, 2000, 2000, 2000}, + SAMPLE_FORMATS, + /* sampleFlags= */ new int[] { + BUFFER_FLAG_KEY_FRAME, + 0, + BUFFER_FLAG_KEY_FRAME, + BUFFER_FLAG_KEY_FRAME, + 0, + 0, + BUFFER_FLAG_KEY_FRAME, + BUFFER_FLAG_KEY_FRAME + }); + + // Discard to first keyframe exactly matching the specified time. + sampleQueue.discardTo( + /* timeUs= */ 1000, /* toKeyframe= */ true, /* stopAtReadPosition= */ false); + assertThat(sampleQueue.getFirstIndex()).isEqualTo(2); + + // Do nothing when trying again. + sampleQueue.discardTo( + /* timeUs= */ 1000, /* toKeyframe= */ true, /* stopAtReadPosition= */ false); + sampleQueue.discardTo( + /* timeUs= */ 1000, /* toKeyframe= */ false, /* stopAtReadPosition= */ false); + assertThat(sampleQueue.getFirstIndex()).isEqualTo(2); + + // Discard to first frame exactly matching the specified time. + sampleQueue.discardTo( + /* timeUs= */ 2000, /* toKeyframe= */ false, /* stopAtReadPosition= */ false); + assertThat(sampleQueue.getFirstIndex()).isEqualTo(4); + + // Do nothing when trying again. + sampleQueue.discardTo( + /* timeUs= */ 2000, /* toKeyframe= */ false, /* stopAtReadPosition= */ false); + assertThat(sampleQueue.getFirstIndex()).isEqualTo(4); + + // Discard to first keyframe at same timestamp. + sampleQueue.discardTo( + /* timeUs= */ 2000, /* toKeyframe= */ true, /* stopAtReadPosition= */ false); + assertThat(sampleQueue.getFirstIndex()).isEqualTo(6); + } + @Test public void discardToDontStopAtReadPosition() { writeTestData();