Use MaskingMediaSource for AdsMediaSource content source

This means the content source is 'prepared' instantly with a
placeholder, enabling all further preparation steps (e.g. loading
preroll ads) while the actual content is still preparing. This
improvement can speed up the start time for prerolls in  manifest-based
content that doesn't have a zero-time preparation step like progressive
media.

Issue: androidx/media#1358
PiperOrigin-RevId: 633640746
This commit is contained in:
tonihei 2024-05-14 10:59:31 -07:00 committed by Copybara-Service
parent bf7b4e0d8c
commit d27c36ac9e
3 changed files with 29 additions and 10 deletions

View File

@ -30,6 +30,9 @@
* Add new error code
`PlaybackException.ERROR_CODE_DECODING_RESOURCES_RECLAIMED` that is used
when codec resources are reclaimed for higher priority tasks.
* Let `AdsMediaSource` load preroll ads before initial content media
preparation completes
([#1358](https://github.com/androidx/media/issues/1358)).
* Transformer:
* Work around a decoder bug where the number of audio channels was capped
at stereo when handling PCM input.

View File

@ -38,6 +38,7 @@ import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.source.CompositeMediaSource;
import androidx.media3.exoplayer.source.LoadEventInfo;
import androidx.media3.exoplayer.source.MaskingMediaPeriod;
import androidx.media3.exoplayer.source.MaskingMediaSource;
import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
@ -134,7 +135,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
private static final MediaPeriodId CHILD_SOURCE_MEDIA_PERIOD_ID =
new MediaPeriodId(/* periodUid= */ new Object());
private final MediaSource contentMediaSource;
private final MaskingMediaSource contentMediaSource;
@Nullable final MediaItem.DrmConfiguration contentDrmConfiguration;
private final MediaSource.Factory adMediaSourceFactory;
private final AdsLoader adsLoader;
@ -171,7 +172,8 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
MediaSource.Factory adMediaSourceFactory,
AdsLoader adsLoader,
AdViewProvider adViewProvider) {
this.contentMediaSource = contentMediaSource;
this.contentMediaSource =
new MaskingMediaSource(contentMediaSource, /* useLazyPreparation= */ true);
this.contentDrmConfiguration =
checkNotNull(contentMediaSource.getMediaItem().localConfiguration).drmConfiguration;
this.adMediaSourceFactory = adMediaSourceFactory;
@ -206,6 +208,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
super.prepareSourceInternal(mediaTransferListener);
ComponentListener componentListener = new ComponentListener();
this.componentListener = componentListener;
contentTimeline = contentMediaSource.getTimeline();
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource);
mainHandler.post(
() ->

View File

@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@ -35,6 +36,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MaskingMediaSource;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
@ -81,7 +83,9 @@ public final class AdsMediaSourceTest {
/* isDynamic= */ false,
/* useLiveConfiguration= */ false,
/* manifest= */ null,
MediaItem.fromUri(Uri.parse("https://google.com/empty")));
FakeMediaSource.FAKE_MEDIA_ITEM);
private static final Timeline PLACEHOLDER_CONTENT_TIMELINE =
new MaskingMediaSource.PlaceholderTimeline(FakeMediaSource.FAKE_MEDIA_ITEM);
private static final Object CONTENT_PERIOD_UID =
CONTENT_TIMELINE.getUidOfPeriod(/* periodIndex= */ 0);
@ -147,7 +151,8 @@ public final class AdsMediaSourceTest {
}
@Test
public void createPeriod_preparesChildAdMediaSourceAndRefreshesSourceInfo() {
public void createPeriod_forPreroll_preparesChildAdMediaSourceAndRefreshesSourceInfo() {
// This should be unused if we only create the preroll period.
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
adsMediaSource.createPeriod(
new MediaPeriodId(
@ -162,11 +167,14 @@ public final class AdsMediaSourceTest {
assertThat(prerollAdMediaSource.isPrepared()).isTrue();
verify(mockMediaSourceCaller)
.onSourceInfoRefreshed(
adsMediaSource, new SinglePeriodAdTimeline(CONTENT_TIMELINE, AD_PLAYBACK_STATE));
adsMediaSource,
new SinglePeriodAdTimeline(PLACEHOLDER_CONTENT_TIMELINE, AD_PLAYBACK_STATE));
}
@Test
public void createPeriod_preparesChildAdMediaSourceAndRefreshesSourceInfoWithAdMediaSourceInfo() {
public void
createPeriod_forPreroll_preparesChildAdMediaSourceAndRefreshesSourceInfoWithAdMediaSourceInfo() {
// This should be unused if we only create the preroll period.
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
adsMediaSource.createPeriod(
new MediaPeriodId(
@ -183,13 +191,12 @@ public final class AdsMediaSourceTest {
.onSourceInfoRefreshed(
adsMediaSource,
new SinglePeriodAdTimeline(
CONTENT_TIMELINE,
PLACEHOLDER_CONTENT_TIMELINE,
AD_PLAYBACK_STATE.withAdDurationsUs(new long[][] {{PREROLL_AD_DURATION_US}})));
}
@Test
public void createPeriod_createsChildPrerollAdMediaPeriod() {
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
public void createPeriod_forPreroll_createsChildPrerollAdMediaPeriod() {
adsMediaSource.createPeriod(
new MediaPeriodId(
CONTENT_PERIOD_UID,
@ -206,7 +213,7 @@ public final class AdsMediaSourceTest {
}
@Test
public void createPeriod_createsChildContentMediaPeriod() {
public void createPeriod_forContent_createsChildContentMediaPeriodAndLoadsContentTimeline() {
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
shadowOf(Looper.getMainLooper()).idle();
adsMediaSource.createPeriod(
@ -216,6 +223,12 @@ public final class AdsMediaSourceTest {
contentMediaSource.assertMediaPeriodCreated(
new MediaPeriodId(CONTENT_PERIOD_UID, /* windowSequenceNumber= */ 0));
ArgumentCaptor<Timeline> adsTimelineCaptor = ArgumentCaptor.forClass(Timeline.class);
verify(mockMediaSourceCaller, times(2))
.onSourceInfoRefreshed(eq(adsMediaSource), adsTimelineCaptor.capture());
TestUtil.timelinesAreSame(
adsTimelineCaptor.getValue(),
new SinglePeriodAdTimeline(CONTENT_TIMELINE, AD_PLAYBACK_STATE));
}
@Test