Add handleContentTimelineChanged to AdsLoader

The new callback allows an app to read data from the content
timeline to populate the `AdPlaybackState`. To avoid a deadlock
between the `AdMediaSource` waiting for the `AdPlaybackState` and
the app waiting for the content `Timeline`, a boolean flag
`useLazyContentSourcePreparation` is introduced to tell the
`AdsMediaSource` to prepare the content source immediately
to make the `Timeline` available.

A unit test verifies that in none of the cases (lazy preparation or
immediate preparation) a `Timeline` without ad data is leaked to the
caller. This ensures that in the case of a preroll, the player won't
initially and accidentally read content media data before starting to
load the preroll ad. While the content source is prepared early, no
content media period must be created before the preroll starts.

PiperOrigin-RevId: 696885392
This commit is contained in:
bachinger 2024-11-15 07:46:18 -08:00 committed by Copybara-Service
parent c3d4a3d683
commit 1af0b5b432
6 changed files with 498 additions and 19 deletions

View File

@ -601,7 +601,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
mediaItem.mediaId, mediaItem.localConfiguration.uri, adsConfiguration.adTagUri),
/* adMediaSourceFactory= */ this,
adsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
}
/** Loads media source factories lazily. */

View File

@ -21,6 +21,7 @@ import androidx.media3.common.AdViewProvider;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.source.MediaSource;
@ -157,6 +158,24 @@ public interface AdsLoader {
@UnstableApi
void stop(AdsMediaSource adsMediaSource, EventListener eventListener);
/**
* Notifies the ads loader when the content source has changed its timeline. Called on the main
* thread by {@link AdsMediaSource}.
*
* <p>If you override this callback for the purpose of reading ad data from the timeline to
* populate the {@link AdPlaybackState} with, you need to pass true to the constructor of {@link
* AdsMediaSource#AdsMediaSource(MediaSource, DataSpec, Object, MediaSource.Factory, AdsLoader,
* AdViewProvider, boolean) AdsMediaSource} to indicate the content source needs to be prepared
* upfront.
*
* @param mediaItem The {@link MediaItem} of the source that produced the timeline.
* @param timeline The timeline of the content source.
*/
@UnstableApi
default void handleContentTimelineChanged(MediaItem mediaItem, Timeline timeline) {
// Do nothing.
}
/**
* Notifies the ads loader that preparation of an ad media period is complete. Called on the main
* thread by {@link AdsMediaSource}.

View File

@ -126,7 +126,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* #TYPE_UNEXPECTED}.
*/
public RuntimeException getRuntimeExceptionForUnexpected() {
Assertions.checkState(type == TYPE_UNEXPECTED);
checkState(type == TYPE_UNEXPECTED);
return (RuntimeException) checkNotNull(getCause());
}
}
@ -155,6 +155,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* Constructs a new source that inserts ads linearly with the content specified by {@code
* contentMediaSource}.
*
* <p>This is equivalent to passing true as param {@code useLazyContentSourcePreparation} when
* calling {@link AdsMediaSource#AdsMediaSource(MediaSource, DataSpec, Object,
* MediaSource.Factory, AdsLoader, AdViewProvider, boolean)}.
*
* @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param adTagDataSpec The data specification of the ad tag to load.
* @param adsId An opaque identifier for ad playback state associated with this instance. Ad
@ -169,11 +173,49 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
MediaSource contentMediaSource,
DataSpec adTagDataSpec,
Object adsId,
MediaSource.Factory adMediaSourceFactory,
Factory adMediaSourceFactory,
AdsLoader adsLoader,
AdViewProvider adViewProvider) {
this(
contentMediaSource,
adTagDataSpec,
adsId,
adMediaSourceFactory,
adsLoader,
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
}
/**
* Constructs a new source that inserts ads linearly with the content specified by {@code
* contentMediaSource}.
*
* @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param adTagDataSpec The data specification of the ad tag to load.
* @param adsId An opaque identifier for ad playback state associated with this instance. Ad
* loading and playback state is shared among all playlist items that have the same ads id (by
* {@link Object#equals(Object) equality}), so it is important to pass the same identifiers
* when constructing playlist items each time the player returns to the foreground.
* @param adMediaSourceFactory Factory for media sources used to load ad media.
* @param adsLoader The loader for ads.
* @param adViewProvider Provider of views for the ad UI.
* @param useLazyContentSourcePreparation True if the content source should be prepared lazily and
* wait for an {@link AdPlaybackState} to be set before preparing. False if the timeline is
* required {@linkplain AdsLoader#handleContentTimelineChanged(MediaItem, Timeline) to read ad
* data from it} to populate the {@link AdPlaybackState} (for instance from HLS
* interstitials).
*/
public AdsMediaSource(
MediaSource contentMediaSource,
DataSpec adTagDataSpec,
Object adsId,
Factory adMediaSourceFactory,
AdsLoader adsLoader,
AdViewProvider adViewProvider,
boolean useLazyContentSourcePreparation) {
this.contentMediaSource =
new MaskingMediaSource(contentMediaSource, /* useLazyPreparation= */ true);
new MaskingMediaSource(
contentMediaSource, /* useLazyPreparation= */ useLazyContentSourcePreparation);
this.contentDrmConfiguration =
checkNotNull(contentMediaSource.getMediaItem().localConfiguration).drmConfiguration;
this.adMediaSourceFactory = adMediaSourceFactory;
@ -288,6 +330,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
} else {
Assertions.checkArgument(newTimeline.getPeriodCount() == 1);
contentTimeline = newTimeline;
mainHandler.post(() -> adsLoader.handleContentTimelineChanged(getMediaItem(), newTimeline));
}
maybeUpdateSourceInfo();
}

