Skip to content
Merged
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 @@ -4099,12 +4099,13 @@ private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange(
// avoid any unintentional renderer reset.
boolean isInStreamAdChange =
isIgnorableServerSideAdInsertionPeriodChange(
timeline,
isUsingPlaceholderPeriod,
oldPeriodId,
oldContentPositionUs,
periodIdWithAds,
timeline.getPeriodByUid(newPeriodUid, period),
newContentPositionUs);
newContentPositionUs,
period);
MediaPeriodId newPeriodId =
onlyNextAdGroupIndexIncreased || isInStreamAdChange ? oldPeriodId : periodIdWithAds;

Expand Down Expand Up @@ -4178,27 +4179,29 @@ private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange(
}

private static boolean isIgnorableServerSideAdInsertionPeriodChange(
Timeline timeline,
boolean isUsingPlaceholderPeriod,
MediaPeriodId oldPeriodId,
long oldContentPositionUs,
MediaPeriodId newPeriodId,
Timeline.Period newPeriod,
long newContentPositionUs) {
long newContentPositionUs,
Timeline.Period period) {
if (isUsingPlaceholderPeriod
|| oldContentPositionUs != newContentPositionUs
|| !oldPeriodId.periodUid.equals(newPeriodId.periodUid)) {
// The period position changed.
return false;
}
if (oldPeriodId.isAd() && newPeriod.isServerSideInsertedAdGroup(oldPeriodId.adGroupIndex)) {
timeline.getPeriodByUid(newPeriodId.periodUid, period);
if (oldPeriodId.isAd() && period.isServerSideInsertedAdGroup(oldPeriodId.adGroupIndex)) {
// Whether the old period was a server side ad that doesn't need skipping to the content.
return newPeriod.getAdState(oldPeriodId.adGroupIndex, oldPeriodId.adIndexInAdGroup)
return period.getAdState(oldPeriodId.adGroupIndex, oldPeriodId.adIndexInAdGroup)
!= AdPlaybackState.AD_STATE_ERROR
&& newPeriod.getAdState(oldPeriodId.adGroupIndex, oldPeriodId.adIndexInAdGroup)
&& period.getAdState(oldPeriodId.adGroupIndex, oldPeriodId.adIndexInAdGroup)
!= AdPlaybackState.AD_STATE_SKIPPED;
}
// If the new period is a server side inserted ad, we can just continue playing.
return newPeriodId.isAd() && newPeriod.isServerSideInsertedAdGroup(newPeriodId.adGroupIndex);
return newPeriodId.isAd() && period.isServerSideInsertedAdGroup(newPeriodId.adGroupIndex);
}

private static boolean isUsingPlaceholderPeriod(
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