Pull IMA cuePoints -> adGroupTimesUs logic into a helper class

We're then able to use this same helper class from tests, to avoid
running into spurious failures caused by long microseconds being
round-tripped through float seconds.

PiperOrigin-RevId: 316435084
This commit is contained in:
ibaker 2020-06-15 11:54:43 +01:00 committed by Andrew Lewis
parent cd54e3e584
commit 9719b66d20
3 changed files with 118 additions and 80 deletions

View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.ima;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import java.util.Arrays;
import java.util.List;
/**
* Static utility class for constructing {@link AdPlaybackState} instances from IMA-specific data.
*/
/* package */ final class AdPlaybackStateFactory {
private AdPlaybackStateFactory() {}
/**
* Construct an {@link AdPlaybackState} from the provided {@code cuePoints}.
*
* @param cuePoints The cue points of the ads in seconds.
* @return The {@link AdPlaybackState}.
*/
public static AdPlaybackState fromCuePoints(List<Float> cuePoints) {
if (cuePoints.isEmpty()) {
// If no cue points are specified, there is a preroll ad.
return new AdPlaybackState(/* adGroupTimesUs...= */ 0);
}
int count = cuePoints.size();
long[] adGroupTimesUs = new long[count];
int adGroupIndex = 0;
for (int i = 0; i < count; i++) {
double cuePoint = cuePoints.get(i);
if (cuePoint == -1.0) {
adGroupTimesUs[count - 1] = C.TIME_END_OF_SOURCE;
} else {
adGroupTimesUs[adGroupIndex++] = (long) (C.MICROS_PER_SECOND * cuePoint);
}
}
// Cue points may be out of order, so sort them.
Arrays.sort(adGroupTimesUs, 0, adGroupIndex);
return new AdPlaybackState(adGroupTimesUs);
}
}

View file

