use of androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource in project media by androidx.
the class ServerSideAdInsertionMediaSourceTest method timeline_missingAdPlaybackStateByPeriodUid_isAssertedAndThrows.
@Test
public void timeline_missingAdPlaybackStateByPeriodUid_isAssertedAndThrows() {
ServerSideAdInsertionMediaSource mediaSource = new ServerSideAdInsertionMediaSource(new FakeMediaSource(), /* adPlaybackStateUpdater= */
null);
// The map of adPlaybackStates does not contain a valid period UID as key.
mediaSource.setAdPlaybackStates(ImmutableMap.of(new Object(), new AdPlaybackState(/* adsId= */
new Object())));
Assert.assertThrows(IllegalStateException.class, () -> mediaSource.prepareSource((source, timeline) -> {
/* Do nothing. */
}, /* mediaTransferListener= */
null, PlayerId.UNSET));
}
use of androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource in project media by androidx.
the class ServerSideAdInsertionMediaSourceTest method playbackWithAdditionalAdsInAdGroup_playsSuccessfulWithoutRendererResets.
@Test
public void playbackWithAdditionalAdsInAdGroup_playsSuccessfulWithoutRendererResets() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
AtomicReference<Object> periodUid = new AtomicReference<>();
CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context);
ExoPlayer player = new ExoPlayer.Builder(context, renderersFactory).setClock(new FakeClock(/* isAutoAdvancing= */
true)).build();
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */
1)));
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
AdPlaybackState firstAdPlaybackState = addAdGroupToAdPlaybackState(new AdPlaybackState(/* adsId= */
new Object()), /* fromPositionUs= */
0, /* contentResumeOffsetUs= */
0, /* adDurationsUs...= */
500_000);
AtomicReference<ServerSideAdInsertionMediaSource> mediaSourceRef = new AtomicReference<>();
mediaSourceRef.set(new ServerSideAdInsertionMediaSource(new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)), /* adPlaybackStateUpdater= */
contentTimeline -> {
if (periodUid.get() == null) {
periodUid.set(checkNotNull(contentTimeline.getPeriod(/* periodIndex= */
0, new Timeline.Period(), /* setIds= */
true).uid));
mediaSourceRef.get().setAdPlaybackStates(ImmutableMap.of(periodUid.get(), firstAdPlaybackState));
}
return true;
}));
AnalyticsListener listener = mock(AnalyticsListener.class);
player.addAnalyticsListener(listener);
player.setMediaSource(mediaSourceRef.get());
player.prepare();
// Wait until playback is ready with first ad and then replace by 3 ads.
runUntilPlaybackState(player, Player.STATE_READY);
AdPlaybackState secondAdPlaybackState = firstAdPlaybackState.withAdCount(/* adGroupIndex= */
0, /* adCount= */
3).withAdDurationsUs(/* adGroupIndex= */
0, /* adDurationsUs...= */
50_000, 250_000, 200_000);
mediaSourceRef.get().setAdPlaybackStates(ImmutableMap.of(periodUid.get(), secondAdPlaybackState));
runUntilPendingCommandsAreFullyHandled(player);
player.play();
runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
// Assert all samples have been played.
DumpFileAsserts.assertOutput(context, playbackOutput, TEST_ASSET_DUMP);
// Assert playback has been reported with ads: [ad0][ad1][ad2][content]
// 4*2(audio+video) format changes, 3 discontinuities between parts.
verify(listener, times(3)).onPositionDiscontinuity(any(), any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION));
verify(listener, times(8)).onDownstreamFormatChanged(any(), any());
// Assert renderers played through without reset (=decoders have been enabled only once).
verify(listener).onVideoEnabled(any(), any());
verify(listener).onAudioEnabled(any(), any());
// Assert playback progression was smooth (=no unexpected delays that cause audio to underrun)
verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong());
}
use of androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource in project media by androidx.
the class ExoPlayerTest method seekTo_beforeSSAIMidRolls_requestedContentPositionNotPropagatedIntoAds.
@Test
public void seekTo_beforeSSAIMidRolls_requestedContentPositionNotPropagatedIntoAds() throws Exception {
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor = ArgumentCaptor.forClass(PositionInfo.class);
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor = ArgumentCaptor.forClass(PositionInfo.class);
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
FakeTimeline adTimeline = FakeTimeline.createMultiPeriodAdTimeline("windowId", /* numberOfPlayedAds= */
0, /* isAdPeriodFlags...= */
false, true, true, false);
Listener listener = mock(Listener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(listener);
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
sourceReference.set(new ServerSideAdInsertionMediaSource(new FakeMediaSource(adTimeline), contentTimeline -> {
sourceReference.get().setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */
0));
return true;
}));
player.setMediaSource(sourceReference.get());
player.pause();
player.prepare();
runUntilPlaybackState(player, Player.STATE_READY);
player.play();
player.seekTo(1600);
runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
verify(listener, times(6)).onPositionDiscontinuity(oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture());
assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(1, 0, 0, 0, 0, 0).inOrder();
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
// seek discontinuity
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
assertThat(newPositions.get(0).periodIndex).isEqualTo(0);
assertThat(newPositions.get(0).positionMs).isEqualTo(1600);
assertThat(newPositions.get(0).contentPositionMs).isEqualTo(1600);
// auto discontinuities through ads has correct content position that is not the seek position.
assertThat(newPositions.get(1).periodIndex).isEqualTo(1);
assertThat(newPositions.get(1).adGroupIndex).isEqualTo(0);
assertThat(newPositions.get(1).adIndexInAdGroup).isEqualTo(0);
assertThat(newPositions.get(1).positionMs).isEqualTo(0);
assertThat(newPositions.get(1).contentPositionMs).isEqualTo(2500);
assertThat(newPositions.get(2).contentPositionMs).isEqualTo(2500);
assertThat(newPositions.get(3).contentPositionMs).isEqualTo(2500);
assertThat(newPositions.get(4).contentPositionMs).isEqualTo(2500);
// Content resumes at expected position that is not the seek position.
assertThat(newPositions.get(5).periodIndex).isEqualTo(3);
assertThat(newPositions.get(5).adGroupIndex).isEqualTo(-1);
assertThat(newPositions.get(5).positionMs).isEqualTo(2500);
assertThat(newPositions.get(5).contentPositionMs).isEqualTo(2500);
}
use of androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource in project media by androidx.
the class ExoPlayerTest method play_playedSSAIPreMidPostRollsSinglePeriodWindow_noDiscontinuities.
@Test
public void play_playedSSAIPreMidPostRollsSinglePeriodWindow_noDiscontinuities() throws Exception {
AdPlaybackState adPlaybackState = addAdGroupToAdPlaybackState(new AdPlaybackState("adsId"), /* fromPositionUs= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, /* contentResumeOffsetUs= */
0, /* adDurationsUs...= */
C.MICROS_PER_SECOND);
adPlaybackState = addAdGroupToAdPlaybackState(adPlaybackState, /* fromPositionUs= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (3 * C.MICROS_PER_SECOND), /* contentResumeOffsetUs= */
0, /* adDurationsUs...= */
C.MICROS_PER_SECOND);
adPlaybackState = addAdGroupToAdPlaybackState(adPlaybackState, /* fromPositionUs= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (5 * C.MICROS_PER_SECOND), /* contentResumeOffsetUs= */
0, /* adDurationsUs...= */
C.MICROS_PER_SECOND);
adPlaybackState = addAdGroupToAdPlaybackState(adPlaybackState, /* fromPositionUs= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (9 * C.MICROS_PER_SECOND), /* contentResumeOffsetUs= */
0, /* adDurationsUs...= */
C.MICROS_PER_SECOND);
adPlaybackState = adPlaybackState.withPlayedAd(/* adGroupIndex= */
0, /* adIndexInAdGroup+ */
0);
adPlaybackState = adPlaybackState.withPlayedAd(/* adGroupIndex= */
1, /* adIndexInAdGroup+ */
0);
adPlaybackState = adPlaybackState.withPlayedAd(/* adGroupIndex= */
2, /* adIndexInAdGroup+ */
0);
adPlaybackState = adPlaybackState.withPlayedAd(/* adGroupIndex= */
3, /* adIndexInAdGroup+ */
0);
FakeTimeline adTimeline = new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */
1, "windowId", /* isSeekable= */
true, /* isDynamic= */
false, /* isLive= */
false, /* isPlaceholder= */
false, /* durationUs= */
DEFAULT_WINDOW_DURATION_US, /* defaultPositionUs= */
0, /* windowOffsetInFirstPeriodUs= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, /* adPlaybackStates= */
ImmutableList.of(adPlaybackState), MediaItem.EMPTY));
Listener listener = mock(Listener.class);
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.addListener(listener);
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
sourceReference.set(new ServerSideAdInsertionMediaSource(new FakeMediaSource(adTimeline, ExoPlayerTestRunner.AUDIO_FORMAT), contentTimeline -> {
sourceReference.get().setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */
0));
return true;
}));
player.setMediaSource(sourceReference.get());
player.prepare();
player.play();
runUntilPlaybackState(player, Player.STATE_ENDED);
long finalPositionMs = player.getCurrentPosition();
player.release();
assertThat(finalPositionMs).isEqualTo(6000);
verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt());
ArgumentCaptor<Integer> playbackStateCaptor = ArgumentCaptor.forClass(Integer.class);
verify(listener, times(3)).onPlaybackStateChanged(playbackStateCaptor.capture());
assertThat(playbackStateCaptor.getAllValues()).containsExactly(2, 3, 4).inOrder();
}
use of androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource in project media by androidx.
the class ImaServerSideAdInsertionMediaSource method setContentUri.
// Internal methods (called on the playback thread).
private void setContentUri(Uri contentUri) {
if (serverSideAdInsertionMediaSource != null) {
return;
}
MediaItem contentMediaItem = new MediaItem.Builder().setUri(contentUri).setDrmConfiguration(checkNotNull(mediaItem.localConfiguration).drmConfiguration).setLiveConfiguration(mediaItem.liveConfiguration).setCustomCacheKey(mediaItem.localConfiguration.customCacheKey).setStreamKeys(mediaItem.localConfiguration.streamKeys).build();
ServerSideAdInsertionMediaSource serverSideAdInsertionMediaSource = new ServerSideAdInsertionMediaSource(contentMediaSourceFactory.createMediaSource(contentMediaItem), componentListener);
this.serverSideAdInsertionMediaSource = serverSideAdInsertionMediaSource;
if (isLiveStream) {
AdPlaybackState liveAdPlaybackState = new AdPlaybackState(adsId).withNewAdGroup(/* adGroupIndex= */
0, /* adGroupTimeUs= */
C.TIME_END_OF_SOURCE).withIsServerSideInserted(/* adGroupIndex= */
0, true);
mainHandler.post(() -> setAdPlaybackState(liveAdPlaybackState));
}
prepareChildSource(/* id= */
null, serverSideAdInsertionMediaSource);
}
Aggregations