View File

@ -16,6 +16,7 @@
package androidx.media3.exoplayer.source.ads;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@ -28,12 +29,16 @@ import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.net.Uri;
import android.os.Looper;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.AdViewProvider;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MaskingMediaSource;
@ -49,6 +54,12 @@ import androidx.media3.test.utils.TestUtil;
import androidx.media3.test.utils.robolectric.RobolectricUtil;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Rule;
@ -102,6 +113,7 @@ public final class AdsMediaSourceTest {
private static final DataSpec TEST_ADS_DATA_SPEC = new DataSpec(Uri.EMPTY);
private static final Object TEST_ADS_ID = new Object();
private static final long TIMEOUT_MS = 5_000L;
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@ -132,7 +144,8 @@ public final class AdsMediaSourceTest {
TEST_ADS_ID,
adMediaSourceFactory,
mockAdsLoader,
mockAdViewProvider);
mockAdViewProvider,
/* useLazyContentSourcePreparation= */ true);
adsMediaSource.prepareSource(
mockMediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
shadowOf(Looper.getMainLooper()).idle();
@ -325,6 +338,395 @@ public final class AdsMediaSourceTest {
.isEqualTo(updatedMediaItem);
}
@Test
public void
prepare_withPrerollUsingLazyContentSourcePreparationFalse_allExternalTimelinesWithAds()
throws InterruptedException {
AtomicBoolean contentMediaPeriodCreated = new AtomicBoolean();
MediaSource fakeContentMediaSource =
new FakeMediaSource() {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
contentMediaPeriodCreated.set(true);
return super.createPeriod(id, allocator, startPositionUs);
}
};
CountDownLatch adSourcePreparedLatch = new CountDownLatch(1);
AtomicInteger adSourcePreparedCounter = new AtomicInteger();
List<MediaPeriodId> createdAdMediaPeriodIds = new ArrayList<>();
MediaSource fakeAdMediaSource =
new FakeMediaSource() {
@Override
public synchronized void prepareSourceInternal(
@Nullable TransferListener mediaTransferListener) {
adSourcePreparedLatch.countDown();
adSourcePreparedCounter.incrementAndGet();
super.prepareSourceInternal(mediaTransferListener);
}
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
createdAdMediaPeriodIds.add(id);
return super.createPeriod(id, allocator, startPositionUs);
}
};
CountDownLatch contentTimelineChangedCalledLatch = new CountDownLatch(1);
AtomicReference<EventListener> eventListenerRef = new AtomicReference<>();
AdsLoader fakeAdsLoader =
new NoOpAdsLoader() {
@Override
public void start(
AdsMediaSource adsMediaSource,
DataSpec adTagDataSpec,
Object adsId,
AdViewProvider adViewProvider,
EventListener eventListener) {
eventListenerRef.set(eventListener);
}
@Override
public void handleContentTimelineChanged(MediaItem mediaItem, Timeline timeline) {
contentTimelineChangedCalledLatch.countDown();
}
};
MediaSource.Factory adMediaSourceFactory = mock(MediaSource.Factory.class);
when(adMediaSourceFactory.createMediaSource(any(MediaItem.class)))
.thenReturn(fakeAdMediaSource);
// Prepare the AdsMediaSource and capture the event listener the ads loader receives.
AdsMediaSource adsMediaSource =
new AdsMediaSource(
fakeContentMediaSource,
TEST_ADS_DATA_SPEC,
TEST_ADS_ID,
adMediaSourceFactory,
fakeAdsLoader,
mock(AdViewProvider.class),
/* useLazyContentSourcePreparation= */ false);
AtomicInteger mediaSourceCallerCallCounter = new AtomicInteger();
List<Timeline> externallyReceivedTimelines = new ArrayList<>();
List<MediaPeriodId> externallyRequestedPeriods = new ArrayList<>();
MediaSource.MediaSourceCaller fakeMediaSourceCaller =
(source, timeline) -> {
// The caller creates a media period at position 0 according to the timeline.
mediaSourceCallerCallCounter.incrementAndGet();
externallyReceivedTimelines.add(timeline);
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
Timeline.Period period =
timeline.getPeriod(
window.firstPeriodIndex, new Timeline.Period(), /* setIds= */ true);
// Search for pre roll ad group if any.
int adGroupIndex =
period.adPlaybackState.getAdGroupIndexForPositionUs(
window.positionInFirstPeriodUs, period.durationUs);
MediaPeriodId mediaPeriodId =
adGroupIndex == C.INDEX_UNSET
? new MediaPeriodId(period.uid, /* windowSequenceNumber= */ 0L)
: new MediaPeriodId(
123L,
/* adGroupIndex= */ adGroupIndex,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0L);
externallyRequestedPeriods.add(mediaPeriodId);
// Create a media period immediately regardless whether it is the same as before.
source.createPeriod(mediaPeriodId, mock(Allocator.class), /* startPositionUs= */ 0L);
};
// Prepare the source which must not notify the caller with a timeline yet.
adsMediaSource.prepareSource(
fakeMediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
shadowOf(Looper.getMainLooper()).idle();
// Verify ads loader was called with the content timeline to allow populating the ads.
assertThat(contentTimelineChangedCalledLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
// Verify external caller not yet notified even when content timeline available.
assertThat(mediaSourceCallerCallCounter.get()).isEqualTo(0);
// Verify no content media period has been created.
assertThat(contentMediaPeriodCreated.get()).isFalse();
// Verify ad source not yet prepared.
assertThat(adSourcePreparedCounter.get()).isEqualTo(0);
// Setting the ad playback state allows the outer AdsMediaSource to complete
// preparation of the AdsMediaSource that makes the external caller create the first period
// according to the timeline.
eventListenerRef
.get()
.onAdPlaybackState(
new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_DURATION_US)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAvailableAdMediaItem(
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
MediaItem.fromUri("https://google.com/ad"))
.withAdResumePositionUs(/* adResumePositionUs= */ 0)
.withAdDurationsUs(/* adGroupIndex= */ 0, 10_000_000L));
shadowOf(Looper.getMainLooper()).idle();
// Ad source was prepared once.
assertThat(adSourcePreparedCounter.get()).isEqualTo(1);
// Verify that no content period was created. Content source prepared only to get the playlist.
assertThat(contentMediaPeriodCreated.get()).isFalse();
// Verify the caller got two timeline updates.
assertThat(mediaSourceCallerCallCounter.get()).isEqualTo(2);
// Verify whether every externally exposed timeline was augmented with ad data.
assertThat(externallyRequestedPeriods)
.containsExactly(
new MediaPeriodId(
123L,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0L),
new MediaPeriodId(
123L,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0L))
.inOrder();
// Verify the requested media ID in the child ad sources without ad data.
assertThat(createdAdMediaPeriodIds)
.containsExactly(
new MediaPeriodId(
new Pair<>(0, 0),
/* adGroupIndex= */ -1,
/* adIndexInAdGroup= */ -1,
/* windowSequenceNumber= */ 0L),
new MediaPeriodId(
new Pair<>(0, 0),
/* adGroupIndex= */ -1,
/* adIndexInAdGroup= */ -1,
/* windowSequenceNumber= */ 0L))
.inOrder();
// Verify all external exposed timelines contained ad data with the duration updated according
// to the actual duration of the ad sources.
assertThat(externallyReceivedTimelines).hasSize(2);
assertThat(
externallyReceivedTimelines
.get(0)
.getPeriod(0, new Timeline.Period())
.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(C.TIME_UNSET); // Overridden by AdsMediaSource before the source was prepared.
assertThat(
externallyReceivedTimelines
.get(1)
.getPeriod(0, new Timeline.Period())
.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(133_000_000); // Overridden by AdsMediaSource with the actual source duration.
}
@Test
public void prepare_withPrerollUsingLazyContentSourcePreparationTrue_allExternalTimelinesWithAds()
throws InterruptedException {
AtomicBoolean contentMediaPeriodCreated = new AtomicBoolean();
MediaSource fakeContentMediaSource =
new FakeMediaSource() {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
contentMediaPeriodCreated.set(true);
return super.createPeriod(id, allocator, startPositionUs);
}
};
CountDownLatch adSourcePreparedLatch = new CountDownLatch(1);
AtomicInteger adSourcePreparedCounter = new AtomicInteger();
List<MediaPeriodId> createdAdMediaPeriodIds = new ArrayList<>();
MediaSource fakeAdMediaSource =
new FakeMediaSource() {
@Override
public synchronized void prepareSourceInternal(
@Nullable TransferListener mediaTransferListener) {
adSourcePreparedLatch.countDown();
adSourcePreparedCounter.incrementAndGet();
super.prepareSourceInternal(mediaTransferListener);
}
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
createdAdMediaPeriodIds.add(id);
return super.createPeriod(id, allocator, startPositionUs);
}
};
AtomicInteger contentTimelineChangedCallCount = new AtomicInteger();
AtomicReference<EventListener> eventListenerRef = new AtomicReference<>();
AdsLoader fakeAdsLoader =
new NoOpAdsLoader() {
@Override
public void start(
AdsMediaSource adsMediaSource,
DataSpec adTagDataSpec,
Object adsId,
AdViewProvider adViewProvider,
EventListener eventListener) {
eventListenerRef.set(eventListener);
}
@Override
public void handleContentTimelineChanged(MediaItem mediaItem, Timeline timeline) {
contentTimelineChangedCallCount.incrementAndGet();
}
};
MediaSource.Factory adMediaSourceFactory = mock(MediaSource.Factory.class);
when(adMediaSourceFactory.createMediaSource(any(MediaItem.class)))
.thenReturn(fakeAdMediaSource);
// Prepare the AdsMediaSource and capture the event listener the ads loader receives.
AdsMediaSource adsMediaSource =
new AdsMediaSource(
fakeContentMediaSource,
TEST_ADS_DATA_SPEC,
TEST_ADS_ID,
adMediaSourceFactory,
fakeAdsLoader,
mock(AdViewProvider.class),
/* useLazyContentSourcePreparation= */ true);
AtomicInteger mediaSourceCallerCallCounter = new AtomicInteger();
List<Timeline> externallyReceivedTimelines = new ArrayList<>();
List<MediaPeriodId> externallyRequestedPeriods = new ArrayList<>();
MediaSource.MediaSourceCaller fakeMediaSourceCaller =
(source, timeline) -> {
mediaSourceCallerCallCounter.incrementAndGet();
externallyReceivedTimelines.add(timeline);
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
Timeline.Period period =
timeline.getPeriod(
window.firstPeriodIndex, new Timeline.Period(), /* setIds= */ true);
// Search for the preroll ad group.
int adGroupIndex =
period.adPlaybackState.getAdGroupIndexForPositionUs(
window.positionInFirstPeriodUs, period.durationUs);
MediaPeriodId mediaPeriodId =
adGroupIndex == C.INDEX_UNSET
? new MediaPeriodId(period.uid, /* windowSequenceNumber= */ 0L)
: new MediaPeriodId(
123L,
/* adGroupIndex= */ adGroupIndex,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0L);
externallyRequestedPeriods.add(mediaPeriodId);
// Create a media period immediately.
source.createPeriod(mediaPeriodId, mock(Allocator.class), /* startPositionUs= */ 0L);
};
// Prepare the source that must not result in an external timeline without ad data.
adsMediaSource.prepareSource(
fakeMediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
shadowOf(Looper.getMainLooper()).idle();
// External caller not yet notified.
assertThat(mediaSourceCallerCallCounter.get()).isEqualTo(0);
// Verify that the content source is not prepared. Must never happen.
assertThat(contentTimelineChangedCallCount.get()).isEqualTo(0);
// Verify that th ad source is not yet prepared.
assertThat(adSourcePreparedCounter.get()).isEqualTo(0);
// Setting the ad playback state allows the outer AdsMediaSource to complete
// preparation of the AdsMediaSource that makes the external caller create the first period
// according to the timeline.
eventListenerRef
.get()
.onAdPlaybackState(
new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_DURATION_US)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAvailableAdMediaItem(
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
MediaItem.fromUri("https://google.com/ad"))
.withAdResumePositionUs(/* adResumePositionUs= */ 0)
.withAdDurationsUs(/* adGroupIndex= */ 0, 10_000_000L));
shadowOf(Looper.getMainLooper()).idle();
// Content source not prepared.
assertThat(contentTimelineChangedCallCount.get()).isEqualTo(0);
// Verify that the ad source was prepared once.
assertThat(adSourcePreparedCounter.get()).isEqualTo(1);
// Verify that no content period was created.
assertThat(contentMediaPeriodCreated.get()).isFalse();
// Verify the caller got two timeline updates.
assertThat(mediaSourceCallerCallCounter.get()).isEqualTo(2);
// Verify whether every externally exposed timeline was augmented with ad data.
assertThat(externallyRequestedPeriods)
.containsExactly(
new MediaPeriodId(
123L,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0L),
new MediaPeriodId(
123L,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0L))
.inOrder();
// Verify the requested media ID in the child ad sources without ad data.
assertThat(createdAdMediaPeriodIds)
.containsExactly(
new MediaPeriodId(
new Pair<>(0, 0),
/* adGroupIndex= */ -1,
/* adIndexInAdGroup= */ -1,
/* windowSequenceNumber= */ 0L),
new MediaPeriodId(
new Pair<>(0, 0),
/* adGroupIndex= */ -1,
/* adIndexInAdGroup= */ -1,
/* windowSequenceNumber= */ 0L))
.inOrder();
// Verify all external exposed timeline contained ad data with the duration updated according
// to the actual duration of the ad sources.
assertThat(externallyReceivedTimelines).hasSize(2);
assertThat(
externallyReceivedTimelines
.get(0)
.getPeriod(0, new Timeline.Period())
.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(C.TIME_UNSET); // Overridden by AdsMediaSource before the source was prepared.
assertThat(
externallyReceivedTimelines
.get(1)
.getPeriod(0, new Timeline.Period())
.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(133_000_000); // Overridden by AdsMediaSource with the actual source duration.
}
private static class NoOpAdsLoader implements AdsLoader {
@Override
public void setPlayer(@Nullable Player player) {}
@Override
public void release() {}
@Override
public void setSupportedContentTypes(@C.ContentType int... contentTypes) {}
@Override
public void start(
AdsMediaSource adsMediaSource,
DataSpec adTagDataSpec,
Object adsId,
AdViewProvider adViewProvider,
EventListener eventListener) {}
@Override
public void stop(AdsMediaSource adsMediaSource, EventListener eventListener) {}
@Override
public void handlePrepareComplete(
AdsMediaSource adsMediaSource, int adGroupIndex, int adIndexInAdGroup) {}
@Override
public void handlePrepareError(
AdsMediaSource adsMediaSource,
int adGroupIndex,
int adIndexInAdGroup,
IOException exception) {}
@Override
public void handleContentTimelineChanged(MediaItem mediaItem, Timeline timeline) {}
}
private static MediaSource buildMediaSource(MediaItem mediaItem) {
FakeMediaSource fakeMediaSource = new FakeMediaSource();
fakeMediaSource.setCanUpdateMediaItems(true);
@ -344,6 +746,7 @@ public final class AdsMediaSourceTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()),
adsLoader,
/* adViewProvider= */ () -> null);
/* adViewProvider= */ () -> null,
/* useLazyContentSourcePreparation= */ true);
}
}

