diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 32f92cee34..c300a06768 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -122,6 +122,8 @@ `http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as the main adaptation sets to which they refer. Trick play tracks are marked with the `C.ROLE_FLAG_TRICK_PLAY` flag. + * Fix assertion failure in `SampleQueue` when playing DASH streams with + EMSG tracks ([#7273](https://github.com/google/ExoPlayer/issues/7273)). * MP3: Add `IndexSeeker` for accurate seeks in VBR streams ([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker is enabled by passing `FLAG_ENABLE_INDEX_SEEKING` to the `Mp3Extractor`. It may diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java index bd7b307ada..87430d8788 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java @@ -686,7 +686,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return sampleQueues[i]; } } - SampleQueue trackOutput = new SampleQueue(allocator, drmSessionManager, eventDispatcher); + SampleQueue trackOutput = + new SampleQueue( + allocator, + /* playbackLooper= */ handler.getLooper(), + drmSessionManager, + eventDispatcher); trackOutput.setUpstreamFormatChangeListener(this); @NullableType TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1); 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 4371e39a3d..3c08012cb8 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 @@ -55,6 +55,7 @@ public class SampleQueue implements TrackOutput { private final SampleDataQueue sampleDataQueue; private final SampleExtrasHolder extrasHolder; + private final Looper playbackLooper; private final DrmSessionManager drmSessionManager; private final MediaSourceEventDispatcher eventDispatcher; @Nullable private UpstreamFormatChangedListener upstreamFormatChangeListener; @@ -94,6 +95,7 @@ public class SampleQueue implements TrackOutput { * Creates a sample queue. * * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. + * @param playbackLooper The looper associated with the media playback thread. * @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions} * from. The created instance does not take ownership of this {@link DrmSessionManager}. * @param eventDispatcher A {@link MediaSourceEventDispatcher} to notify of events related to this @@ -101,11 +103,13 @@ public class SampleQueue implements TrackOutput { */ public SampleQueue( Allocator allocator, + Looper playbackLooper, DrmSessionManager drmSessionManager, MediaSourceEventDispatcher eventDispatcher) { - sampleDataQueue = new SampleDataQueue(allocator); + this.playbackLooper = playbackLooper; this.drmSessionManager = drmSessionManager; this.eventDispatcher = eventDispatcher; + sampleDataQueue = new SampleDataQueue(allocator); extrasHolder = new SampleExtrasHolder(); capacity = SAMPLE_CAPACITY_INCREMENT; sourceIds = new int[capacity]; @@ -799,7 +803,6 @@ public class SampleQueue implements TrackOutput { // Ensure we acquire the new session before releasing the previous one in case the same session // is being used for both DrmInitData. @Nullable DrmSession previousSession = currentDrmSession; - Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper()); currentDrmSession = newDrmInitData != null ? drmSessionManager.acquireSession(playbackLooper, eventDispatcher, newDrmInitData) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 597cd78271..c67b35c857 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.chunk; +import android.os.Looper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -132,14 +133,22 @@ public class ChunkSampleStream implements SampleStream, S int[] trackTypes = new int[1 + embeddedTrackCount]; SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount]; - primarySampleQueue = new SampleQueue(allocator, drmSessionManager, eventDispatcher); + primarySampleQueue = + new SampleQueue( + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + drmSessionManager, + eventDispatcher); trackTypes[0] = primaryTrackType; sampleQueues[0] = primarySampleQueue; for (int i = 0; i < embeddedTrackCount; i++) { SampleQueue sampleQueue = new SampleQueue( - allocator, DrmSessionManager.getDummyDrmSessionManager(), eventDispatcher); + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + DrmSessionManager.getDummyDrmSessionManager(), + eventDispatcher); embeddedSampleQueues[i] = sampleQueue; sampleQueues[i + 1] = sampleQueue; trackTypes[i + 1] = this.embeddedTrackTypes[i]; 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 9a4524819e..41b953a0d2 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 @@ -26,6 +26,7 @@ import static java.util.Arrays.copyOfRange; import static org.junit.Assert.assertArrayEquals; import static org.mockito.Mockito.when; +import android.os.Looper; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; @@ -39,6 +40,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DefaultAllocator; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MediaSourceEventDispatcher; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; @@ -139,7 +141,12 @@ public final class SampleQueueTest { ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) .thenReturn(mockDrmSession); eventDispatcher = new MediaSourceEventDispatcher(); - sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher); + sampleQueue = + new SampleQueue( + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + mockDrmSessionManager, + eventDispatcher); formatHolder = new FormatHolder(); inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); } @@ -356,7 +363,12 @@ public final class SampleQueueTest { public void isReadyReturnsTrueForClearSampleAndPlayClearSamplesWithoutKeysIsTrue() { when(mockDrmSession.playClearSamplesWithoutKeys()).thenReturn(true); // We recreate the queue to ensure the mock DRM session manager flags are taken into account. - sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher); + sampleQueue = + new SampleQueue( + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + mockDrmSessionManager, + eventDispatcher); writeTestDataWithEncryptedSections(); assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue(); } @@ -534,7 +546,12 @@ public final class SampleQueueTest { public void allowPlayClearSamplesWithoutKeysReadsClearSamples() { when(mockDrmSession.playClearSamplesWithoutKeys()).thenReturn(true); // We recreate the queue to ensure the mock DRM session manager flags are taken into account. - sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher); + sampleQueue = + new SampleQueue( + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + mockDrmSessionManager, + eventDispatcher); when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED); writeTestDataWithEncryptedSections(); @@ -924,7 +941,11 @@ public final class SampleQueueTest { public void adjustUpstreamFormat() { String label = "label"; sampleQueue = - new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher) { + new SampleQueue( + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + mockDrmSessionManager, + eventDispatcher) { @Override public Format getAdjustedUpstreamFormat(Format format) { return super.getAdjustedUpstreamFormat(copyWithLabel(format, label)); @@ -940,7 +961,11 @@ public final class SampleQueueTest { public void invalidateUpstreamFormatAdjustment() { AtomicReference label = new AtomicReference<>("label1"); sampleQueue = - new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher) { + new SampleQueue( + allocator, + /* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), + mockDrmSessionManager, + eventDispatcher) { @Override public Format getAdjustedUpstreamFormat(Format format) { return super.getAdjustedUpstreamFormat(copyWithLabel(format, label.get())); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java index 365c05b3f4..7888841e23 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java @@ -288,6 +288,7 @@ public final class PlayerEmsgHandler implements Handler.Callback { this.sampleQueue = new SampleQueue( allocator, + /* playbackLooper= */ handler.getLooper(), DrmSessionManager.getDummyDrmSessionManager(), new MediaSourceEventDispatcher()); formatHolder = new FormatHolder(); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 14bf0eec25..20d8ed381c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.util.SparseIntArray; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -910,7 +911,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO; HlsSampleQueue sampleQueue = - new HlsSampleQueue(allocator, drmSessionManager, eventDispatcher, overridingDrmInitData); + new HlsSampleQueue( + allocator, + /* playbackLooper= */ handler.getLooper(), + drmSessionManager, + eventDispatcher, + overridingDrmInitData); if (isAudioVideo) { sampleQueue.setDrmInitData(drmInitData); } @@ -1380,10 +1386,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private HlsSampleQueue( Allocator allocator, + Looper playbackLooper, DrmSessionManager drmSessionManager, MediaSourceEventDispatcher eventDispatcher, Map overridingDrmInitData) { - super(allocator, drmSessionManager, eventDispatcher); + super(allocator, playbackLooper, drmSessionManager, eventDispatcher); this.overridingDrmInitData = overridingDrmInitData; }