diff --git a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java index 90fbeea96d..60ae1cd4b7 100644 --- a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java +++ b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java @@ -421,6 +421,7 @@ import java.util.Set; Map adPlaybackStates = new HashMap<>(); for (int i = adPlaybackState.removedAdGroupCount; i < adPlaybackState.adGroupCount; i++) { AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ i); + int mappedAdCount = 0; if (adGroup.timeUs == C.TIME_END_OF_SOURCE) { checkState(i == adPlaybackState.adGroupCount - 1); // The last ad group is a placeholder for a potential post roll. We can just stop here. @@ -454,8 +455,10 @@ import java.util.Set; adsId, adGroup, periodStartUs, periodDurationUs, window.isLive())); // Current period added as an ad period. Advance and look at the next period. periodIndex++; + mappedAdCount++; elapsedAdGroupAdDurationUs += periodDurationUs; - if (periodStartUs + periodDurationUs == adGroup.timeUs + adGroupDurationUs) { + if ((adGroup.originalCount > adGroup.count && adGroup.count == mappedAdCount) + || periodStartUs + periodDurationUs == adGroup.timeUs + adGroupDurationUs) { // Periods have consumed the ad group. We're at the end of the ad group. if (window.isLive()) { // Add elapsed ad duration to elapsed content duration for live streams to account diff --git a/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java b/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java index d0bf367819..e7c694bce5 100644 --- a/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java +++ b/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java @@ -853,6 +853,46 @@ public class ImaUtilTest { assertThat(adPlaybackStates.get("uid-14[a]").adGroupCount).isEqualTo(2); } + @Test + public void + splitAdPlaybackStateForPeriods_partialAdGroupEndingAtPeriodBeforeLast_adPeriodsCorrectlyDetected() { + AdPlaybackState adPlaybackState = + new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE) + .withIsServerSideInserted(/* adGroupIndex= */ 0, true); + FakeMultiPeriodLiveTimeline liveTimeline = + new FakeMultiPeriodLiveTimeline( + /* availabilityStartTimeMs= */ 0, + /* liveWindowDurationUs= */ 60_000_000, + /* nowUs= */ 70_000_000, + /* adSequencePattern= */ new boolean[] {false, true, true, true}, + /* periodDurationMsPattern= */ new long[] {30_000, 10_000, 10_000, 10_000}, + /* isContentTimeline= */ true, + /* populateAds= */ false, + /* playedAds= */ false); + // Ad event received from SDK around 30s. + adPlaybackState = + addLiveAdBreak( + /* currentContentPeriodPositionUs= */ 40_000_000, + /* adDurationUs= */ 10_000_000, + /* adPositionInAdPod= */ 2, + /* totalAdDurationUs= */ 30_000_000, + /* totalAdsInAdPod= */ 3, + adPlaybackState); + + ImmutableMap splitAdPlaybackStates = + ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, liveTimeline); + + assertThat(liveTimeline.getPeriodCount()).isEqualTo(5); + assertThat(splitAdPlaybackStates).hasSize(5); + assertThat(liveTimeline.getWindow(0, new Window()).windowStartTimeMs).isEqualTo(10_000L); + assertThat(liveTimeline.getPeriod(0, new Period()).positionInWindowUs).isEqualTo(-10_000_000L); + assertThat(splitAdPlaybackStates.get("uid-0[c]").adGroupCount).isEqualTo(1); + assertThat(splitAdPlaybackStates.get("uid-1[a]").adGroupCount).isEqualTo(1); + assertThat(splitAdPlaybackStates.get("uid-2[a]").adGroupCount).isEqualTo(2); + assertThat(splitAdPlaybackStates.get("uid-3[a]").adGroupCount).isEqualTo(2); + assertThat(splitAdPlaybackStates.get("uid-4[c]").adGroupCount).isEqualTo(1); + } + @Test public void splitAdPlaybackStateForPeriods_fullAdGroupAtBeginOfWindow_adPeriodsCorrectlyDetected() {