View File

@ -241,7 +241,8 @@ public final class ImaPlaybackTest {
/* adsId= */ adTagDataSpec.uri,
new DefaultMediaSourceFactory(context),
Assertions.checkNotNull(imaAdsLoader),
() -> overlayFrameLayout);
() -> overlayFrameLayout,
/* useLazyContentSourcePreparation= */ true);
}
@Override

View File

@ -177,7 +177,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
timelineWindowDefinitions =
new TimelineWindowDefinition[] {getInitialTimelineWindowDefinition(TEST_ADS_ID)};
adsLoaderListener = new TestAdsLoaderListener(/* periodIndex= */ 0);
@ -760,7 +761,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
@ -802,7 +804,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
@ -843,7 +846,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
@ -882,7 +886,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long firstMidrollPeriodTimeUs =
firstMidrollWindowTimeUs
@ -931,7 +936,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long firstMidrollPeriodTimeUs =
firstMidrollWindowTimeUs
@ -1021,7 +1027,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
@ -1106,7 +1113,8 @@ public final class ImaAdsLoaderTest {
secondAdsId,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
timelineWindowDefinitions =
new TimelineWindowDefinition[] {
getInitialTimelineWindowDefinition(TEST_ADS_ID),
@ -1166,7 +1174,8 @@ public final class ImaAdsLoaderTest {
secondAdsId,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
timelineWindowDefinitions =
new TimelineWindowDefinition[] {
getInitialTimelineWindowDefinition(TEST_ADS_ID),
@ -1233,7 +1242,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
timelineWindowDefinitions =
new TimelineWindowDefinition[] {
getInitialTimelineWindowDefinition(TEST_ADS_ID),
@ -1285,7 +1295,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
@ -1311,7 +1322,8 @@ public final class ImaAdsLoaderTest {
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adViewProvider,
/* useLazyContentSourcePreparation= */ true);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);