Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
* Fix an issue where Player.getCurrentPosition() could return stale values
(updating only a few times per second) when dynamic scheduling is
enabled ([#3286](https://github.com/androidx/media/issues/3286)).
* Fix `ArrayIndexOutOfBoundsException` when a live timeline refresh moves
the default position past a server-side inserted ad that is currently
being played ([#3348](https://github.com/androidx/media/issues/3348)).
* CompositionPlayer:
* Support configuring the frame rate of video frame aggregation via
`Composition.Builder.setVideoFrameAggregationParameters` for playback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4103,7 +4103,7 @@ private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange(
oldPeriodId,
oldContentPositionUs,
periodIdWithAds,
timeline.getPeriodByUid(newPeriodUid, period),
timeline.getPeriodByUid(periodIdWithAds.periodUid, period),
newContentPositionUs);
MediaPeriodId newPeriodId =
onlyNextAdGroupIndexIncreased || isInStreamAdChange ? oldPeriodId : periodIdWithAds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,66 @@ public void adInMovingLiveWindow_keepsContentPosition() throws Exception {
assertThat(contentPositionAfterLiveWindowUpdateMs).isEqualTo(2000);
}

@Test
public void timelineRefresh_movingLiveDefaultPositionPastPlayingSsaiAd_keepsPlayingAd()
throws Exception {
// Live window with three 20s periods: |- p0 content -|- p1 ad -|- p2 content -|.
Object adsId = new Object();
TimelineWindowDefinition liveWindowDefinition =
new TimelineWindowDefinition.Builder()
.setDynamic(true)
.setLive(true)
.setSeekable(true)
.setPeriodCount(3)
.setDurationUs(60_000_000)
.setWindowStartTimeUs(1_720_000_000_000_000L)
.setWindowPositionInFirstPeriodUs(0)
.setDefaultPositionUs(30_000_000)
.build();
Timeline initialContentTimeline = new FakeTimeline(liveWindowDefinition);
// p1 is entirely covered by a server-side inserted ad.
AdPlaybackState contentOnlyAdPlaybackState = new AdPlaybackState(adsId);
AdPlaybackState adPeriodAdPlaybackState =
addAdGroupToAdPlaybackState(
contentOnlyAdPlaybackState,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 20_000_000,
/* adDurationsUs...= */ 20_000_000);
// ServerSideAdInsertionMediaSource requires an AdPlaybackState for every period.
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImmutableMap.of(
initialContentTimeline.getUidOfPeriod(/* periodIndex= */ 0),
contentOnlyAdPlaybackState,
initialContentTimeline.getUidOfPeriod(/* periodIndex= */ 1),
adPeriodAdPlaybackState,
initialContentTimeline.getUidOfPeriod(/* periodIndex= */ 2),
contentOnlyAdPlaybackState);
FakeMediaSource contentMediaSource = new FakeMediaSource(initialContentTimeline);
ServerSideAdInsertionMediaSource mediaSource =
new ServerSideAdInsertionMediaSource(
contentMediaSource, /* adPlaybackStateUpdater= */ contentTimeline -> false);
mediaSource.setAdPlaybackStates(adPlaybackStates, initialContentTimeline);
ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build();

// Join the live stream while the ad in p1 is on air.
player.setMediaSource(mediaSource);
player.prepare();
advance(player).untilState(Player.STATE_READY);
boolean isPlayingAdAfterJoining = player.isPlayingAd();
// Refresh the live timeline with a default position that moved past the ad, into p2.
contentMediaSource.setNewSourceInfo(
new FakeTimeline(
liveWindowDefinition.buildUpon().setDefaultPositionUs(45_000_000).build()));
advance(player).untilPendingCommandsAreFullyHandled();
boolean isPlayingAdAfterRefresh = player.isPlayingAd();
@Nullable PlaybackException error = player.getPlayerError();
player.release();

assertThat(isPlayingAdAfterJoining).isTrue();
assertThat(error).isNull();
assertThat(isPlayingAdAfterRefresh).isTrue();
}

@Test
public void addMediaSource_whilePlayingAd_correctMasking() throws Exception {
long contentDurationMs = 10_000;
Expand Down