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 0b10cce3b1b..ac939c56082 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 @@ -128,6 +128,7 @@ private final ImaUtil.ImaFactory imaFactory; private final List supportedMimeTypes; private final DataSpec adTagDataSpec; + private final Object adsId; private final Timeline.Period period; private final Handler handler; private final ComponentListener componentListener; @@ -146,7 +147,6 @@ @Nullable private AdsManager adsManager; private boolean isAdsManagerInitialized; - private boolean hasAdPlaybackState; @Nullable private AdLoadException pendingAdLoadError; private Timeline timeline; private long contentDurationMs; @@ -214,6 +214,7 @@ public AdTagLoader( ImaUtil.ImaFactory imaFactory, List supportedMimeTypes, DataSpec adTagDataSpec, + Object adsId, @Nullable ViewGroup adViewGroup) { this.configuration = configuration; this.imaFactory = imaFactory; @@ -228,6 +229,7 @@ public AdTagLoader( imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION); this.supportedMimeTypes = supportedMimeTypes; this.adTagDataSpec = adTagDataSpec; + this.adsId = adsId; period = new Timeline.Period(); handler = Util.createHandler(getImaLooper(), /* callback= */ null); componentListener = new ComponentListener(); @@ -286,14 +288,16 @@ public void start(Player player, AdViewProvider adViewProvider, EventListener ev lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; maybeNotifyPendingAdLoadError(); - if (hasAdPlaybackState) { + if (!AdPlaybackState.NONE.equals(adPlaybackState)) { // Pass the ad playback state to the player, and resume ads if necessary. eventListener.onAdPlaybackState(adPlaybackState); if (adsManager != null && imaPausedContent && playWhenReady) { adsManager.resume(); } } else if (adsManager != null) { - adPlaybackState = ImaUtil.getInitialAdPlaybackStateForCuePoints(adsManager.getAdCuePoints()); + adPlaybackState = + new AdPlaybackState( + adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); updateAdPlaybackState(); } if (adDisplayContainer != null) { @@ -348,8 +352,7 @@ public void release() { stopUpdatingAdProgress(); imaAdInfo = null; pendingAdLoadError = null; - adPlaybackState = AdPlaybackState.NONE; - hasAdPlaybackState = true; + adPlaybackState = new AdPlaybackState(adsId); updateAdPlaybackState(); } @@ -496,7 +499,7 @@ private AdsLoader requestAds( try { request = ImaUtil.getAdsRequestForAdTagDataSpec(imaFactory, adTagDataSpec); } catch (IOException e) { - hasAdPlaybackState = true; + adPlaybackState = new AdPlaybackState(adsId); updateAdPlaybackState(); pendingAdLoadError = AdLoadException.createForAllAds(e); maybeNotifyPendingAdLoadError(); @@ -1215,8 +1218,8 @@ public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) { // If a player is attached already, start playback immediately. try { adPlaybackState = - ImaUtil.getInitialAdPlaybackStateForCuePoints(adsManager.getAdCuePoints()); - hasAdPlaybackState = true; + new AdPlaybackState( + adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); updateAdPlaybackState(); } catch (RuntimeException e) { maybeNotifyInternalError("onAdsManagerLoaded", e); @@ -1276,8 +1279,7 @@ public void onAdError(AdErrorEvent adErrorEvent) { if (adsManager == null) { // No ads were loaded, so allow playback to start without any ads. pendingAdRequestContext = null; - adPlaybackState = AdPlaybackState.NONE; - hasAdPlaybackState = true; + adPlaybackState = new AdPlaybackState(adsId); updateAdPlaybackState(); } else if (ImaUtil.isAdGroupLoadError(error)) { try { diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index ccffd1aca7d..a60b7147bce 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -417,14 +417,21 @@ public AdDisplayContainer getAdDisplayContainer() { * * @param adTagDataSpec The data specification of the ad tag to load. See class javadoc for * information about compatible ad tag formats. + * @param adsId A opaque identifier for the ad playback state across start/stop calls. * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code * null} if playing audio-only ads. */ - public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) { + public void requestAds(DataSpec adTagDataSpec, Object adsId, @Nullable ViewGroup adViewGroup) { if (adTagLoader == null) { adTagLoader = new AdTagLoader( - context, configuration, imaFactory, supportedMimeTypes, adTagDataSpec, adViewGroup); + context, + configuration, + imaFactory, + supportedMimeTypes, + adTagDataSpec, + adsId, + adViewGroup); } } @@ -488,7 +495,7 @@ public void start( return; } if (adTagLoader == null) { - requestAds(adTagDataSpec, adViewProvider.getAdViewGroup()); + requestAds(adTagDataSpec, adsId, adViewProvider.getAdViewGroup()); } checkNotNull(adTagLoader).start(player, adViewProvider, eventListener); } diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index ae12819e841..ed3d3c74e13 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -36,7 +36,6 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo; import com.google.android.exoplayer2.upstream.DataSchemeDataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -154,15 +153,14 @@ public static FriendlyObstructionPurpose getFriendlyObstructionPurpose( } /** - * Returns an initial {@link AdPlaybackState} with ad groups at the provided {@code cuePoints}. + * Returns the microsecond ad group timestamps corresponding to the specified cue points. * - * @param cuePoints The cue points of the ads in seconds. - * @return The {@link AdPlaybackState}. + * @param cuePoints The cue points of the ads in seconds, provided by the IMA SDK. + * @return The corresponding microsecond ad group timestamps. */ - public static AdPlaybackState getInitialAdPlaybackStateForCuePoints(List cuePoints) { + public static long[] getAdGroupTimesUsForCuePoints(List cuePoints) { if (cuePoints.isEmpty()) { - // If no cue points are specified, there is a preroll ad. - return new AdPlaybackState(/* adGroupTimesUs...= */ 0); + return new long[] {0L}; } int count = cuePoints.size(); @@ -178,7 +176,7 @@ public static AdPlaybackState getInitialAdPlaybackStateForCuePoints(List } // Cue points may be out of order, so sort them. Arrays.sort(adGroupTimesUs, 0, adGroupIndex); - return new AdPlaybackState(adGroupTimesUs); + return adGroupTimesUs; } /** Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. */ 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 59c9718c6ea..c8110ea4e4e 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 @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.ext.ima; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.android.exoplayer2.ext.ima.ImaUtil.getAdGroupTimesUsForCuePoints; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyDouble; @@ -226,7 +227,7 @@ public void start_updatesAdPlaybackState() { assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - new AdPlaybackState(/* adGroupTimesUs...= */ 0) + new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)); } @@ -242,7 +243,7 @@ public void startAfterRelease() { public void startAndCallbacksAfterRelease() { setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS); // Request ads in order to get a reference to the ad event listener. - imaAdsLoader.requestAds(TEST_DATA_SPEC, adViewGroup); + imaAdsLoader.requestAds(TEST_DATA_SPEC, TEST_ADS_ID, adViewGroup); imaAdsLoader.release(); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); @@ -253,7 +254,7 @@ public void startAndCallbacksAfterRelease() { // Note: we can't currently call getContentProgress/getAdProgress as a VerifyError is thrown // when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA // SDK being proguarded. - imaAdsLoader.requestAds(TEST_DATA_SPEC, adViewGroup); + imaAdsLoader.requestAds(TEST_DATA_SPEC, TEST_ADS_ID, adViewGroup); adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd)); videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo); adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd)); @@ -300,7 +301,7 @@ public void playback_withPrerollAd_marksAdAsPlayed() { // Verify that the preroll ad has been marked as played. assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - new AdPlaybackState(/* adGroupTimesUs...= */ 0) + new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI) @@ -324,7 +325,7 @@ public void playback_withMidrollFetchError_marksAdAsInErrorState() { assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - new AdPlaybackState(/* adGroupTimesUs...= */ 20_500_000) + new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 20_500_000) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}}) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) @@ -372,7 +373,7 @@ public void playback_withPostrollFetchError_marksAdAsInErrorState() { assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - new AdPlaybackState(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE) + new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}}) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) @@ -400,7 +401,7 @@ public void playback_withAdNotPreloadingBeforeTimeout_hasNoError() { assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)); } @@ -425,7 +426,7 @@ public void playback_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() { assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}}) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) @@ -448,7 +449,7 @@ public void resumePlaybackBeforeMidroll_playsPreroll() { verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble()); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)); } @@ -473,7 +474,7 @@ public void resumePlaybackAtMidroll_skipsPreroll() { .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withSkippedAdGroup(/* adGroupIndex= */ 0)); } @@ -499,7 +500,7 @@ public void resumePlaybackAfterMidroll_skipsPreroll() { .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withSkippedAdGroup(/* adGroupIndex= */ 0)); } @@ -527,7 +528,7 @@ public void resumePlaybackBeforeSecondMidroll_playsFirstMidroll() { verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble()); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)); } @@ -559,7 +560,7 @@ public void resumePlaybackAtSecondMidroll_skipsFirstMidroll() { .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withSkippedAdGroup(/* adGroupIndex= */ 0)); } @@ -594,7 +595,7 @@ public void resumePlaybackBeforeMidroll_withoutPlayAdBeforeStartPosition_skipsPr .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withSkippedAdGroup(/* adGroupIndex= */ 0) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)); } @@ -629,7 +630,7 @@ public void resumePlaybackAtMidroll_withoutPlayAdBeforeStartPosition_skipsPrerol .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withSkippedAdGroup(/* adGroupIndex= */ 0)); } @@ -659,7 +660,7 @@ public void resumePlaybackAfterMidroll_withoutPlayAdBeforeStartPosition_skipsMid verify(mockAdsManager).destroy(); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withSkippedAdGroup(/* adGroupIndex= */ 0) .withSkippedAdGroup(/* adGroupIndex= */ 1)); @@ -703,7 +704,7 @@ public void resumePlaybackAfterMidroll_withoutPlayAdBeforeStartPosition_skipsMid .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withSkippedAdGroup(/* adGroupIndex= */ 0) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)); } @@ -745,7 +746,7 @@ public void resumePlaybackAtSecondMidroll_withoutPlayAdBeforeStartPosition_skips .of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withSkippedAdGroup(/* adGroupIndex= */ 0)); } @@ -835,7 +836,7 @@ public void stop_unregistersAllVideoControlOverlays() { setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS); imaAdsLoader.start( adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); - imaAdsLoader.requestAds(TEST_DATA_SPEC, adViewGroup); + imaAdsLoader.requestAds(TEST_DATA_SPEC, TEST_ADS_ID, adViewGroup); imaAdsLoader.stop(adsMediaSource); InOrder inOrder = inOrder(mockAdDisplayContainer); @@ -887,7 +888,7 @@ public double getTimeOffset() { assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( - ImaUtil.getInitialAdPlaybackStateForCuePoints(cuePoints) + new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints)) .withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index e992eb588d9..086ff817ea9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -519,9 +519,13 @@ public long getPositionInWindowUs() { return positionInWindowUs; } - /** - * Returns the number of ad groups in the period. - */ + /** Returns the opaque identifier for ads played with this period, or {@code null} if unset. */ + @Nullable + public Object getAdsId() { + return adPlaybackState.adsId; + } + + /** Returns the number of ad groups in the period. */ public int getAdGroupCount() { return adPlaybackState.adGroupCount; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 9493746669c..a50fcd7d1dc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -258,7 +258,18 @@ private static long[] copyDurationsUsWithSpaceForAdCount(long[] durationsUs, int public static final int AD_STATE_ERROR = 4; /** Ad playback state with no ads. */ - public static final AdPlaybackState NONE = new AdPlaybackState(); + public static final AdPlaybackState NONE = + new AdPlaybackState( + /* adsId= */ null, + /* adGroupTimesUs= */ new long[0], + /* adGroups= */ null, + /* adResumePositionUs= */ 0L, + /* contentDurationUs= */ C.TIME_UNSET); + + /** + * The opaque identifier for ads with which this instance is associated, or {@code null} if unset. + */ + @Nullable public final Object adsId; /** The number of ad groups. */ public final int adGroupCount; @@ -280,29 +291,38 @@ private static long[] copyDurationsUsWithSpaceForAdCount(long[] durationsUs, int /** * Creates a new ad playback state with the specified ad group times. * + * @param adsId The opaque identifier for ads with which this instance is associated. * @param adGroupTimesUs 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 that there is a postroll ad. */ - public AdPlaybackState(long... adGroupTimesUs) { - int count = adGroupTimesUs.length; - adGroupCount = count; - this.adGroupTimesUs = Arrays.copyOf(adGroupTimesUs, count); - this.adGroups = new AdGroup[count]; - for (int i = 0; i < count; i++) { - adGroups[i] = new AdGroup(); - } - adResumePositionUs = 0; - contentDurationUs = C.TIME_UNSET; + public AdPlaybackState(Object adsId, long... adGroupTimesUs) { + this( + adsId, + adGroupTimesUs, + /* adGroups= */ null, + /* adResumePositionUs= */ 0, + /* contentDurationUs= */ C.TIME_UNSET); } private AdPlaybackState( - long[] adGroupTimesUs, AdGroup[] adGroups, long adResumePositionUs, long contentDurationUs) { - adGroupCount = adGroups.length; + @Nullable Object adsId, + long[] adGroupTimesUs, + @Nullable AdGroup[] adGroups, + long adResumePositionUs, + long contentDurationUs) { + this.adsId = adsId; this.adGroupTimesUs = adGroupTimesUs; - this.adGroups = adGroups; this.adResumePositionUs = adResumePositionUs; this.contentDurationUs = contentDurationUs; + adGroupCount = adGroupTimesUs.length; + if (adGroups == null) { + adGroups = new AdGroup[adGroupCount]; + for (int i = 0; i < adGroupCount; i++) { + adGroups[i] = new AdGroup(); + } + } + this.adGroups = adGroups; } /** @@ -378,7 +398,8 @@ public AdPlaybackState withAdCount(int adGroupIndex, int adCount) { } AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = this.adGroups[adGroupIndex].withAdCount(adCount); - return new AdPlaybackState(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad URI. */ @@ -386,7 +407,8 @@ public AdPlaybackState withAdCount(int adGroupIndex, int adCount) { 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(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad marked as played. */ @@ -394,7 +416,8 @@ public AdPlaybackState withAdUri(int adGroupIndex, int adIndexInAdGroup, Uri uri 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(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad marked as skipped. */ @@ -402,7 +425,8 @@ public AdPlaybackState withPlayedAd(int adGroupIndex, int adIndexInAdGroup) { 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(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad marked as having a load error. */ @@ -410,7 +434,8 @@ public AdPlaybackState withSkippedAd(int adGroupIndex, int adIndexInAdGroup) { 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(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -421,7 +446,8 @@ public AdPlaybackState withAdLoadError(int adGroupIndex, int adIndexInAdGroup) { public AdPlaybackState withSkippedAdGroup(int adGroupIndex) { AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length); adGroups[adGroupIndex] = adGroups[adGroupIndex].withAllAdsSkipped(); - return new AdPlaybackState(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** Returns an instance with the specified ad durations, in microseconds. */ @@ -431,7 +457,8 @@ public AdPlaybackState withAdDurationsUs(long[][] adDurationUs) { for (int adGroupIndex = 0; adGroupIndex < adGroupCount; adGroupIndex++) { adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdDurationsUs(adDurationUs[adGroupIndex]); } - return new AdPlaybackState(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } /** @@ -443,7 +470,8 @@ public AdPlaybackState withAdResumePositionUs(long adResumePositionUs) { if (this.adResumePositionUs == adResumePositionUs) { return this; } else { - return new AdPlaybackState(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } } @@ -453,7 +481,8 @@ public AdPlaybackState withContentDurationUs(long contentDurationUs) { if (this.contentDurationUs == contentDurationUs) { return this; } else { - return new AdPlaybackState(adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); + return new AdPlaybackState( + adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); } } @@ -466,7 +495,8 @@ public boolean equals(@Nullable Object o) { return false; } AdPlaybackState that = (AdPlaybackState) o; - return adGroupCount == that.adGroupCount + return Util.areEqual(adsId, that.adsId) + && adGroupCount == that.adGroupCount && adResumePositionUs == that.adResumePositionUs && contentDurationUs == that.contentDurationUs && Arrays.equals(adGroupTimesUs, that.adGroupTimesUs) @@ -476,6 +506,7 @@ public boolean equals(@Nullable Object o) { @Override public int hashCode() { int result = adGroupCount; + result = 31 * result + (adsId == null ? 0 : adsId.hashCode()); result = 31 * result + (int) adResumePositionUs; result = 31 * result + (int) contentDurationUs; result = 31 * result + Arrays.hashCode(adGroupTimesUs); @@ -486,7 +517,9 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("AdPlaybackState(adResumePositionUs="); + sb.append("AdPlaybackState(adsId="); + sb.append(adsId); + sb.append(", adResumePositionUs="); sb.append(adResumePositionUs); sb.append(", adGroups=["); for (int i = 0; i < adGroups.length; i++) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index d5ebe951c30..e4f17b351d1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -4349,7 +4349,8 @@ protected FakeMediaPeriod createFakeMediaPeriod( public void addMediaSource_whilePlayingAd_correctMasking() throws Exception { long contentDurationMs = 10_000; long adDurationMs = 100_000; - AdPlaybackState adPlaybackState = new AdPlaybackState(/* adGroupTimesUs...= */ 0); + AdPlaybackState adPlaybackState = + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0); adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1); adPlaybackState = adPlaybackState.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY); @@ -4455,7 +4456,8 @@ adsMediaSource, new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1))) public void seekTo_whilePlayingAd_correctMasking() throws Exception { long contentDurationMs = 10_000; long adDurationMs = 4_000; - AdPlaybackState adPlaybackState = new AdPlaybackState(/* adGroupTimesUs...= */ 0); + AdPlaybackState adPlaybackState = + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0); adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1); adPlaybackState = adPlaybackState.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY); 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 6b90dfab15b..41b980d7e66 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 @@ -404,7 +404,8 @@ public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriod private void setupAdTimeline(long... adGroupTimesUs) { adPlaybackState = - new AdPlaybackState(adGroupTimesUs).withContentDurationUs(CONTENT_DURATION_US); + new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs) + .withContentDurationUs(CONTENT_DURATION_US); SinglePeriodAdTimeline adTimeline = new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState); setupTimeline(adTimeline); @@ -498,7 +499,8 @@ private void setAdGroupFailedToLoad(int adGroupIndex) { private void updateAdPlaybackStateAndTimeline(long... adGroupTimesUs) { adPlaybackState = - new AdPlaybackState(adGroupTimesUs).withContentDurationUs(CONTENT_DURATION_US); + new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs) + .withContentDurationUs(CONTENT_DURATION_US); updateTimeline(); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java index 5f97ad78f23..d804479dfa5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java @@ -419,7 +419,9 @@ public void updateSessions_withoutMediaPeriodId_afterSessionForMediaPeriodId_ret /* isDynamic= */ false, /* durationUs =*/ 10 * C.MICROS_PER_SECOND, new AdPlaybackState( - /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND) + /* adsId= */ new Object(), + /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, + 5 * C.MICROS_PER_SECOND) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1))); EventTime adEventTime1 = @@ -701,7 +703,8 @@ public void timelineUpdate_withContent_doesNotFinishFuturePostrollAd() { /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs =*/ 10 * C.MICROS_PER_SECOND, - new AdPlaybackState(/* adGroupTimesUs=... */ C.TIME_END_OF_SOURCE) + new AdPlaybackState( + /* adsId= */ new Object(), /* adGroupTimesUs=... */ C.TIME_END_OF_SOURCE) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1))); EventTime adEventTime = createEventTime( @@ -903,7 +906,10 @@ public void positionDiscontinuity_fromAdToContent_finishesAd() { /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs =*/ 10 * C.MICROS_PER_SECOND, - new AdPlaybackState(/* adGroupTimesUs=... */ 0, 5 * C.MICROS_PER_SECOND) + new AdPlaybackState( + /* adsId= */ new Object(), /* adGroupTimesUs=... */ + 0, + 5 * C.MICROS_PER_SECOND) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1))); EventTime adEventTime1 = @@ -983,7 +989,9 @@ public void positionDiscontinuity_fromContentToAd_doesNotFinishSessions() { /* isDynamic= */ false, /* durationUs =*/ 10 * C.MICROS_PER_SECOND, new AdPlaybackState( - /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND) + /* adsId= */ new Object(), /* adGroupTimesUs=... */ + 2 * C.MICROS_PER_SECOND, + 5 * C.MICROS_PER_SECOND) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1))); EventTime adEventTime1 = @@ -1032,7 +1040,10 @@ public void positionDiscontinuity_fromAdToAd_finishesPastAds_andNotifiesAdPlayba /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs =*/ 10 * C.MICROS_PER_SECOND, - new AdPlaybackState(/* adGroupTimesUs=... */ 0, 5 * C.MICROS_PER_SECOND) + new AdPlaybackState( + /* adsId= */ new Object(), /* adGroupTimesUs=... */ + 0, + 5 * C.MICROS_PER_SECOND) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1))); EventTime adEventTime1 = diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index 3a253b29761..de998bb8b16 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -31,12 +31,13 @@ public final class AdPlaybackStateTest { private static final long[] TEST_AD_GROUP_TMES_US = new long[] {0, C.msToUs(10_000)}; private static final Uri TEST_URI = Uri.EMPTY; + private static final Object TEST_ADS_ID = new Object(); private AdPlaybackState state; @Before public void setUp() { - state = new AdPlaybackState(TEST_AD_GROUP_TMES_US); + state = new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TMES_US); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java index 7fcd740d5f8..83386673af2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdsMediaSourceTest.java @@ -77,7 +77,7 @@ public final class AdsMediaSourceTest { CONTENT_TIMELINE.getUidOfPeriod(/* periodIndex= */ 0); private static final AdPlaybackState AD_PLAYBACK_STATE = - new AdPlaybackState(/* adGroupTimesUs...= */ 0) + new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0) .withContentDurationUs(CONTENT_DURATION_US) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index a5f94202da0..3fb29f284d5 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -246,7 +246,8 @@ public TimelineWindowDefinition( */ public static AdPlaybackState createAdPlaybackState(int adsPerAdGroup, long... adGroupTimesUs) { int adGroupCount = adGroupTimesUs.length; - AdPlaybackState adPlaybackState = new AdPlaybackState(adGroupTimesUs); + AdPlaybackState adPlaybackState = + new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs); long[][] adDurationsUs = new long[adGroupCount][]; for (int i = 0; i < adGroupCount; i++) { adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ i, adsPerAdGroup);