From 05e23996fa1801efb4569211a1c47cac585d3d64 Mon Sep 17 00:00:00 2001 From: bachinger Date: Tue, 18 Jan 2022 14:16:05 +0000 Subject: [PATCH] Split ad playback state and recalculate window and period duriations This change enables the ImaServerSideAdInsertionMediaSource for multi-period content. The global ad playback state is split into pieces for each period and the window and period durations are calculated accordingly in the ServerSideAdInsertionTimeline. For multi-period live content (DASH), the ad playback state is not set with this change. This is deferred to a follow up CL. Splitting is very tricky. For each timeline update the windowStartTimeUs may vary for some milliseconds relative to the start of the period.positionInWindowUs. This requires to either introduce some fuzzy logic or to choose a different approach than for multi-period VOD. Because mistakes within the playback states of subsequent moving live windows produces crashes, it seems sensible to defer this for now and keep this change in a separate future CL (unblock further work, easy to rollback). In this state, live DASH stream are working and the ad overlay is placed over the player correctly bu the SDK. However, ads are not reported by the position discontinuity event. Similarly, the player.isPlayingAd() does never returns true when a ad period is playing. PiperOrigin-RevId: 422539770 --- .../ads/ServerSideAdInsertionMediaSource.java | 48 ++++++++++++------- .../source/ads/ServerSideAdInsertionUtil.java | 23 --------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java index 0251ee2621..248b7648ec 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java @@ -994,7 +994,6 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource public ServerSideAdInsertionTimeline( Timeline contentTimeline, ImmutableMap adPlaybackStates) { super(contentTimeline); - checkState(contentTimeline.getPeriodCount() == 1); checkState(contentTimeline.getWindowCount() == 1); Period period = new Period(); for (int i = 0; i < contentTimeline.getPeriodCount(); i++) { @@ -1008,25 +1007,23 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { super.getWindow(windowIndex, window, defaultPositionProjectionUs); Object firstPeriodUid = - checkNotNull(getPeriod(/* periodIndex= */ 0, new Period(), /* setIds= */ true).uid); - AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid)); + checkNotNull(getPeriod(window.firstPeriodIndex, new Period(), /* setIds= */ true).uid); + AdPlaybackState firstAdPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid)); long positionInPeriodUs = getMediaPeriodPositionUsForContent( window.positionInFirstPeriodUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, - adPlaybackState); + firstAdPlaybackState); if (window.durationUs == C.TIME_UNSET) { - if (adPlaybackState.contentDurationUs != C.TIME_UNSET) { - window.durationUs = adPlaybackState.contentDurationUs - positionInPeriodUs; + if (firstAdPlaybackState.contentDurationUs != C.TIME_UNSET) { + window.durationUs = firstAdPlaybackState.contentDurationUs - positionInPeriodUs; } } else { - long actualWindowEndPositionInPeriodUs = window.positionInFirstPeriodUs + window.durationUs; - long windowEndPositionInPeriodUs = - getMediaPeriodPositionUsForContent( - actualWindowEndPositionInPeriodUs, - /* nextAdGroupIndex= */ C.INDEX_UNSET, - adPlaybackState); - window.durationUs = windowEndPositionInPeriodUs - positionInPeriodUs; + Period lastPeriod = getPeriod(/* periodIndex= */ window.lastPeriodIndex, new Period()); + window.durationUs = + lastPeriod.durationUs == C.TIME_UNSET + ? C.TIME_UNSET + : lastPeriod.positionInWindowUs + lastPeriod.durationUs; } window.positionInFirstPeriodUs = positionInPeriodUs; return window; @@ -1044,11 +1041,26 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource getMediaPeriodPositionUsForContent( durationUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState); } - long positionInWindowUs = - -getMediaPeriodPositionUsForContent( - -period.getPositionInWindowUs(), - /* nextAdGroupIndex= */ C.INDEX_UNSET, - adPlaybackState); + long positionInWindowUs = 0; + Period innerPeriod = new Period(); + for (int i = 0; i < periodIndex + 1; i++) { + timeline.getPeriod(/* periodIndex= */ i, innerPeriod, /* setIds= */ true); + AdPlaybackState innerAdPlaybackState = checkNotNull(adPlaybackStates.get(innerPeriod.uid)); + if (i == 0) { + positionInWindowUs = + -getMediaPeriodPositionUsForContent( + -innerPeriod.getPositionInWindowUs(), + /* nextAdGroupIndex= */ C.INDEX_UNSET, + innerAdPlaybackState); + } + if (i != periodIndex) { + positionInWindowUs += + getMediaPeriodPositionUsForContent( + innerPeriod.durationUs, + /* nextAdGroupIndex= */ C.INDEX_UNSET, + innerAdPlaybackState); + } + } period.set( period.id, period.uid, diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionUtil.java index e1e97f7d93..052181ec33 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionUtil.java @@ -74,29 +74,6 @@ public final class ServerSideAdInsertionUtil { adPlaybackState, insertionIndex, adDurationUs, contentResumeOffsetUs); } - /** - * Returns the duration of the underlying server-side inserted ads stream for the current {@link - * Timeline.Period} in the {@link Player}. - * - * @param player The {@link Player}. - * @param adPlaybackState The {@link AdPlaybackState} defining the ad groups. - * @return The duration of the underlying server-side inserted ads stream, in microseconds, or - * {@link C#TIME_UNSET} if it can't be determined. - */ - public static long getStreamDurationUs(Player player, AdPlaybackState adPlaybackState) { - Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { - return C.TIME_UNSET; - } - Timeline.Period period = - timeline.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period()); - if (period.durationUs == C.TIME_UNSET) { - return C.TIME_UNSET; - } - return getStreamPositionUsForContent( - period.durationUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState); - } - /** * Returns the position in the underlying server-side inserted ads stream for the current playback * position in the {@link Player}.