Skip to content

Commit

Permalink
Use MaskingMediaSource for AdsMediaSource content source
Browse files Browse the repository at this point in the history
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: #1358
PiperOrigin-RevId: 633640746
  • Loading branch information
tonihei authored and copybara-github committed May 14, 2024
1 parent bf7b4e0 commit d27c36a
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 10 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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;
Expand Down Expand Up @@ -134,7 +135,7 @@ public RuntimeException getRuntimeExceptionForUnexpected() {
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;
Expand Down Expand Up @@ -171,7 +172,8 @@ public AdsMediaSource(
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;
Expand Down Expand Up @@ -206,6 +208,7 @@ protected void prepareSourceInternal(@Nullable TransferListener mediaTransferLis
super.prepareSourceInternal(mediaTransferListener);
ComponentListener componentListener = new ComponentListener();
this.componentListener = componentListener;
contentTimeline = contentMediaSource.getTimeline();
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource);
mainHandler.post(
() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand All @@ -35,6 +36,7 @@
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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -147,7 +151,8 @@ public void setUp() {
}

@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(
Expand All @@ -162,11 +167,14 @@ public void createPeriod_preparesChildAdMediaSourceAndRefreshesSourceInfo() {
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(
Expand All @@ -183,13 +191,12 @@ public void createPeriod_preparesChildAdMediaSourceAndRefreshesSourceInfoWithAdM
.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,
Expand All @@ -206,7 +213,7 @@ public void createPeriod_createsChildPrerollAdMediaPeriod() {
}

@Test
public void createPeriod_createsChildContentMediaPeriod() {
public void createPeriod_forContent_createsChildContentMediaPeriodAndLoadsContentTimeline() {
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
shadowOf(Looper.getMainLooper()).idle();
adsMediaSource.createPeriod(
Expand All @@ -216,6 +223,12 @@ public void createPeriod_createsChildContentMediaPeriod() {

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
Expand Down

0 comments on commit d27c36a

Please sign in to comment.