From 21fb8c9942a8fa179800ec0adcc5e42f2b34bede Mon Sep 17 00:00:00 2001 From: bachinger Date: Fri, 9 Jun 2023 16:30:28 +0000 Subject: [PATCH] Fix splitting ad playback state for partial ad group when joining This change addresses the case when the user joins the live stream on an ad period but the metadata for the ad period is not emitted. This results in inserting a partial ad group. In this case the ad group duration is longer than the partial ad group. If now the partial ad group ends at the period before the last period of the window (unknown duration), the splitting algorithm didn't recognize that the ad group already ended and made the last period wrongly an ad period. This change handles this edge case by counting the mapped ads in the partial ad group to detect this situation and stops splitting. #minor-release PiperOrigin-RevId: 539102785 (cherry picked from commit cd604e7ead3e4393dd6bf7b615df00b1b6da5d4a) --- .../media3/exoplayer/ima/ImaUtil.java | 5 ++- .../media3/exoplayer/ima/ImaUtilTest.java | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) 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() {