diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index 6690fa95ec..4f5affa77a 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -602,17 +602,16 @@ import java.util.Map; } // Skip ads based on the start position as required. - long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs; int adGroupForPositionIndex = adPlaybackState.getAdGroupIndexForPositionUs( C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); if (adGroupForPositionIndex != C.INDEX_UNSET) { boolean playAdWhenStartingPlayback = - configuration.playAdBeforeStartPosition - || adGroupTimesUs[adGroupForPositionIndex] == C.msToUs(contentPositionMs); + adPlaybackState.getAdGroup(adGroupForPositionIndex).timeUs == C.msToUs(contentPositionMs) + || configuration.playAdBeforeStartPosition; if (!playAdWhenStartingPlayback) { adGroupForPositionIndex++; - } else if (hasMidrollAdGroups(adGroupTimesUs)) { + } else if (hasMidrollAdGroups(adPlaybackState)) { // Provide the player's initial position to trigger loading and playing the ad. If there are // no midrolls, we are playing a preroll and any pending content position wouldn't be // cleared. @@ -622,13 +621,14 @@ import java.util.Map; for (int i = 0; i < adGroupForPositionIndex; i++) { adPlaybackState = adPlaybackState.withSkippedAdGroup(i); } - if (adGroupForPositionIndex == adGroupTimesUs.length) { + if (adGroupForPositionIndex == adPlaybackState.adGroupCount) { // We don't need to play any ads. Because setPlayAdsAfterTime does not discard non-VMAP // ads, we signal that no ads will render so the caller can destroy the ads manager. return null; } - long adGroupForPositionTimeUs = adGroupTimesUs[adGroupForPositionIndex]; - long adGroupBeforePositionTimeUs = adGroupTimesUs[adGroupForPositionIndex - 1]; + long adGroupForPositionTimeUs = adPlaybackState.getAdGroup(adGroupForPositionIndex).timeUs; + long adGroupBeforePositionTimeUs = + adPlaybackState.getAdGroup(adGroupForPositionIndex - 1).timeUs; if (adGroupForPositionTimeUs == C.TIME_END_OF_SOURCE) { // Play the postroll by offsetting the start position just past the last non-postroll ad. adsRenderingSettings.setPlayAdsAfterTime( @@ -786,14 +786,14 @@ import java.util.Map; if (adGroupIndex == C.INDEX_UNSET) { return false; } - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); if (adGroup.count != C.LENGTH_UNSET && adGroup.count != 0 && adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) { // An ad is available already. return false; } - long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); + long adGroupTimeMs = C.usToMs(adGroup.timeUs); long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); long timeUntilAdMs = adGroupTimeMs - contentPositionMs; return timeUntilAdMs < configuration.adPreloadTimeoutMs; @@ -877,13 +877,13 @@ import java.util.Map; } } if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { - int adGroupIndex = player.getCurrentAdGroupIndex(); - if (adPlaybackState.adGroupTimesUs[adGroupIndex] == C.TIME_END_OF_SOURCE) { + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(player.getCurrentAdGroupIndex()); + if (adGroup.timeUs == C.TIME_END_OF_SOURCE) { sendContentComplete(); } else { // IMA hasn't called playAd yet, so fake the content position. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); + fakeContentProgressOffsetMs = C.usToMs(adGroup.timeUs); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { fakeContentProgressOffsetMs = contentDurationMs; } @@ -919,11 +919,11 @@ import java.util.Map; // The ad count may increase on successive loads of ads in the same ad pod, for example, due to // separate requests for ad tags with multiple ads within the ad pod completing after an earlier // ad has loaded. See also https://github.com/google/ExoPlayer/issues/7477. - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adInfo.adGroupIndex]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adInfo.adGroupIndex); adPlaybackState = adPlaybackState.withAdCount( adInfo.adGroupIndex, max(adPodInfo.getTotalAds(), adGroup.states.length)); - adGroup = adPlaybackState.adGroups[adInfo.adGroupIndex]; + adGroup = adPlaybackState.getAdGroup(adInfo.adGroupIndex); for (int i = 0; i < adIndexInAdGroup; i++) { // Any preceding ads that haven't loaded are not going to load. if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) { @@ -1062,10 +1062,10 @@ import java.util.Map; private void markAdGroupInErrorStateAndClearPendingContentPosition(int adGroupIndex) { // Update the ad playback state so all ads in the ad group are in the error state. - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); if (adGroup.count == C.LENGTH_UNSET) { adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, max(1, adGroup.states.length)); - adGroup = adPlaybackState.adGroups[adGroupIndex]; + adGroup = adPlaybackState.getAdGroup(adGroupIndex); } for (int i = 0; i < adGroup.count; i++) { if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) { @@ -1094,7 +1094,7 @@ import java.util.Map; // Send IMA a content position at the ad group so that it will try to play it, at which point // we can notify that it failed to load. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); + fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.getAdGroup(adGroupIndex).timeUs); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { fakeContentProgressOffsetMs = contentDurationMs; } @@ -1109,7 +1109,7 @@ import java.util.Map; adCallbacks.get(i).onEnded(adMediaInfo); } } - playingAdIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); + playingAdIndexInAdGroup = adPlaybackState.getAdGroup(adGroupIndex).getFirstAdIndexToPlay(); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onError(checkNotNull(adMediaInfo)); } @@ -1138,7 +1138,7 @@ import java.util.Map; Log.d(TAG, "adsLoader.contentComplete"); } for (int i = 0; i < adPlaybackState.adGroupCount; i++) { - if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) { + if (adPlaybackState.getAdGroup(i).timeUs != C.TIME_END_OF_SOURCE) { adPlaybackState = adPlaybackState.withSkippedAdGroup(/* adGroupIndex= */ i); } } @@ -1213,7 +1213,7 @@ import java.util.Map; float cuePointTimeSecondsFloat = (float) cuePointTimeSeconds; long adPodTimeUs = Math.round((double) cuePointTimeSecondsFloat * C.MICROS_PER_SECOND); for (int adGroupIndex = 0; adGroupIndex < adPlaybackState.adGroupCount; adGroupIndex++) { - long adGroupTimeUs = adPlaybackState.adGroupTimesUs[adGroupIndex]; + long adGroupTimeUs = adPlaybackState.getAdGroup(adGroupIndex).timeUs; if (adGroupTimeUs != C.TIME_END_OF_SOURCE && Math.abs(adGroupTimeUs - adPodTimeUs) < THRESHOLD_AD_MATCH_US) { return adGroupIndex; @@ -1242,14 +1242,16 @@ import java.util.Map; } } - private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { - int count = adGroupTimesUs.length; + private static boolean hasMidrollAdGroups(AdPlaybackState adPlaybackState) { + int count = adPlaybackState.adGroupCount; if (count == 1) { - return adGroupTimesUs[0] != 0 && adGroupTimesUs[0] != C.TIME_END_OF_SOURCE; + long adGroupTimeUs = adPlaybackState.getAdGroup(0).timeUs; + return adGroupTimeUs != 0 && adGroupTimeUs != C.TIME_END_OF_SOURCE; } else if (count == 2) { - return adGroupTimesUs[0] != 0 || adGroupTimesUs[1] != C.TIME_END_OF_SOURCE; + return adPlaybackState.getAdGroup(0).timeUs != 0 + || adPlaybackState.getAdGroup(1).timeUs != C.TIME_END_OF_SOURCE; } else { - // There's at least one midroll ad group, as adGroupTimesUs is never empty. + // There's at least one midroll ad group, as adPlaybackState is never empty. return true; } } diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java index bc668f088e..ceec291278 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java @@ -1413,7 +1413,8 @@ public final class ImaAdsLoaderTest { public void onAdPlaybackState(AdPlaybackState adPlaybackState) { long[][] adDurationsUs = new long[adPlaybackState.adGroupCount][]; for (int adGroupIndex = 0; adGroupIndex < adPlaybackState.adGroupCount; adGroupIndex++) { - adDurationsUs[adGroupIndex] = new long[adPlaybackState.adGroups[adGroupIndex].uris.length]; + adDurationsUs[adGroupIndex] = + new long[adPlaybackState.getAdGroup(adGroupIndex).uris.length]; Arrays.fill(adDurationsUs[adGroupIndex], TEST_AD_DURATION_US); } adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs); 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 b8a2d2aae0..80c1de5190 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 @@ -708,7 +708,7 @@ public abstract class Timeline implements Bundleable { * Period}, in microseconds, or {@link C#TIME_END_OF_SOURCE} for a post-roll ad group. */ public long getAdGroupTimeUs(int adGroupIndex) { - return adPlaybackState.adGroupTimesUs[adGroupIndex]; + return adPlaybackState.getAdGroup(adGroupIndex).timeUs; } /** @@ -720,7 +720,7 @@ public abstract class Timeline implements Bundleable { * if no ads should be played. */ public int getFirstAdIndexToPlay(int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); + return adPlaybackState.getAdGroup(adGroupIndex).getFirstAdIndexToPlay(); } /** @@ -734,7 +734,7 @@ public abstract class Timeline implements Bundleable { * if the ad group does not have any ads remaining to play. */ public int getNextAdIndexToPlay(int adGroupIndex, int lastPlayedAdIndex) { - return adPlaybackState.adGroups[adGroupIndex].getNextAdIndexToPlay(lastPlayedAdIndex); + return adPlaybackState.getAdGroup(adGroupIndex).getNextAdIndexToPlay(lastPlayedAdIndex); } /** @@ -746,7 +746,7 @@ public abstract class Timeline implements Bundleable { * skipped or failed. */ public boolean hasPlayedAdGroup(int adGroupIndex) { - return !adPlaybackState.adGroups[adGroupIndex].hasUnplayedAds(); + return !adPlaybackState.getAdGroup(adGroupIndex).hasUnplayedAds(); } /** @@ -782,7 +782,7 @@ public abstract class Timeline implements Bundleable { * @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known. */ public int getAdCountInAdGroup(int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].count; + return adPlaybackState.getAdGroup(adGroupIndex).count; } /** @@ -794,7 +794,7 @@ public abstract class Timeline implements Bundleable { * @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known. */ public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) { - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); return adGroup.count != C.LENGTH_UNSET ? adGroup.durationsUs[adIndexInAdGroup] : C.TIME_UNSET; } @@ -814,7 +814,7 @@ public abstract class Timeline implements Bundleable { * @return Whether this ad group is server-side inserted and part of the content stream. */ public boolean isServerSideInsertedAdGroup(int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].isServerSideInserted; + return adPlaybackState.getAdGroup(adGroupIndex).isServerSideInserted; } /** @@ -825,7 +825,7 @@ public abstract class Timeline implements Bundleable { * @return The offset that should be added to the content stream, in microseconds. */ public long getContentResumeOffsetUs(int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].contentResumeOffsetUs; + return adPlaybackState.getAdGroup(adGroupIndex).contentResumeOffsetUs; } @Override diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index c6ad0dddf2..f6cb40075a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -49,6 +49,11 @@ public final class AdPlaybackState implements Bundleable { */ public static final class AdGroup implements Bundleable { + /** + * The time of the ad group in the {@link com.google.android.exoplayer2.Timeline.Period}, in + * microseconds, or {@link C#TIME_END_OF_SOURCE} to indicate a postroll ad. + */ + public final long timeUs; /** The number of ads in the ad group, or {@link C#LENGTH_UNSET} if unknown. */ public final int count; /** The URI of each ad in the ad group. */ @@ -65,9 +70,16 @@ public final class AdPlaybackState implements Bundleable { /** Whether this ad group is server-side inserted and part of the content stream. */ public final boolean isServerSideInserted; - /** Creates a new ad group with an unspecified number of ads. */ - public AdGroup() { + /** + * Creates a new ad group with an unspecified number of ads. + * + * @param timeUs The time of the ad group in the {@link + * com.google.android.exoplayer2.Timeline.Period}, in microseconds, or {@link + * C#TIME_END_OF_SOURCE} to indicate a postroll ad. + */ + public AdGroup(long timeUs) { this( + timeUs, /* count= */ C.LENGTH_UNSET, /* states= */ new int[0], /* uris= */ new Uri[0], @@ -77,6 +89,7 @@ public final class AdPlaybackState implements Bundleable { } private AdGroup( + long timeUs, int count, @AdState int[] states, @NullableType Uri[] uris, @@ -84,6 +97,7 @@ public final class AdPlaybackState implements Bundleable { long contentResumeOffsetUs, boolean isServerSideInserted) { checkArgument(states.length == uris.length); + this.timeUs = timeUs; this.count = count; this.states = states; this.uris = uris; @@ -146,7 +160,8 @@ public final class AdPlaybackState implements Bundleable { return false; } AdGroup adGroup = (AdGroup) o; - return count == adGroup.count + return timeUs == adGroup.timeUs + && count == adGroup.count && Arrays.equals(uris, adGroup.uris) && Arrays.equals(states, adGroup.states) && Arrays.equals(durationsUs, adGroup.durationsUs) @@ -157,6 +172,7 @@ public final class AdPlaybackState implements Bundleable { @Override public int hashCode() { int result = count; + result = 31 * result + (int) (timeUs ^ (timeUs >>> 32)); result = 31 * result + Arrays.hashCode(uris); result = 31 * result + Arrays.hashCode(states); result = 31 * result + Arrays.hashCode(durationsUs); @@ -165,6 +181,13 @@ public final class AdPlaybackState implements Bundleable { return result; } + /** Returns a new instance with the {@link #timeUs} set to the specified value. */ + @CheckResult + public AdGroup withTimeUs(long timeUs) { + return new AdGroup( + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + } + /** Returns a new instance with the ad count set to {@code count}. */ @CheckResult public AdGroup withAdCount(int count) { @@ -172,7 +195,7 @@ public final class AdPlaybackState implements Bundleable { long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count); @NullableType Uri[] uris = Arrays.copyOf(this.uris, count); return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } /** @@ -190,7 +213,7 @@ public final class AdPlaybackState implements Bundleable { uris[index] = uri; states[index] = AD_STATE_AVAILABLE; return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } /** @@ -218,7 +241,7 @@ public final class AdPlaybackState implements Bundleable { this.uris.length == states.length ? this.uris : Arrays.copyOf(this.uris, states.length); states[index] = state; return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } /** Returns a new instance with the specified ad durations, in microseconds. */ @@ -230,21 +253,21 @@ public final class AdPlaybackState implements Bundleable { durationsUs = Arrays.copyOf(durationsUs, uris.length); } return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } /** Returns an instance with the specified {@link #contentResumeOffsetUs}. */ @CheckResult public AdGroup withContentResumeOffsetUs(long contentResumeOffsetUs) { return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } /** Returns an instance with the specified value for {@link #isServerSideInserted}. */ @CheckResult public AdGroup withIsServerSideInserted(boolean isServerSideInserted) { return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } /** @@ -255,6 +278,7 @@ public final class AdPlaybackState implements Bundleable { public AdGroup withAllAdsSkipped() { if (count == C.LENGTH_UNSET) { return new AdGroup( + timeUs, /* count= */ 0, /* states= */ new int[0], /* uris= */ new Uri[0], @@ -270,7 +294,7 @@ public final class AdPlaybackState implements Bundleable { } } return new AdGroup( - count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); + timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted); } @CheckResult @@ -296,6 +320,7 @@ public final class AdPlaybackState implements Bundleable { @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ + FIELD_TIME_US, FIELD_COUNT, FIELD_URIS, FIELD_STATES, @@ -305,18 +330,20 @@ public final class AdPlaybackState implements Bundleable { }) private @interface FieldNumber {} - private static final int FIELD_COUNT = 0; - private static final int FIELD_URIS = 1; - private static final int FIELD_STATES = 2; - private static final int FIELD_DURATIONS_US = 3; - private static final int FIELD_CONTENT_RESUME_OFFSET_US = 4; - private static final int FIELD_IS_SERVER_SIDE_INSERTED = 5; + private static final int FIELD_TIME_US = 0; + private static final int FIELD_COUNT = 1; + private static final int FIELD_URIS = 2; + private static final int FIELD_STATES = 3; + private static final int FIELD_DURATIONS_US = 4; + private static final int FIELD_CONTENT_RESUME_OFFSET_US = 5; + private static final int FIELD_IS_SERVER_SIDE_INSERTED = 6; // putParcelableArrayList actually supports null elements. @SuppressWarnings("nullness:argument.type.incompatible") @Override public Bundle toBundle() { Bundle bundle = new Bundle(); + bundle.putLong(keyForField(FIELD_TIME_US), timeUs); bundle.putInt(keyForField(FIELD_COUNT), count); bundle.putParcelableArrayList( keyForField(FIELD_URIS), new ArrayList<@NullableType Uri>(Arrays.asList(uris))); @@ -333,6 +360,7 @@ public final class AdPlaybackState implements Bundleable { // getParcelableArrayList may have null elements. @SuppressWarnings("nullness:type.argument.type.incompatible") private static AdGroup fromBundle(Bundle bundle) { + long timeUs = bundle.getLong(keyForField(FIELD_TIME_US)); int count = bundle.getInt(keyForField(FIELD_COUNT), /* defaultValue= */ C.LENGTH_UNSET); @Nullable ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(keyForField(FIELD_URIS)); @@ -343,6 +371,7 @@ public final class AdPlaybackState implements Bundleable { long contentResumeOffsetUs = bundle.getLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US)); boolean isServerSideInserted = bundle.getBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED)); return new AdGroup( + timeUs, count, states == null ? new int[0] : states, uriList == null ? new Uri[0] : uriList.toArray(new Uri[0]), @@ -386,7 +415,6 @@ public final class AdPlaybackState implements Bundleable { public static final AdPlaybackState NONE = new AdPlaybackState( /* adsId= */ null, - /* adGroupTimesUs= */ new long[0], /* adGroups= */ new AdGroup[0], /* adResumePositionUs= */ 0L, /* contentDurationUs= */ C.TIME_UNSET); @@ -398,14 +426,6 @@ public final class AdPlaybackState implements Bundleable { /** The number of ad groups. */ public final int adGroupCount; - /** - * The times of ad groups, in microseconds, relative to the start of the {@link - * com.google.android.exoplayer2.Timeline.Period} they belong to. A final element with the value - * {@link C#TIME_END_OF_SOURCE} indicates a postroll ad. - */ - public final long[] adGroupTimesUs; - /** The ad groups. */ - public final AdGroup[] adGroups; /** The position offset in the first unplayed ad at which to begin playback, in microseconds. */ public final long adResumePositionUs; /** @@ -413,6 +433,8 @@ public final class AdPlaybackState implements Bundleable { */ public final long contentDurationUs; + private final AdGroup[] adGroups; + /** * Creates a new ad playback state with the specified ad group times. * @@ -424,27 +446,25 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState(Object adsId, long... adGroupTimesUs) { this( adsId, - adGroupTimesUs, - createEmptyAdGroups(adGroupTimesUs.length), + createEmptyAdGroups(adGroupTimesUs), /* adResumePositionUs= */ 0, /* contentDurationUs= */ C.TIME_UNSET); } private AdPlaybackState( - @Nullable Object adsId, - long[] adGroupTimesUs, - AdGroup[] adGroups, - long adResumePositionUs, - long contentDurationUs) { - checkArgument(adGroups.length == adGroupTimesUs.length); + @Nullable Object adsId, AdGroup[] adGroups, long adResumePositionUs, long contentDurationUs) { this.adsId = adsId; - this.adGroupTimesUs = adGroupTimesUs; this.adResumePositionUs = adResumePositionUs; this.contentDurationUs = contentDurationUs; - adGroupCount = adGroupTimesUs.length; + adGroupCount = adGroups.length; this.adGroups = adGroups; } + /** Returns the specified {@link AdGroup}. */ + public AdGroup getAdGroup(int adGroupIndex) { + return adGroups[adGroupIndex]; + } + /** * Returns the index of the ad group at or before {@code positionUs} that should be played before * the content at {@code positionUs}. Returns {@link C#INDEX_UNSET} if the ad group at or before @@ -460,7 +480,7 @@ public final class AdPlaybackState implements Bundleable { public int getAdGroupIndexForPositionUs(long positionUs, long periodDurationUs) { // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. // In practice we expect there to be few ad groups so the search shouldn't be expensive. - int index = adGroupTimesUs.length - 1; + int index = adGroups.length - 1; while (index >= 0 && isPositionBeforeAdGroup(positionUs, periodDurationUs, index)) { index--; } @@ -486,12 +506,12 @@ public final class AdPlaybackState implements Bundleable { // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. // In practice we expect there to be few ad groups so the search shouldn't be expensive. int index = 0; - while (index < adGroupTimesUs.length - && ((adGroupTimesUs[index] != C.TIME_END_OF_SOURCE && adGroupTimesUs[index] <= positionUs) + while (index < adGroups.length + && ((adGroups[index].timeUs != C.TIME_END_OF_SOURCE && adGroups[index].timeUs <= positionUs) || !adGroups[index].shouldPlayAdGroup())) { index++; } - return index < adGroupTimesUs.length ? index : C.INDEX_UNSET; + return index < adGroups.length ? index : C.INDEX_UNSET; } /** Returns whether the specified ad has been marked as in {@link #AD_STATE_ERROR}. */ @@ -516,10 +536,9 @@ public final class AdPlaybackState implements Bundleable { */ @CheckResult public AdPlaybackState withAdGroupTimeUs(int adGroupIndex, long adGroupTimeUs) { - long[] adGroupTimesUs = Arrays.copyOf(this.adGroupTimesUs, this.adGroupCount); - adGroupTimesUs[adGroupIndex] = adGroupTimeUs; - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); + adGroups[adGroupIndex] = this.adGroups[adGroupIndex].withTimeUs(adGroupTimeUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -532,7 +551,7 @@ public final class AdPlaybackState implements Bundleable { */ @CheckResult public AdPlaybackState withNewAdGroup(int adGroupIndex, long adGroupTimeUs) { - AdGroup newAdGroup = new AdGroup(); + AdGroup newAdGroup = new AdGroup(adGroupTimeUs); AdGroup[] adGroups = Util.nullSafeArrayAppend(this.adGroups, newAdGroup); System.arraycopy( /* src= */ adGroups, @@ -541,16 +560,7 @@ public final class AdPlaybackState implements Bundleable { /* destPos= */ adGroupIndex + 1, /* length= */ adGroupCount - adGroupIndex); adGroups[adGroupIndex] = newAdGroup; - long[] adGroupTimesUs = Arrays.copyOf(this.adGroupTimesUs, adGroupCount + 1); - System.arraycopy( - /* src= */ adGroupTimesUs, - /* srcPos= */ adGroupIndex, - /* dest= */ adGroupTimesUs, - /* destPos= */ adGroupIndex + 1, - /* length= */ adGroupCount - adGroupIndex); - adGroupTimesUs[adGroupIndex] = adGroupTimeUs; - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -565,8 +575,7 @@ public final class AdPlaybackState implements Bundleable { } AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = this.adGroups[adGroupIndex].withAdCount(adCount); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad URI. */ @@ -574,8 +583,7 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState withAdUri(int adGroupIndex, int adIndexInAdGroup, Uri uri) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdUri(uri, adIndexInAdGroup); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad marked as played. */ @@ -583,8 +591,7 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState withPlayedAd(int adGroupIndex, int adIndexInAdGroup) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdState(AD_STATE_PLAYED, adIndexInAdGroup); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad marked as skipped. */ @@ -592,8 +599,7 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState withSkippedAd(int adGroupIndex, int adIndexInAdGroup) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdState(AD_STATE_SKIPPED, adIndexInAdGroup); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad marked as having a load error. */ @@ -601,8 +607,7 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState withAdLoadError(int adGroupIndex, int adIndexInAdGroup) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdState(AD_STATE_ERROR, adIndexInAdGroup); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -613,8 +618,7 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState withSkippedAdGroup(int adGroupIndex) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAllAdsSkipped(); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad durations, in microseconds. */ @@ -624,8 +628,7 @@ public final class AdPlaybackState implements Bundleable { for (int adGroupIndex = 0; adGroupIndex < adGroupCount; adGroupIndex++) { adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdDurationsUs(adDurationUs[adGroupIndex]); } - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -636,8 +639,7 @@ public final class AdPlaybackState implements Bundleable { public AdPlaybackState withAdDurationsUs(int adGroupIndex, long... adDurationsUs) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdDurationsUs(adDurationsUs); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -649,8 +651,7 @@ public final class AdPlaybackState implements Bundleable { if (this.adResumePositionUs == adResumePositionUs) { return this; } else { - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } } @@ -660,8 +661,7 @@ public final class AdPlaybackState implements Bundleable { if (this.contentDurationUs == contentDurationUs) { return this; } else { - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } } @@ -677,8 +677,7 @@ public final class AdPlaybackState implements Bundleable { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withContentResumeOffsetUs(contentResumeOffsetUs); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -692,8 +691,7 @@ public final class AdPlaybackState implements Bundleable { } AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withIsServerSideInserted(isServerSideInserted); - return new AdPlaybackState( - adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(adsId, adGroups, adResumePositionUs, contentDurationUs); } @Override @@ -709,7 +707,6 @@ public final class AdPlaybackState implements Bundleable { && adGroupCount == that.adGroupCount && adResumePositionUs == that.adResumePositionUs && contentDurationUs == that.contentDurationUs - && Arrays.equals(adGroupTimesUs, that.adGroupTimesUs) && Arrays.equals(adGroups, that.adGroups); } @@ -719,7 +716,6 @@ public final class AdPlaybackState implements Bundleable { result = 31 * result + (adsId == null ? 0 : adsId.hashCode()); result = 31 * result + (int) adResumePositionUs; result = 31 * result + (int) contentDurationUs; - result = 31 * result + Arrays.hashCode(adGroupTimesUs); result = 31 * result + Arrays.hashCode(adGroups); return result; } @@ -734,7 +730,7 @@ public final class AdPlaybackState implements Bundleable { sb.append(", adGroups=["); for (int i = 0; i < adGroups.length; i++) { sb.append("adGroup(timeUs="); - sb.append(adGroupTimesUs[i]); + sb.append(adGroups[i].timeUs); sb.append(", ads=["); for (int j = 0; j < adGroups[i].states.length; j++) { sb.append("ad(state="); @@ -780,7 +776,7 @@ public final class AdPlaybackState implements Bundleable { // The end of the content is at (but not before) any postroll ad, and after any other ads. return false; } - long adGroupPositionUs = adGroupTimesUs[adGroupIndex]; + long adGroupPositionUs = adGroups[adGroupIndex].timeUs; if (adGroupPositionUs == C.TIME_END_OF_SOURCE) { return periodDurationUs == C.TIME_UNSET || positionUs < periodDurationUs; } else { @@ -792,18 +788,12 @@ public final class AdPlaybackState implements Bundleable { @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({ - FIELD_AD_GROUP_TIMES_US, - FIELD_AD_GROUPS, - FIELD_AD_RESUME_POSITION_US, - FIELD_CONTENT_DURATION_US - }) + @IntDef({FIELD_AD_GROUPS, FIELD_AD_RESUME_POSITION_US, FIELD_CONTENT_DURATION_US}) private @interface FieldNumber {} - private static final int FIELD_AD_GROUP_TIMES_US = 1; - private static final int FIELD_AD_GROUPS = 2; - private static final int FIELD_AD_RESUME_POSITION_US = 3; - private static final int FIELD_CONTENT_DURATION_US = 4; + private static final int FIELD_AD_GROUPS = 1; + private static final int FIELD_AD_RESUME_POSITION_US = 2; + private static final int FIELD_CONTENT_DURATION_US = 3; /** * {@inheritDoc} @@ -815,7 +805,6 @@ public final class AdPlaybackState implements Bundleable { @Override public Bundle toBundle() { Bundle bundle = new Bundle(); - bundle.putLongArray(keyForField(FIELD_AD_GROUP_TIMES_US), adGroupTimesUs); ArrayList adGroupBundleList = new ArrayList<>(); for (AdGroup adGroup : adGroups) { adGroupBundleList.add(adGroup.toBundle()); @@ -834,16 +823,12 @@ public final class AdPlaybackState implements Bundleable { public static final Bundleable.Creator CREATOR = AdPlaybackState::fromBundle; private static AdPlaybackState fromBundle(Bundle bundle) { - @Nullable long[] adGroupTimesUs = bundle.getLongArray(keyForField(FIELD_AD_GROUP_TIMES_US)); - if (adGroupTimesUs == null) { - adGroupTimesUs = new long[0]; - } @Nullable ArrayList adGroupBundleList = bundle.getParcelableArrayList(keyForField(FIELD_AD_GROUPS)); @Nullable AdGroup[] adGroups; if (adGroupBundleList == null) { - adGroups = createEmptyAdGroups(adGroupTimesUs.length); + adGroups = new AdGroup[0]; } else { adGroups = new AdGroup[adGroupBundleList.size()]; for (int i = 0; i < adGroupBundleList.size(); i++) { @@ -854,18 +839,17 @@ public final class AdPlaybackState implements Bundleable { bundle.getLong(keyForField(FIELD_AD_RESUME_POSITION_US), /* defaultValue= */ 0); long contentDurationUs = bundle.getLong(keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ C.TIME_UNSET); - return new AdPlaybackState( - /* adsId= */ null, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState(/* adsId= */ null, adGroups, adResumePositionUs, contentDurationUs); } private static String keyForField(@FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); } - private static AdGroup[] createEmptyAdGroups(int count) { - AdGroup[] adGroups = new AdGroup[count]; - for (int i = 0; i < count; i++) { - adGroups[i] = new AdGroup(); + private static AdGroup[] createEmptyAdGroups(long[] adGroupTimesUs) { + AdGroup[] adGroups = new AdGroup[adGroupTimesUs.length]; + for (int i = 0; i < adGroups.length; i++) { + adGroups[i] = new AdGroup(adGroupTimesUs[i]); } return adGroups; } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index 78bc4c9351..d42a0f91c4 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -44,9 +44,9 @@ public class AdPlaybackStateTest { @Test public void setAdCount() { - assertThat(state.adGroups[0].count).isEqualTo(C.LENGTH_UNSET); + assertThat(state.getAdGroup(0).count).isEqualTo(C.LENGTH_UNSET); state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1); - assertThat(state.adGroups[0].count).isEqualTo(1); + assertThat(state.getAdGroup(0).count).isEqualTo(1); } @Test @@ -54,10 +54,10 @@ public class AdPlaybackStateTest { state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI); state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2); - assertThat(state.adGroups[0].uris[0]).isNull(); - assertThat(state.adGroups[0].states[0]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); - assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI); - assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); + assertThat(state.getAdGroup(0).uris[0]).isNull(); + assertThat(state.getAdGroup(0).states[0]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); + assertThat(state.getAdGroup(0).uris[1]).isSameInstanceAs(TEST_URI); + assertThat(state.getAdGroup(0).states[1]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); } @Test @@ -65,10 +65,10 @@ public class AdPlaybackStateTest { state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2); - assertThat(state.adGroups[0].uris[0]).isNull(); - assertThat(state.adGroups[0].states[0]).isEqualTo(AdPlaybackState.AD_STATE_ERROR); + assertThat(state.getAdGroup(0).uris[0]).isNull(); + assertThat(state.getAdGroup(0).states[0]).isEqualTo(AdPlaybackState.AD_STATE_ERROR); assertThat(state.isAdInErrorState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)).isTrue(); - assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); + assertThat(state.getAdGroup(0).states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); assertThat(state.isAdInErrorState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1)).isFalse(); } @@ -82,8 +82,8 @@ public class AdPlaybackStateTest { .withAdGroupTimeUs(/* adGroupIndex= */ 1, 6_000); assertThat(state.adGroupCount).isEqualTo(2); - assertThat(state.adGroupTimesUs[0]).isEqualTo(3_000); - assertThat(state.adGroupTimesUs[1]).isEqualTo(6_000); + assertThat(state.getAdGroup(0).timeUs).isEqualTo(3_000); + assertThat(state.getAdGroup(1).timeUs).isEqualTo(6_000); } @Test @@ -102,13 +102,13 @@ public class AdPlaybackStateTest { .withNewAdGroup(/* adGroupIndex= */ 4, /* adGroupTimeUs= */ 8_000); assertThat(state.adGroupCount).isEqualTo(5); - assertThat(state.adGroups[0].count).isEqualTo(C.INDEX_UNSET); - assertThat(state.adGroups[1].count).isEqualTo(2); - assertThat(state.adGroups[1].uris[1]).isSameInstanceAs(TEST_URI); - assertThat(state.adGroups[2].count).isEqualTo(C.INDEX_UNSET); - assertThat(state.adGroups[3].count).isEqualTo(1); - assertThat(state.adGroups[3].states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED); - assertThat(state.adGroups[4].count).isEqualTo(C.INDEX_UNSET); + assertThat(state.getAdGroup(0).count).isEqualTo(C.INDEX_UNSET); + assertThat(state.getAdGroup(1).count).isEqualTo(2); + assertThat(state.getAdGroup(1).uris[1]).isSameInstanceAs(TEST_URI); + assertThat(state.getAdGroup(2).count).isEqualTo(C.INDEX_UNSET); + assertThat(state.getAdGroup(3).count).isEqualTo(1); + assertThat(state.getAdGroup(3).states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED); + assertThat(state.getAdGroup(4).count).isEqualTo(C.INDEX_UNSET); } @Test @@ -121,10 +121,10 @@ public class AdPlaybackStateTest { state = state.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 1_000, 2_000); - assertThat(state.adGroups[0].durationsUs[0]).isEqualTo(5_000); - assertThat(state.adGroups[0].durationsUs[1]).isEqualTo(6_000); - assertThat(state.adGroups[1].durationsUs[0]).isEqualTo(1_000); - assertThat(state.adGroups[1].durationsUs[1]).isEqualTo(2_000); + assertThat(state.getAdGroup(0).durationsUs[0]).isEqualTo(5_000); + assertThat(state.getAdGroup(0).durationsUs[1]).isEqualTo(6_000); + assertThat(state.getAdGroup(1).durationsUs[0]).isEqualTo(1_000); + assertThat(state.getAdGroup(1).durationsUs[1]).isEqualTo(2_000); } @Test @@ -133,7 +133,7 @@ public class AdPlaybackStateTest { state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); - assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(0); + assertThat(state.getAdGroup(0).getFirstAdIndexToPlay()).isEqualTo(0); } @Test @@ -144,9 +144,9 @@ public class AdPlaybackStateTest { state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); - assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(1); - assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); - assertThat(state.adGroups[0].states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); + assertThat(state.getAdGroup(0).getFirstAdIndexToPlay()).isEqualTo(1); + assertThat(state.getAdGroup(0).states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); + assertThat(state.getAdGroup(0).states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); } @Test @@ -157,9 +157,9 @@ public class AdPlaybackStateTest { state = state.withSkippedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); - assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(1); - assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); - assertThat(state.adGroups[0].states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); + assertThat(state.getAdGroup(0).getFirstAdIndexToPlay()).isEqualTo(1); + assertThat(state.getAdGroup(0).states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); + assertThat(state.getAdGroup(0).states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); } @Test @@ -171,7 +171,7 @@ public class AdPlaybackStateTest { state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); - assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(2); + assertThat(state.getAdGroup(0).getFirstAdIndexToPlay()).isEqualTo(2); } @Test @@ -181,7 +181,7 @@ public class AdPlaybackStateTest { state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); - assertThat(state.adGroups[0].getNextAdIndexToPlay(0)).isEqualTo(2); + assertThat(state.getAdGroup(0).getNextAdIndexToPlay(0)).isEqualTo(2); } @Test @@ -194,7 +194,7 @@ public class AdPlaybackStateTest { state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); - assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(0); + assertThat(state.getAdGroup(0).getFirstAdIndexToPlay()).isEqualTo(0); } @Test @@ -209,8 +209,8 @@ public class AdPlaybackStateTest { state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2); - assertThat(state.adGroups[0].getNextAdIndexToPlay(/* lastPlayedAdIndex= */ 0)).isEqualTo(1); - assertThat(state.adGroups[0].getNextAdIndexToPlay(/* lastPlayedAdIndex= */ 1)).isEqualTo(2); + assertThat(state.getAdGroup(0).getNextAdIndexToPlay(/* lastPlayedAdIndex= */ 0)).isEqualTo(1); + assertThat(state.getAdGroup(0).getNextAdIndexToPlay(/* lastPlayedAdIndex= */ 1)).isEqualTo(2); } @Test @@ -229,8 +229,8 @@ public class AdPlaybackStateTest { public void skipAllWithoutAdCount() { state = state.withSkippedAdGroup(0); state = state.withSkippedAdGroup(1); - assertThat(state.adGroups[0].count).isEqualTo(0); - assertThat(state.adGroups[1].count).isEqualTo(0); + assertThat(state.getAdGroup(0).count).isEqualTo(0); + assertThat(state.getAdGroup(1).count).isEqualTo(0); } @Test @@ -257,8 +257,9 @@ public class AdPlaybackStateTest { assertThat(restoredState.adsId).isNull(); assertThat(restoredState.adGroupCount).isEqualTo(originalState.adGroupCount); - assertThat(restoredState.adGroupTimesUs).isEqualTo(originalState.adGroupTimesUs); - assertThat(restoredState.adGroups).isEqualTo(originalState.adGroups); + for (int i = 0; i < restoredState.adGroupCount; i++) { + assertThat(restoredState.getAdGroup(i)).isEqualTo(originalState.getAdGroup(i)); + } assertThat(restoredState.adResumePositionUs).isEqualTo(originalState.adResumePositionUs); assertThat(restoredState.contentDurationUs).isEqualTo(originalState.contentDurationUs); } @@ -266,7 +267,7 @@ public class AdPlaybackStateTest { @Test public void roundTripViaBundle_ofAdGroup_yieldsEqualInstance() { AdPlaybackState.AdGroup adGroup = - new AdPlaybackState.AdGroup() + new AdPlaybackState.AdGroup(/* timeUs= */ 42) .withAdCount(2) .withAdState(AD_STATE_AVAILABLE, /* index= */ 0) .withAdState(AD_STATE_PLAYED, /* index= */ 1) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index 906139cf76..f937b0a07b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -50,7 +50,7 @@ public interface AdsLoader { /** * Called when the ad playback state has been updated. The number of {@link - * AdPlaybackState#adGroups ad groups} may not change after the first call. + * AdPlaybackState#adGroupCount ad groups} may not change after the first call. * * @param adPlaybackState The new ad playback state. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 3baf48db87..182ead64d1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -305,11 +305,11 @@ public final class AdsMediaSource extends CompositeMediaSource { @Nullable AdMediaSourceHolder adMediaSourceHolder = this.adMediaSourceHolders[adGroupIndex][adIndexInAdGroup]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); if (adMediaSourceHolder != null && !adMediaSourceHolder.hasMediaSource() - && adPlaybackState.adGroups[adGroupIndex] != null - && adIndexInAdGroup < adPlaybackState.adGroups[adGroupIndex].uris.length) { - @Nullable Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup]; + && adIndexInAdGroup < adGroup.uris.length) { + @Nullable Uri adUri = adGroup.uris[adIndexInAdGroup]; if (adUri != null) { MediaItem.Builder adMediaItem = new MediaItem.Builder().setUri(adUri); // Propagate the content's DRM config into the ad media source. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java index 922fac6c87..1eaa369f72 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsMediaSource.java @@ -122,13 +122,14 @@ public final class ServerSideInsertedAdsMediaSource extends BaseMediaSource public void setAdPlaybackState(AdPlaybackState adPlaybackState) { checkArgument(adPlaybackState.adGroupCount >= this.adPlaybackState.adGroupCount); for (int i = 0; i < adPlaybackState.adGroupCount; i++) { - checkArgument(adPlaybackState.adGroups[i].isServerSideInserted); + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i); + checkArgument(adGroup.isServerSideInserted); if (i < this.adPlaybackState.adGroupCount) { checkArgument( getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i) >= getAdCountInGroup(this.adPlaybackState, /* adGroupIndex= */ i)); } - if (adPlaybackState.adGroupTimesUs[i] == C.TIME_END_OF_SOURCE) { + if (adGroup.timeUs == C.TIME_END_OF_SOURCE) { checkArgument(getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i) == 0); } } @@ -482,14 +483,14 @@ public final class ServerSideInsertedAdsMediaSource extends BaseMediaSource MediaPeriodImpl mediaPeriod, AdPlaybackState adPlaybackState) { MediaPeriodId id = mediaPeriod.mediaPeriodId; if (id.isAd()) { - return adPlaybackState.adGroups[id.adGroupIndex].count == C.LENGTH_UNSET - ? 0 - : adPlaybackState.adGroups[id.adGroupIndex].durationsUs[id.adIndexInAdGroup]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(id.adGroupIndex); + return adGroup.count == C.LENGTH_UNSET ? 0 : adGroup.durationsUs[id.adIndexInAdGroup]; } - return id.nextAdGroupIndex == C.INDEX_UNSET - || adPlaybackState.adGroupTimesUs[id.nextAdGroupIndex] == C.TIME_END_OF_SOURCE - ? Long.MAX_VALUE - : adPlaybackState.adGroupTimesUs[id.nextAdGroupIndex]; + if (id.nextAdGroupIndex == C.INDEX_UNSET) { + return Long.MAX_VALUE; + } + AdPlaybackState.AdGroup nextAdGroup = adPlaybackState.getAdGroup(id.nextAdGroupIndex); + return nextAdGroup.timeUs == C.TIME_END_OF_SOURCE ? Long.MAX_VALUE : nextAdGroup.timeUs; } private static MediaLoadData correctMediaLoadData( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java index c329deb5ee..aace219597 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideInsertedAdsUtil.java @@ -55,8 +55,8 @@ public final class ServerSideInsertedAdsUtil { fromPositionUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState); int insertionIndex = 0; while (insertionIndex < adPlaybackState.adGroupCount - && adPlaybackState.adGroupTimesUs[insertionIndex] != C.TIME_END_OF_SOURCE - && adPlaybackState.adGroupTimesUs[insertionIndex] <= adGroupInsertionPositionUs) { + && adPlaybackState.getAdGroup(insertionIndex).timeUs != C.TIME_END_OF_SOURCE + && adPlaybackState.getAdGroup(insertionIndex).timeUs <= adGroupInsertionPositionUs) { insertionIndex++; } long adDurationUs = toPositionUs - fromPositionUs; @@ -69,11 +69,11 @@ public final class ServerSideInsertedAdsUtil { .withContentResumeOffsetUs(insertionIndex, contentResumeOffsetUs); long followingAdGroupTimeUsOffset = -adDurationUs + contentResumeOffsetUs; for (int i = insertionIndex + 1; i < adPlaybackState.adGroupCount; i++) { - if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) { + long adGroupTimeUs = adPlaybackState.getAdGroup(i).timeUs; + if (adGroupTimeUs != C.TIME_END_OF_SOURCE) { adPlaybackState = adPlaybackState.withAdGroupTimeUs( - /* adGroupIndex= */ i, - adPlaybackState.adGroupTimesUs[i] + followingAdGroupTimeUsOffset); + /* adGroupIndex= */ i, adGroupTimeUs + followingAdGroupTimeUsOffset); } } return adPlaybackState; @@ -182,16 +182,18 @@ public final class ServerSideInsertedAdsUtil { */ public static long getStreamPositionUsForAd( long positionUs, int adGroupIndex, int adIndexInAdGroup, AdPlaybackState adPlaybackState) { - positionUs += adPlaybackState.adGroupTimesUs[adGroupIndex]; + AdPlaybackState.AdGroup currentAdGroup = adPlaybackState.getAdGroup(adGroupIndex); + positionUs += currentAdGroup.timeUs; for (int i = 0; i < adGroupIndex; i++) { + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i); for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) { - positionUs += adPlaybackState.adGroups[i].durationsUs[j]; + positionUs += adGroup.durationsUs[j]; } - positionUs -= adPlaybackState.adGroups[i].contentResumeOffsetUs; + positionUs -= adGroup.contentResumeOffsetUs; } if (adIndexInAdGroup < getAdCountInGroup(adPlaybackState, adGroupIndex)) { for (int i = 0; i < adIndexInAdGroup; i++) { - positionUs += adPlaybackState.adGroups[adGroupIndex].durationsUs[i]; + positionUs += currentAdGroup.durationsUs[i]; } } return positionUs; @@ -210,16 +212,18 @@ public final class ServerSideInsertedAdsUtil { */ public static long getMediaPeriodPositionUsForAd( long positionUs, int adGroupIndex, int adIndexInAdGroup, AdPlaybackState adPlaybackState) { - positionUs -= adPlaybackState.adGroupTimesUs[adGroupIndex]; + AdPlaybackState.AdGroup currentAdGroup = adPlaybackState.getAdGroup(adGroupIndex); + positionUs -= currentAdGroup.timeUs; for (int i = 0; i < adGroupIndex; i++) { + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i); for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) { - positionUs -= adPlaybackState.adGroups[i].durationsUs[j]; + positionUs -= adGroup.durationsUs[j]; } - positionUs += adPlaybackState.adGroups[i].contentResumeOffsetUs; + positionUs += adGroup.contentResumeOffsetUs; } if (adIndexInAdGroup < getAdCountInGroup(adPlaybackState, adGroupIndex)) { for (int i = 0; i < adIndexInAdGroup; i++) { - positionUs -= adPlaybackState.adGroups[adGroupIndex].durationsUs[i]; + positionUs -= currentAdGroup.durationsUs[i]; } } return positionUs; @@ -243,18 +247,16 @@ public final class ServerSideInsertedAdsUtil { nextAdGroupIndex = adPlaybackState.adGroupCount; } for (int i = 0; i < nextAdGroupIndex; i++) { - if (adPlaybackState.adGroupTimesUs[i] == C.TIME_END_OF_SOURCE - || adPlaybackState.adGroupTimesUs[i] > positionUs) { + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i); + if (adGroup.timeUs == C.TIME_END_OF_SOURCE || adGroup.timeUs > positionUs) { break; } - long adGroupStreamStartPositionUs = - adPlaybackState.adGroupTimesUs[i] + totalAdDurationBeforePositionUs; + long adGroupStreamStartPositionUs = adGroup.timeUs + totalAdDurationBeforePositionUs; for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) { - totalAdDurationBeforePositionUs += adPlaybackState.adGroups[i].durationsUs[j]; + totalAdDurationBeforePositionUs += adGroup.durationsUs[j]; } - totalAdDurationBeforePositionUs -= adPlaybackState.adGroups[i].contentResumeOffsetUs; - long adGroupResumePositionUs = - adPlaybackState.adGroupTimesUs[i] + adPlaybackState.adGroups[i].contentResumeOffsetUs; + totalAdDurationBeforePositionUs -= adGroup.contentResumeOffsetUs; + long adGroupResumePositionUs = adGroup.timeUs + adGroup.contentResumeOffsetUs; if (adGroupResumePositionUs > positionUs) { // The position is inside the ad group. return max(adGroupStreamStartPositionUs, positionUs + totalAdDurationBeforePositionUs); @@ -282,19 +284,19 @@ public final class ServerSideInsertedAdsUtil { nextAdGroupIndex = adPlaybackState.adGroupCount; } for (int i = 0; i < nextAdGroupIndex; i++) { - if (adPlaybackState.adGroupTimesUs[i] == C.TIME_END_OF_SOURCE - || adPlaybackState.adGroupTimesUs[i] > positionUs - totalAdDurationBeforePositionUs) { + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i); + if (adGroup.timeUs == C.TIME_END_OF_SOURCE + || adGroup.timeUs > positionUs - totalAdDurationBeforePositionUs) { break; } for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) { - totalAdDurationBeforePositionUs += adPlaybackState.adGroups[i].durationsUs[j]; + totalAdDurationBeforePositionUs += adGroup.durationsUs[j]; } - totalAdDurationBeforePositionUs -= adPlaybackState.adGroups[i].contentResumeOffsetUs; - long adGroupResumePositionUs = - adPlaybackState.adGroupTimesUs[i] + adPlaybackState.adGroups[i].contentResumeOffsetUs; + totalAdDurationBeforePositionUs -= adGroup.contentResumeOffsetUs; + long adGroupResumePositionUs = adGroup.timeUs + adGroup.contentResumeOffsetUs; if (adGroupResumePositionUs > positionUs - totalAdDurationBeforePositionUs) { // The position is inside the ad group. - return max(adPlaybackState.adGroupTimesUs[i], positionUs - totalAdDurationBeforePositionUs); + return max(adGroup.timeUs, positionUs - totalAdDurationBeforePositionUs); } } return positionUs - totalAdDurationBeforePositionUs; @@ -308,8 +310,7 @@ public final class ServerSideInsertedAdsUtil { * @return The number of ads in the ad group. */ public static int getAdCountInGroup(AdPlaybackState adPlaybackState, int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].count == C.LENGTH_UNSET - ? 0 - : adPlaybackState.adGroups[adGroupIndex].count; + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); + return adGroup.count == C.LENGTH_UNSET ? 0 : adGroup.count; } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index a59d84abb4..58d0d0c47b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -797,7 +797,9 @@ public final class MediaPeriodQueueTest { long[][] newDurations = new long[adPlaybackState.adGroupCount][]; for (int i = 0; i < adPlaybackState.adGroupCount; i++) { newDurations[i] = - i == adGroupIndex ? new long[] {AD_DURATION_US} : adPlaybackState.adGroups[i].durationsUs; + i == adGroupIndex + ? new long[] {AD_DURATION_US} + : adPlaybackState.getAdGroup(i).durationsUs; } adPlaybackState = adPlaybackState @@ -808,7 +810,7 @@ public final class MediaPeriodQueueTest { } private void setAdGroupPlayed(int adGroupIndex) { - for (int i = 0; i < adPlaybackState.adGroups[adGroupIndex].count; i++) { + for (int i = 0; i < adPlaybackState.getAdGroup(adGroupIndex).count; i++) { adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, /* adIndexInAdGroup= */ i); } updateTimeline();