From 92c971ecd0ad4196e3ad48d1fd7f016d7e19eb3f Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 22 Nov 2021 19:28:40 +0000 Subject: [PATCH] Don't drop updates of the playing period for skipped SSI ads Before this change ExpPlayerImplInternal dropped a change of the playing period when a change in the timeline occurred that actually changed the playing period but we don't want to update the period queue. This logic also dropped the update of a skipped server side inserted preroll ad for which we want the periodQueue to 'seek' to the stream position after the preroll ad and trigger a SKIP discontinuity. This change now introduces an exception so that a skipped SSI ad is still causing an update in the period queue which leads to a 'seek' and a discontinuity of type SKIP. PiperOrigin-RevId: 411607299 --- .../google/android/exoplayer2/Timeline.java | 16 ++++++++ .../exoplayer2/ExoPlayerImplInternal.java | 40 +++++++++++++++---- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java index 8b6e734e68..cf77941844 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_UNAVAILABLE; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkState; import static java.lang.Math.max; @@ -815,6 +816,21 @@ public abstract class Timeline implements Bundleable { return adGroup.count != C.LENGTH_UNSET ? adGroup.durationsUs[adIndexInAdGroup] : C.TIME_UNSET; } + /** + * Returns the state of the ad at index {@code adIndexInAdGroup} in the ad group at {@code + * adGroupIndex}, or {@link AdPlaybackState#AD_STATE_UNAVAILABLE} if not yet known. + * + * @param adGroupIndex The ad group index. + * @return The state of the ad, or {@link AdPlaybackState#AD_STATE_UNAVAILABLE} if not yet + * known. + */ + public int getAdState(int adGroupIndex, int adIndexInAdGroup) { + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); + return adGroup.count != C.LENGTH_UNSET + ? adGroup.states[adIndexInAdGroup] + : AD_STATE_UNAVAILABLE; + } + /** * Returns the position offset in the first unplayed ad at which to begin playback, in * microseconds. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index ca1ae359f5..ae8063516a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -44,6 +44,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelector; @@ -2648,15 +2649,14 @@ import java.util.concurrent.atomic.AtomicBoolean; && earliestCuePointIsUnchangedOrLater; // Drop update if the change is from/to server-side inserted ads at the same content position to // avoid any unintentional renderer reset. - timeline.getPeriodByUid(newPeriodUid, period); boolean isInStreamAdChange = - sameOldAndNewPeriodUid - && !isUsingPlaceholderPeriod - && oldContentPositionUs == newContentPositionUs - && ((periodIdWithAds.isAd() - && period.isServerSideInsertedAdGroup(periodIdWithAds.adGroupIndex)) - || (oldPeriodId.isAd() - && period.isServerSideInsertedAdGroup(oldPeriodId.adGroupIndex))); + isIgnorableServerSideAdInsertionPeriodChange( + isUsingPlaceholderPeriod, + oldPeriodId, + oldContentPositionUs, + periodIdWithAds, + timeline.getPeriodByUid(newPeriodUid, period), + newContentPositionUs); MediaPeriodId newPeriodId = onlyNextAdGroupIndexIncreased || isInStreamAdChange ? oldPeriodId : periodIdWithAds; @@ -2682,6 +2682,30 @@ import java.util.concurrent.atomic.AtomicBoolean; setTargetLiveOffset); } + private static boolean isIgnorableServerSideAdInsertionPeriodChange( + boolean isUsingPlaceholderPeriod, + MediaPeriodId oldPeriodId, + long oldContentPositionUs, + MediaPeriodId newPeriodId, + Timeline.Period newPeriod, + long newContentPositionUs) { + if (isUsingPlaceholderPeriod + || oldContentPositionUs != newContentPositionUs + || !oldPeriodId.periodUid.equals(newPeriodId.periodUid)) { + // The period position changed. + return false; + } + if (oldPeriodId.isAd() && newPeriod.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) + != AdPlaybackState.AD_STATE_ERROR + && newPeriod.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); + } + private static boolean isUsingPlaceholderPeriod( PlaybackInfo playbackInfo, Timeline.Period period) { MediaPeriodId periodId = playbackInfo.periodId;