From 4932300b9ac04521cabe52162af976949a77b9f6 Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Mon, 17 Mar 2025 08:54:37 -0700 Subject: [PATCH] =?UTF-8?q?Reduce=20flakiness=20for=20ServerSideAdInsertio?= =?UTF-8?q?n=C2=B1MediaSourceTest=20past=20SDK=2030?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PiperOrigin-RevId: 737631774 --- .../ServerSideAdInsertionMediaSourceTest.java | 37 +++++++++++++++++-- .../mp4/ssai-extended-adgroup.mp4.dump | 3 -- .../mp4/ssai-newly-inserted-adgroup.mp4.dump | 4 -- .../media3/test/utils/CapturingAudioSink.java | 30 +++++++++------ .../test/utils/CapturingRenderersFactory.java | 12 +++++- 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java index 130ddb96e9..b2bc1fa309 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java @@ -57,6 +57,9 @@ import androidx.media3.exoplayer.FormatHolder; import androidx.media3.exoplayer.LoadingInfo; import androidx.media3.exoplayer.analytics.AnalyticsListener; import androidx.media3.exoplayer.analytics.PlayerId; +import androidx.media3.exoplayer.audio.AudioSink; +import androidx.media3.exoplayer.audio.DefaultAudioSink; +import androidx.media3.exoplayer.audio.TeeAudioProcessor; import androidx.media3.exoplayer.drm.DrmSessionEventListener; import androidx.media3.exoplayer.drm.DrmSessionManager; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; @@ -71,6 +74,7 @@ import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.trackselection.FixedTrackSelection; import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.DefaultAllocator; +import androidx.media3.test.utils.CapturingAudioSink; import androidx.media3.test.utils.CapturingRenderersFactory; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.FakeClock; @@ -93,10 +97,8 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Unit test for {@link ServerSideAdInsertionMediaSource}. */ -@Config(sdk = 30) // TODO: b/382017156 - Remove this when the tests pass on API 31+. @RunWith(AndroidJUnit4.class) public final class ServerSideAdInsertionMediaSourceTest { @@ -472,7 +474,8 @@ public final class ServerSideAdInsertionMediaSourceTest { public void playbackWithNewlyInsertedAds_playsSuccessfulWithoutRendererResets() throws Exception { Context context = ApplicationProvider.getApplicationContext(); AtomicReference periodUid = new AtomicReference<>(); - CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context); + CapturingRenderersFactory renderersFactory = + new CapturingRenderersFactory(context, DiscontinuitySkippingCapturingAudioSink.create()); ExoPlayer player = new ExoPlayer.Builder(context, renderersFactory) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) @@ -552,7 +555,8 @@ public final class ServerSideAdInsertionMediaSourceTest { throws Exception { Context context = ApplicationProvider.getApplicationContext(); AtomicReference periodUid = new AtomicReference<>(); - CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context); + CapturingRenderersFactory renderersFactory = + new CapturingRenderersFactory(context, DiscontinuitySkippingCapturingAudioSink.create()); ExoPlayer player = new ExoPlayer.Builder(context, renderersFactory) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) @@ -832,4 +836,29 @@ public final class ServerSideAdInsertionMediaSourceTest { assertThat(readSamples).containsExactly(0L, 200L, 400L, 600L, 800L).inOrder(); } + + private static final class DiscontinuitySkippingCapturingAudioSink extends CapturingAudioSink { + /** Creates the capturing audio sink that skips dumping discontinuity events. */ + public static DiscontinuitySkippingCapturingAudioSink create() { + InterceptingBufferSink interceptingBufferSink = new InterceptingBufferSink(); + DiscontinuitySkippingCapturingAudioSink capturingAudioSink = + new DiscontinuitySkippingCapturingAudioSink( + new DefaultAudioSink.Builder(ApplicationProvider.getApplicationContext()) + .setAudioProcessorChain( + new DefaultAudioSink.DefaultAudioProcessorChain( + new TeeAudioProcessor(interceptingBufferSink))) + .build()); + interceptingBufferSink.setCapturingAudioSink(capturingAudioSink); + return capturingAudioSink; + } + + private DiscontinuitySkippingCapturingAudioSink(AudioSink sink) { + super(sink); + } + + @Override + public void handleDiscontinuity() { + getDelegateAudioSink().handleDiscontinuity(); + } + } } diff --git a/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-extended-adgroup.mp4.dump b/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-extended-adgroup.mp4.dump index c5ffa2358b..4e8d057c60 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-extended-adgroup.mp4.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-extended-adgroup.mp4.dump @@ -593,9 +593,6 @@ AudioSink: buffer #19: time = 1000000485179 data = empty - discontinuity: - discontinuity: - discontinuity: buffer #20: time = 1000000508399 data = empty diff --git a/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-newly-inserted-adgroup.mp4.dump b/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-newly-inserted-adgroup.mp4.dump index ff37699ae3..84fc497fb1 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-newly-inserted-adgroup.mp4.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-newly-inserted-adgroup.mp4.dump @@ -620,9 +620,6 @@ AudioSink: buffer #28: time = 1000000694158 data = empty - discontinuity: - discontinuity: - discontinuity: buffer #29: time = 1000000717378 data = empty @@ -635,7 +632,6 @@ AudioSink: buffer #32: time = 1000000787038 data = empty - discontinuity: buffer #33: time = 1000000810258 data = empty diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingAudioSink.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingAudioSink.java index 128cf87b4d..0153d39742 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingAudioSink.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingAudioSink.java @@ -41,9 +41,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * discontinuity and buffer events. */ @UnstableApi -public final class CapturingAudioSink extends ForwardingAudioSink implements Dumper.Dumpable { +public class CapturingAudioSink extends ForwardingAudioSink implements Dumper.Dumpable { private final List interceptedData; + private final AudioSink audioSink; private int bufferCount; private long lastPresentationTimeUs; @@ -53,19 +54,26 @@ public final class CapturingAudioSink extends ForwardingAudioSink implements Dum /** Creates the capturing audio sink. */ public static CapturingAudioSink create() { InterceptingBufferSink interceptingBufferSink = new InterceptingBufferSink(); - return new CapturingAudioSink( - new DefaultAudioSink.Builder(ApplicationProvider.getApplicationContext()) - .setAudioProcessorChain( - new DefaultAudioSink.DefaultAudioProcessorChain( - new TeeAudioProcessor(interceptingBufferSink))) - .build(), - interceptingBufferSink); + CapturingAudioSink capturingAudioSink = + new CapturingAudioSink( + new DefaultAudioSink.Builder(ApplicationProvider.getApplicationContext()) + .setAudioProcessorChain( + new DefaultAudioSink.DefaultAudioProcessorChain( + new TeeAudioProcessor(interceptingBufferSink))) + .build()); + interceptingBufferSink.setCapturingAudioSink(capturingAudioSink); + return capturingAudioSink; } - private CapturingAudioSink(AudioSink sink, InterceptingBufferSink interceptingBufferSink) { + protected CapturingAudioSink(AudioSink sink) { super(sink); + audioSink = sink; interceptedData = new ArrayList<>(); - interceptingBufferSink.setCapturingAudioSink(this); + } + + /** Returns the wrapped {@link AudioSink}. */ + protected final AudioSink getDelegateAudioSink() { + return audioSink; } @Override @@ -121,7 +129,7 @@ public final class CapturingAudioSink extends ForwardingAudioSink implements Dum dumper.endBlock(); } - private static final class InterceptingBufferSink implements TeeAudioProcessor.AudioBufferSink { + public static final class InterceptingBufferSink implements TeeAudioProcessor.AudioBufferSink { private @MonotonicNonNull CapturingAudioSink capturingAudioSink; private @MonotonicNonNull Format format; diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java index 4865bf2289..6c288e8c45 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java @@ -86,9 +86,19 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa * @param context The {@link Context}. */ public CapturingRenderersFactory(Context context) { + this(context, CapturingAudioSink.create()); + } + + /** + * Creates an instance. + * + * @param context The {@link Context}. + * @param capturingAudioSink The audio sink to use for capturing audio output. + */ + public CapturingRenderersFactory(Context context, CapturingAudioSink capturingAudioSink) { this.context = context; this.mediaCodecAdapterFactory = new CapturingMediaCodecAdapter.Factory(context); - this.audioSink = CapturingAudioSink.create(); + this.audioSink = capturingAudioSink; this.imageOutput = new CapturingImageOutput(); this.imageDecoderFactory = ImageDecoder.Factory.DEFAULT; this.textRendererFactory = TextRenderer::new;