mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
bf7b4e0d8c
commit
d27c36ac9e
@ -30,6 +30,9 @@
|
|||||||
* Add new error code
|
* Add new error code
|
||||||
`PlaybackException.ERROR_CODE_DECODING_RESOURCES_RECLAIMED` that is used
|
`PlaybackException.ERROR_CODE_DECODING_RESOURCES_RECLAIMED` that is used
|
||||||
when codec resources are reclaimed for higher priority tasks.
|
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:
|
* Transformer:
|
||||||
* Work around a decoder bug where the number of audio channels was capped
|
* Work around a decoder bug where the number of audio channels was capped
|
||||||
at stereo when handling PCM input.
|
at stereo when handling PCM input.
|
||||||
|
@ -38,6 +38,7 @@ import androidx.media3.datasource.TransferListener;
|
|||||||
import androidx.media3.exoplayer.source.CompositeMediaSource;
|
import androidx.media3.exoplayer.source.CompositeMediaSource;
|
||||||
import androidx.media3.exoplayer.source.LoadEventInfo;
|
import androidx.media3.exoplayer.source.LoadEventInfo;
|
||||||
import androidx.media3.exoplayer.source.MaskingMediaPeriod;
|
import androidx.media3.exoplayer.source.MaskingMediaPeriod;
|
||||||
|
import androidx.media3.exoplayer.source.MaskingMediaSource;
|
||||||
import androidx.media3.exoplayer.source.MediaLoadData;
|
import androidx.media3.exoplayer.source.MediaLoadData;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
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 =
|
private static final MediaPeriodId CHILD_SOURCE_MEDIA_PERIOD_ID =
|
||||||
new MediaPeriodId(/* periodUid= */ new Object());
|
new MediaPeriodId(/* periodUid= */ new Object());
|
||||||
|
|
||||||
private final MediaSource contentMediaSource;
|
private final MaskingMediaSource contentMediaSource;
|
||||||
@Nullable final MediaItem.DrmConfiguration contentDrmConfiguration;
|
@Nullable final MediaItem.DrmConfiguration contentDrmConfiguration;
|
||||||
private final MediaSource.Factory adMediaSourceFactory;
|
private final MediaSource.Factory adMediaSourceFactory;
|
||||||
private final AdsLoader adsLoader;
|
private final AdsLoader adsLoader;
|
||||||
@ -171,7 +172,8 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
MediaSource.Factory adMediaSourceFactory,
|
MediaSource.Factory adMediaSourceFactory,
|
||||||
AdsLoader adsLoader,
|
AdsLoader adsLoader,
|
||||||
AdViewProvider adViewProvider) {
|
AdViewProvider adViewProvider) {
|
||||||
this.contentMediaSource = contentMediaSource;
|
this.contentMediaSource =
|
||||||
|
new MaskingMediaSource(contentMediaSource, /* useLazyPreparation= */ true);
|
||||||
this.contentDrmConfiguration =
|
this.contentDrmConfiguration =
|
||||||
checkNotNull(contentMediaSource.getMediaItem().localConfiguration).drmConfiguration;
|
checkNotNull(contentMediaSource.getMediaItem().localConfiguration).drmConfiguration;
|
||||||
this.adMediaSourceFactory = adMediaSourceFactory;
|
this.adMediaSourceFactory = adMediaSourceFactory;
|
||||||
@ -206,6 +208,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
super.prepareSourceInternal(mediaTransferListener);
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
ComponentListener componentListener = new ComponentListener();
|
ComponentListener componentListener = new ComponentListener();
|
||||||
this.componentListener = componentListener;
|
this.componentListener = componentListener;
|
||||||
|
contentTimeline = contentMediaSource.getTimeline();
|
||||||
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource);
|
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource);
|
||||||
mainHandler.post(
|
mainHandler.post(
|
||||||
() ->
|
() ->
|
||||||
|
@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
@ -35,6 +36,7 @@ import androidx.media3.common.Timeline;
|
|||||||
import androidx.media3.datasource.DataSpec;
|
import androidx.media3.datasource.DataSpec;
|
||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||||
|
import androidx.media3.exoplayer.source.MaskingMediaSource;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||||
@ -81,7 +83,9 @@ public final class AdsMediaSourceTest {
|
|||||||
/* isDynamic= */ false,
|
/* isDynamic= */ false,
|
||||||
/* useLiveConfiguration= */ false,
|
/* useLiveConfiguration= */ false,
|
||||||
/* manifest= */ null,
|
/* 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 =
|
private static final Object CONTENT_PERIOD_UID =
|
||||||
CONTENT_TIMELINE.getUidOfPeriod(/* periodIndex= */ 0);
|
CONTENT_TIMELINE.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
|
|
||||||
@ -147,7 +151,8 @@ public final class AdsMediaSourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
new MediaPeriodId(
|
new MediaPeriodId(
|
||||||
@ -162,11 +167,14 @@ public final class AdsMediaSourceTest {
|
|||||||
assertThat(prerollAdMediaSource.isPrepared()).isTrue();
|
assertThat(prerollAdMediaSource.isPrepared()).isTrue();
|
||||||
verify(mockMediaSourceCaller)
|
verify(mockMediaSourceCaller)
|
||||||
.onSourceInfoRefreshed(
|
.onSourceInfoRefreshed(
|
||||||
adsMediaSource, new SinglePeriodAdTimeline(CONTENT_TIMELINE, AD_PLAYBACK_STATE));
|
adsMediaSource,
|
||||||
|
new SinglePeriodAdTimeline(PLACEHOLDER_CONTENT_TIMELINE, AD_PLAYBACK_STATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
new MediaPeriodId(
|
new MediaPeriodId(
|
||||||
@ -183,13 +191,12 @@ public final class AdsMediaSourceTest {
|
|||||||
.onSourceInfoRefreshed(
|
.onSourceInfoRefreshed(
|
||||||
adsMediaSource,
|
adsMediaSource,
|
||||||
new SinglePeriodAdTimeline(
|
new SinglePeriodAdTimeline(
|
||||||
CONTENT_TIMELINE,
|
PLACEHOLDER_CONTENT_TIMELINE,
|
||||||
AD_PLAYBACK_STATE.withAdDurationsUs(new long[][] {{PREROLL_AD_DURATION_US}})));
|
AD_PLAYBACK_STATE.withAdDurationsUs(new long[][] {{PREROLL_AD_DURATION_US}})));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createPeriod_createsChildPrerollAdMediaPeriod() {
|
public void createPeriod_forPreroll_createsChildPrerollAdMediaPeriod() {
|
||||||
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
new MediaPeriodId(
|
new MediaPeriodId(
|
||||||
CONTENT_PERIOD_UID,
|
CONTENT_PERIOD_UID,
|
||||||
@ -206,7 +213,7 @@ public final class AdsMediaSourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createPeriod_createsChildContentMediaPeriod() {
|
public void createPeriod_forContent_createsChildContentMediaPeriodAndLoadsContentTimeline() {
|
||||||
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
shadowOf(Looper.getMainLooper()).idle();
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
@ -216,6 +223,12 @@ public final class AdsMediaSourceTest {
|
|||||||
|
|
||||||
contentMediaSource.assertMediaPeriodCreated(
|
contentMediaSource.assertMediaPeriodCreated(
|
||||||
new MediaPeriodId(CONTENT_PERIOD_UID, /* windowSequenceNumber= */ 0));
|
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
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user