From 7ba79cfa4259de84de1b6900f2ffd81212573a3d 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/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionMediaSource.java index c663b1aa3d..7d5ddd2c55 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionMediaSource.java @@ -991,7 +991,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++) { @@ -1005,25 +1004,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; @@ -1041,11 +1038,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/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionUtil.java index d7609848af..59ccb5c90f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionUtil.java @@ -71,29 +71,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}.