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;