@ -662,7 +662,7 @@ public final class ImaAdsLoader
adsManager.resume();
}
} else if (adsManager != null) {
adPlaybackState = new AdPlaybackState(getAdGroupTimesUs(adsManager.getAdCuePoints()));
adPlaybackState = AdPlaybackStateFactory.fromCuePoints(adsManager.getAdCuePoints());
updateAdPlaybackState();
} else {
// Ads haven't loaded yet, so request them.
@ -739,7 +739,7 @@ public final class ImaAdsLoader
if (player != null) {
// If a player is attached already, start playback immediately.
try {
adPlaybackState = new AdPlaybackState(getAdGroupTimesUs(adsManager.getAdCuePoints()));
adPlaybackState = AdPlaybackStateFactory.fromCuePoints(adsManager.getAdCuePoints());
hasAdPlaybackState = true;
updateAdPlaybackState();
} catch (RuntimeException e) {
@ -1554,28 +1554,6 @@ public final class ImaAdsLoader
: timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs());
}
private static long[] getAdGroupTimesUs(List<Float> cuePoints) {
if (cuePoints.isEmpty()) {
// If no cue points are specified, there is a preroll ad.
return new long[] {0};
}
int count = cuePoints.size();
long[] adGroupTimesUs = new long[count];
int adGroupIndex = 0;
for (int i = 0; i < count; i++) {
double cuePoint = cuePoints.get(i);
if (cuePoint == -1.0) {
adGroupTimesUs[count - 1] = C.TIME_END_OF_SOURCE;
} else {
adGroupTimesUs[adGroupIndex++] = (long) (C.MICROS_PER_SECOND * cuePoint);
}
}
// Cue points may be out of order, so sort them.
Arrays.sort(adGroupTimesUs, 0, adGroupIndex);
return adGroupTimesUs;
}
private static boolean isAdGroupLoadError(AdError adError) {
// TODO: Find out what other errors need to be handled (if any), and whether each one relates to
// a single ad, ad group or the whole timeline.

View file

@ -59,6 +59,7 @@ import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.time.Duration;
@ -94,7 +95,7 @@ public final class ImaAdsLoaderTest {
private static final Uri TEST_URI = Uri.EMPTY;
private static final AdMediaInfo TEST_AD_MEDIA_INFO = new AdMediaInfo(TEST_URI.toString());
private static final long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND;
private static final Float[] PREROLL_CUE_POINTS_SECONDS = new Float[] {0f};
private static final ImmutableList<Float> PREROLL_CUE_POINTS_SECONDS = ImmutableList.of(0f);
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@ -257,7 +258,7 @@ public final class ImaAdsLoaderTest {
@Test
public void playback_withPostrollFetchError_marksAdAsInErrorState() {
setupPlayback(CONTENT_TIMELINE, new Float[] {-1f});
setupPlayback(CONTENT_TIMELINE, ImmutableList.of(-1f));
// Simulate loading an empty postroll ad.
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -279,9 +280,8 @@ public final class ImaAdsLoaderTest {
long adGroupTimeUs =
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE,
new Float[] {(float) adGroupTimeUs / C.MICROS_PER_SECOND});
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
// Advance playback to just before the midroll and simulate buffering.
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -293,7 +293,7 @@ public final class ImaAdsLoaderTest {
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ adGroupTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@ -304,9 +304,8 @@ public final class ImaAdsLoaderTest {
long adGroupTimeUs =
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE,
new Float[] {(float) adGroupTimeUs / C.MICROS_PER_SECOND});
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
// Advance playback to just before the midroll and simulate buffering.
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -318,7 +317,7 @@ public final class ImaAdsLoaderTest {
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ adGroupTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
@ -330,8 +329,9 @@ public final class ImaAdsLoaderTest {
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE, new Float[] {0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND});
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -339,7 +339,7 @@ public final class ImaAdsLoaderTest {
verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble());
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@ -348,8 +348,9 @@ public final class ImaAdsLoaderTest {
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE, new Float[] {0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND});
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs));
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -362,7 +363,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withSkippedAdGroup(/* adGroupIndex= */ 0));
}
@ -372,8 +373,9 @@ public final class ImaAdsLoaderTest {
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE, new Float[] {0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND});
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -386,7 +388,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withSkippedAdGroup(/* adGroupIndex= */ 0));
}
@ -401,12 +403,11 @@ public final class ImaAdsLoaderTest {
long secondMidrollPeriodTimeUs =
secondMidrollWindowTimeUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE,
new Float[] {
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND
});
ImmutableList<Float> cuePoints =
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -414,8 +415,7 @@ public final class ImaAdsLoaderTest {
verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble());
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(
/* adGroupTimesUs...= */ firstMidrollPeriodTimeUs, secondMidrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@ -429,12 +429,11 @@ public final class ImaAdsLoaderTest {
long secondMidrollPeriodTimeUs =
secondMidrollWindowTimeUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
setupPlayback(
CONTENT_TIMELINE,
new Float[] {
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND
});
ImmutableList<Float> cuePoints =
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs));
imaAdsLoader.start(adsLoaderListener, adViewProvider);
@ -447,8 +446,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(
/* adGroupTimesUs...= */ firstMidrollPeriodTimeUs, secondMidrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withSkippedAdGroup(/* adGroupIndex= */ 0));
}
@ -458,9 +456,11 @@ public final class ImaAdsLoaderTest {
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
new Float[] {0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND},
cuePoints,
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
@ -478,7 +478,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withSkippedAdGroup(/* adGroupIndex= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@ -488,9 +488,11 @@ public final class ImaAdsLoaderTest {
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
new Float[] {0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND},
cuePoints,
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
@ -508,7 +510,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withSkippedAdGroup(/* adGroupIndex= */ 0));
}
@ -518,9 +520,11 @@ public final class ImaAdsLoaderTest {
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
new Float[] {0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND},
cuePoints,
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
@ -533,7 +537,7 @@ public final class ImaAdsLoaderTest {
verify(mockAdsManager).destroy();
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withSkippedAdGroup(/* adGroupIndex= */ 0)
.withSkippedAdGroup(/* adGroupIndex= */ 1));
@ -550,12 +554,13 @@ public final class ImaAdsLoaderTest {
long secondMidrollPeriodTimeUs =
secondMidrollWindowTimeUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
new Float[] {
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND
},
cuePoints,
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
@ -573,8 +578,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(
/* adGroupTimesUs...= */ firstMidrollPeriodTimeUs, secondMidrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withSkippedAdGroup(/* adGroupIndex= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@ -589,12 +593,13 @@ public final class ImaAdsLoaderTest {
long secondMidrollPeriodTimeUs =
secondMidrollWindowTimeUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
new Float[] {
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND
},
cuePoints,
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
@ -612,8 +617,7 @@ public final class ImaAdsLoaderTest {
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
new AdPlaybackState(
/* adGroupTimesUs...= */ firstMidrollPeriodTimeUs, secondMidrollPeriodTimeUs)
AdPlaybackStateFactory.fromCuePoints(cuePoints)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withSkippedAdGroup(/* adGroupIndex= */ 0));
}
@ -630,7 +634,7 @@ public final class ImaAdsLoaderTest {
inOrder.verify(mockAdDisplayContainer).unregisterAllVideoControlsOverlays();
}
private void setupPlayback(Timeline contentTimeline, Float[] cuePoints) {
private void setupPlayback(Timeline contentTimeline, List<Float> cuePoints) {
setupPlayback(
contentTimeline,
cuePoints,
@ -641,10 +645,10 @@ public final class ImaAdsLoaderTest {
}
private void setupPlayback(
Timeline contentTimeline, Float[] cuePoints, ImaAdsLoader imaAdsLoader) {
Timeline contentTimeline, List<Float> cuePoints, ImaAdsLoader imaAdsLoader) {
fakeExoPlayer = new FakePlayer();
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
when(mockAdsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
this.imaAdsLoader = imaAdsLoader;
imaAdsLoader.setPlayer(fakeExoPlayer);
}