Allow skipping the ad before the start position
PiperOrigin-RevId: 315867160
This commit is contained in:
parent
e1beb1d194
commit
e111f850d0
@ -205,6 +205,7 @@
|
|||||||
([#6922](https://github.com/google/ExoPlayer/pull/6922)).
|
([#6922](https://github.com/google/ExoPlayer/pull/6922)).
|
||||||
* Cast extension: Implement playlist API and deprecate the old queue
|
* Cast extension: Implement playlist API and deprecate the old queue
|
||||||
manipulation API.
|
manipulation API.
|
||||||
|
* IMA extension: Add option to skip ads before the start position.
|
||||||
* Demo app: Retain previous position in list of samples.
|
* Demo app: Retain previous position in list of samples.
|
||||||
* Add Guava dependency.
|
* Add Guava dependency.
|
||||||
|
|
||||||
|
@ -123,6 +123,7 @@ public final class ImaAdsLoader
|
|||||||
private int mediaLoadTimeoutMs;
|
private int mediaLoadTimeoutMs;
|
||||||
private int mediaBitrate;
|
private int mediaBitrate;
|
||||||
private boolean focusSkipButtonWhenAvailable;
|
private boolean focusSkipButtonWhenAvailable;
|
||||||
|
private boolean playAdBeforeStartPosition;
|
||||||
private ImaFactory imaFactory;
|
private ImaFactory imaFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,6 +138,7 @@ public final class ImaAdsLoader
|
|||||||
mediaLoadTimeoutMs = TIMEOUT_UNSET;
|
mediaLoadTimeoutMs = TIMEOUT_UNSET;
|
||||||
mediaBitrate = BITRATE_UNSET;
|
mediaBitrate = BITRATE_UNSET;
|
||||||
focusSkipButtonWhenAvailable = true;
|
focusSkipButtonWhenAvailable = true;
|
||||||
|
playAdBeforeStartPosition = true;
|
||||||
imaFactory = new DefaultImaFactory();
|
imaFactory = new DefaultImaFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +252,21 @@ public final class ImaAdsLoader
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to play an ad before the start position when beginning playback. If {@code
|
||||||
|
* true}, an ad will be played if there is one at or before the start position. If {@code
|
||||||
|
* false}, an ad will be played only if there is one exactly at the start position. The default
|
||||||
|
* setting is {@code true}.
|
||||||
|
*
|
||||||
|
* @param playAdBeforeStartPosition Whether to play an ad before the start position when
|
||||||
|
* beginning playback.
|
||||||
|
* @return This builder, for convenience.
|
||||||
|
*/
|
||||||
|
public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) {
|
||||||
|
this.playAdBeforeStartPosition = playAdBeforeStartPosition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
/* package */ Builder setImaFactory(ImaFactory imaFactory) {
|
/* package */ Builder setImaFactory(ImaFactory imaFactory) {
|
||||||
this.imaFactory = Assertions.checkNotNull(imaFactory);
|
this.imaFactory = Assertions.checkNotNull(imaFactory);
|
||||||
@ -275,6 +292,7 @@ public final class ImaAdsLoader
|
|||||||
mediaLoadTimeoutMs,
|
mediaLoadTimeoutMs,
|
||||||
mediaBitrate,
|
mediaBitrate,
|
||||||
focusSkipButtonWhenAvailable,
|
focusSkipButtonWhenAvailable,
|
||||||
|
playAdBeforeStartPosition,
|
||||||
adUiElements,
|
adUiElements,
|
||||||
adEventListener,
|
adEventListener,
|
||||||
imaFactory);
|
imaFactory);
|
||||||
@ -298,6 +316,7 @@ public final class ImaAdsLoader
|
|||||||
mediaLoadTimeoutMs,
|
mediaLoadTimeoutMs,
|
||||||
mediaBitrate,
|
mediaBitrate,
|
||||||
focusSkipButtonWhenAvailable,
|
focusSkipButtonWhenAvailable,
|
||||||
|
playAdBeforeStartPosition,
|
||||||
adUiElements,
|
adUiElements,
|
||||||
adEventListener,
|
adEventListener,
|
||||||
imaFactory);
|
imaFactory);
|
||||||
@ -360,6 +379,7 @@ public final class ImaAdsLoader
|
|||||||
private final int vastLoadTimeoutMs;
|
private final int vastLoadTimeoutMs;
|
||||||
private final int mediaLoadTimeoutMs;
|
private final int mediaLoadTimeoutMs;
|
||||||
private final boolean focusSkipButtonWhenAvailable;
|
private final boolean focusSkipButtonWhenAvailable;
|
||||||
|
private final boolean playAdBeforeStartPosition;
|
||||||
private final int mediaBitrate;
|
private final int mediaBitrate;
|
||||||
@Nullable private final Set<UiElement> adUiElements;
|
@Nullable private final Set<UiElement> adUiElements;
|
||||||
@Nullable private final AdEventListener adEventListener;
|
@Nullable private final AdEventListener adEventListener;
|
||||||
@ -465,6 +485,7 @@ public final class ImaAdsLoader
|
|||||||
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
|
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
|
||||||
/* mediaBitrate= */ BITRATE_UNSET,
|
/* mediaBitrate= */ BITRATE_UNSET,
|
||||||
/* focusSkipButtonWhenAvailable= */ true,
|
/* focusSkipButtonWhenAvailable= */ true,
|
||||||
|
/* playAdBeforeStartPosition= */ true,
|
||||||
/* adUiElements= */ null,
|
/* adUiElements= */ null,
|
||||||
/* adEventListener= */ null,
|
/* adEventListener= */ null,
|
||||||
/* imaFactory= */ new DefaultImaFactory());
|
/* imaFactory= */ new DefaultImaFactory());
|
||||||
@ -481,6 +502,7 @@ public final class ImaAdsLoader
|
|||||||
int mediaLoadTimeoutMs,
|
int mediaLoadTimeoutMs,
|
||||||
int mediaBitrate,
|
int mediaBitrate,
|
||||||
boolean focusSkipButtonWhenAvailable,
|
boolean focusSkipButtonWhenAvailable,
|
||||||
|
boolean playAdBeforeStartPosition,
|
||||||
@Nullable Set<UiElement> adUiElements,
|
@Nullable Set<UiElement> adUiElements,
|
||||||
@Nullable AdEventListener adEventListener,
|
@Nullable AdEventListener adEventListener,
|
||||||
ImaFactory imaFactory) {
|
ImaFactory imaFactory) {
|
||||||
@ -492,6 +514,7 @@ public final class ImaAdsLoader
|
|||||||
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
|
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
|
||||||
this.mediaBitrate = mediaBitrate;
|
this.mediaBitrate = mediaBitrate;
|
||||||
this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable;
|
this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable;
|
||||||
|
this.playAdBeforeStartPosition = playAdBeforeStartPosition;
|
||||||
this.adUiElements = adUiElements;
|
this.adUiElements = adUiElements;
|
||||||
this.adEventListener = adEventListener;
|
this.adEventListener = adEventListener;
|
||||||
this.imaFactory = imaFactory;
|
this.imaFactory = imaFactory;
|
||||||
@ -671,15 +694,7 @@ public final class ImaAdsLoader
|
|||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
pendingAdRequestContext = null;
|
pendingAdRequestContext = null;
|
||||||
if (adsManager != null) {
|
destroyAdsManager();
|
||||||
adsManager.removeAdErrorListener(this);
|
|
||||||
adsManager.removeAdEventListener(this);
|
|
||||||
if (adEventListener != null) {
|
|
||||||
adsManager.removeAdEventListener(adEventListener);
|
|
||||||
}
|
|
||||||
adsManager.destroy();
|
|
||||||
adsManager = null;
|
|
||||||
}
|
|
||||||
adsLoader.removeAdsLoadedListener(/* adsLoadedListener= */ this);
|
adsLoader.removeAdsLoadedListener(/* adsLoadedListener= */ this);
|
||||||
adsLoader.removeAdErrorListener(/* adErrorListener= */ this);
|
adsLoader.removeAdErrorListener(/* adErrorListener= */ this);
|
||||||
imaPausedContent = false;
|
imaPausedContent = false;
|
||||||
@ -983,14 +998,19 @@ public final class ImaAdsLoader
|
|||||||
@Nullable AdsManager adsManager = this.adsManager;
|
@Nullable AdsManager adsManager = this.adsManager;
|
||||||
if (!isAdsManagerInitialized && adsManager != null) {
|
if (!isAdsManagerInitialized && adsManager != null) {
|
||||||
isAdsManagerInitialized = true;
|
isAdsManagerInitialized = true;
|
||||||
AdsRenderingSettings adsRenderingSettings = setupAdsRendering();
|
@Nullable AdsRenderingSettings adsRenderingSettings = setupAdsRendering();
|
||||||
|
if (adsRenderingSettings == null) {
|
||||||
|
// There are no ads to play.
|
||||||
|
destroyAdsManager();
|
||||||
|
} else {
|
||||||
adsManager.init(adsRenderingSettings);
|
adsManager.init(adsRenderingSettings);
|
||||||
adsManager.start();
|
adsManager.start();
|
||||||
updateAdPlaybackState();
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Initialized with ads rendering settings: " + adsRenderingSettings);
|
Log.d(TAG, "Initialized with ads rendering settings: " + adsRenderingSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateAdPlaybackState();
|
||||||
|
}
|
||||||
handleTimelineOrPositionChanged();
|
handleTimelineOrPositionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1063,7 +1083,11 @@ public final class ImaAdsLoader
|
|||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
/** Configures ads rendering for starting playback, returning the settings for the IMA SDK. */
|
/**
|
||||||
|
* Configures ads rendering for starting playback, returning the settings for the IMA SDK or
|
||||||
|
* {@code null} if no ads should play.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
private AdsRenderingSettings setupAdsRendering() {
|
private AdsRenderingSettings setupAdsRendering() {
|
||||||
AdsRenderingSettings adsRenderingSettings = imaFactory.createAdsRenderingSettings();
|
AdsRenderingSettings adsRenderingSettings = imaFactory.createAdsRenderingSettings();
|
||||||
adsRenderingSettings.setEnablePreloading(true);
|
adsRenderingSettings.setEnablePreloading(true);
|
||||||
@ -1083,28 +1107,44 @@ public final class ImaAdsLoader
|
|||||||
long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs;
|
long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs;
|
||||||
long contentPositionMs =
|
long contentPositionMs =
|
||||||
getContentPeriodPositionMs(Assertions.checkNotNull(player), timeline, period);
|
getContentPeriodPositionMs(Assertions.checkNotNull(player), timeline, period);
|
||||||
int adGroupIndexForPosition =
|
int adGroupForPositionIndex =
|
||||||
adPlaybackState.getAdGroupIndexForPositionUs(
|
adPlaybackState.getAdGroupIndexForPositionUs(
|
||||||
C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
|
C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
|
||||||
if (adGroupIndexForPosition != C.INDEX_UNSET) {
|
if (adGroupForPositionIndex != C.INDEX_UNSET) {
|
||||||
|
boolean playAdWhenStartingPlayback =
|
||||||
|
playAdBeforeStartPosition
|
||||||
|
|| adGroupTimesUs[adGroupForPositionIndex] == C.msToUs(contentPositionMs);
|
||||||
|
if (!playAdWhenStartingPlayback) {
|
||||||
|
adGroupForPositionIndex++;
|
||||||
|
} else if (hasMidrollAdGroups(adGroupTimesUs)) {
|
||||||
// Provide the player's initial position to trigger loading and playing the ad. If there are
|
// 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.
|
// no midrolls, we are playing a preroll and any pending content position wouldn't be
|
||||||
if (hasMidrollAdGroups(adGroupTimesUs)) {
|
// cleared.
|
||||||
pendingContentPositionMs = contentPositionMs;
|
pendingContentPositionMs = contentPositionMs;
|
||||||
}
|
}
|
||||||
if (adGroupIndexForPosition > 0) {
|
if (adGroupForPositionIndex > 0) {
|
||||||
// Skip any ad groups before the one at or immediately before the playback position.
|
for (int i = 0; i < adGroupForPositionIndex; i++) {
|
||||||
for (int i = 0; i < adGroupIndexForPosition; i++) {
|
|
||||||
adPlaybackState = adPlaybackState.withSkippedAdGroup(i);
|
adPlaybackState = adPlaybackState.withSkippedAdGroup(i);
|
||||||
}
|
}
|
||||||
// Play ads after the midpoint between the ad to play and the one before it, to avoid issues
|
if (adGroupForPositionIndex == adGroupTimesUs.length) {
|
||||||
// with rounding one of the two ad times.
|
// We don't need to play any ads. Because setPlayAdsAfterTime does not discard non-VMAP
|
||||||
long adGroupForPositionTimeUs = adGroupTimesUs[adGroupIndexForPosition];
|
// ads, we signal that no ads will render so the caller can destroy the ads manager.
|
||||||
long adGroupBeforeTimeUs = adGroupTimesUs[adGroupIndexForPosition - 1];
|
return null;
|
||||||
double midpointTimeUs = (adGroupForPositionTimeUs + adGroupBeforeTimeUs) / 2d;
|
}
|
||||||
|
long adGroupForPositionTimeUs = adGroupTimesUs[adGroupForPositionIndex];
|
||||||
|
long adGroupBeforePositionTimeUs = adGroupTimesUs[adGroupForPositionIndex - 1];
|
||||||
|
if (adGroupForPositionTimeUs == C.TIME_END_OF_SOURCE) {
|
||||||
|
// Play the postroll by offsetting the start position just past the last non-postroll ad.
|
||||||
|
adsRenderingSettings.setPlayAdsAfterTime(
|
||||||
|
(double) adGroupBeforePositionTimeUs / C.MICROS_PER_SECOND + 1d);
|
||||||
|
} else {
|
||||||
|
// Play ads after the midpoint between the ad to play and the one before it, to avoid
|
||||||
|
// issues with rounding one of the two ad times.
|
||||||
|
double midpointTimeUs = (adGroupForPositionTimeUs + adGroupBeforePositionTimeUs) / 2d;
|
||||||
adsRenderingSettings.setPlayAdsAfterTime(midpointTimeUs / C.MICROS_PER_SECOND);
|
adsRenderingSettings.setPlayAdsAfterTime(midpointTimeUs / C.MICROS_PER_SECOND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return adsRenderingSettings;
|
return adsRenderingSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1552,6 +1592,18 @@ public final class ImaAdsLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void destroyAdsManager() {
|
||||||
|
if (adsManager != null) {
|
||||||
|
adsManager.removeAdErrorListener(this);
|
||||||
|
adsManager.removeAdEventListener(this);
|
||||||
|
if (adEventListener != null) {
|
||||||
|
adsManager.removeAdEventListener(adEventListener);
|
||||||
|
}
|
||||||
|
adsManager.destroy();
|
||||||
|
adsManager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Factory for objects provided by the IMA SDK. */
|
/** Factory for objects provided by the IMA SDK. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
/* package */ interface ImaFactory {
|
/* package */ interface ImaFactory {
|
||||||
|
@ -453,6 +453,171 @@ public final class ImaAdsLoaderTest {
|
|||||||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resumePlaybackBeforeMidroll_withoutPlayAdBeforeStartPosition_skipsPreroll() {
|
||||||
|
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},
|
||||||
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setPlayAdBeforeStartPosition(false)
|
||||||
|
.setImaFactory(mockImaFactory)
|
||||||
|
.setImaSdkSettings(mockImaSdkSettings)
|
||||||
|
.buildForAdTag(TEST_URI));
|
||||||
|
|
||||||
|
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000);
|
||||||
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
|
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||||
|
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||||
|
double expectedPlayAdsAfterTimeUs = midrollPeriodTimeUs / 2d;
|
||||||
|
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||||
|
.isWithin(0.1d)
|
||||||
|
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||||
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
|
||||||
|
.withSkippedAdGroup(/* adGroupIndex= */ 0)
|
||||||
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resumePlaybackAtMidroll_withoutPlayAdBeforeStartPosition_skipsPreroll() {
|
||||||
|
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},
|
||||||
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setPlayAdBeforeStartPosition(false)
|
||||||
|
.setImaFactory(mockImaFactory)
|
||||||
|
.setImaSdkSettings(mockImaSdkSettings)
|
||||||
|
.buildForAdTag(TEST_URI));
|
||||||
|
|
||||||
|
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs));
|
||||||
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
|
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||||
|
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||||
|
double expectedPlayAdsAfterTimeUs = midrollPeriodTimeUs / 2d;
|
||||||
|
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||||
|
.isWithin(0.1d)
|
||||||
|
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||||
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
|
||||||
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||||
|
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resumePlaybackAfterMidroll_withoutPlayAdBeforeStartPosition_skipsMidroll() {
|
||||||
|
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},
|
||||||
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setPlayAdBeforeStartPosition(false)
|
||||||
|
.setImaFactory(mockImaFactory)
|
||||||
|
.setImaSdkSettings(mockImaSdkSettings)
|
||||||
|
.buildForAdTag(TEST_URI));
|
||||||
|
|
||||||
|
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000);
|
||||||
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
|
verify(mockAdsManager).destroy();
|
||||||
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
new AdPlaybackState(/* adGroupTimesUs...= */ 0, midrollPeriodTimeUs)
|
||||||
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||||
|
.withSkippedAdGroup(/* adGroupIndex= */ 0)
|
||||||
|
.withSkippedAdGroup(/* adGroupIndex= */ 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
resumePlaybackBeforeSecondMidroll_withoutPlayAdBeforeStartPosition_skipsFirstMidroll() {
|
||||||
|
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||||
|
long firstMidrollPeriodTimeUs =
|
||||||
|
firstMidrollWindowTimeUs
|
||||||
|
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||||
|
long secondMidrollWindowTimeUs = 4 * C.MICROS_PER_SECOND;
|
||||||
|
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
|
||||||
|
},
|
||||||
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setPlayAdBeforeStartPosition(false)
|
||||||
|
.setImaFactory(mockImaFactory)
|
||||||
|
.setImaSdkSettings(mockImaSdkSettings)
|
||||||
|
.buildForAdTag(TEST_URI));
|
||||||
|
|
||||||
|
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000);
|
||||||
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
|
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||||
|
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||||
|
double expectedPlayAdsAfterTimeUs = (firstMidrollPeriodTimeUs + secondMidrollPeriodTimeUs) / 2d;
|
||||||
|
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||||
|
.isWithin(0.1d)
|
||||||
|
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||||
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
new AdPlaybackState(
|
||||||
|
/* adGroupTimesUs...= */ firstMidrollPeriodTimeUs, secondMidrollPeriodTimeUs)
|
||||||
|
.withSkippedAdGroup(/* adGroupIndex= */ 0)
|
||||||
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resumePlaybackAtSecondMidroll_withoutPlayAdBeforeStartPosition_skipsFirstMidroll() {
|
||||||
|
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||||
|
long firstMidrollPeriodTimeUs =
|
||||||
|
firstMidrollWindowTimeUs
|
||||||
|
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||||
|
long secondMidrollWindowTimeUs = 4 * C.MICROS_PER_SECOND;
|
||||||
|
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
|
||||||
|
},
|
||||||
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setPlayAdBeforeStartPosition(false)
|
||||||
|
.setImaFactory(mockImaFactory)
|
||||||
|
.setImaSdkSettings(mockImaSdkSettings)
|
||||||
|
.buildForAdTag(TEST_URI));
|
||||||
|
|
||||||
|
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs));
|
||||||
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
|
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||||
|
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||||
|
double expectedPlayAdsAfterTimeUs = (firstMidrollPeriodTimeUs + secondMidrollPeriodTimeUs) / 2d;
|
||||||
|
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||||
|
.isWithin(0.1d)
|
||||||
|
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||||
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
new AdPlaybackState(
|
||||||
|
/* adGroupTimesUs...= */ firstMidrollPeriodTimeUs, secondMidrollPeriodTimeUs)
|
||||||
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||||
|
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stop_unregistersAllVideoControlOverlays() {
|
public void stop_unregistersAllVideoControlOverlays() {
|
||||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||||
@ -466,14 +631,21 @@ public final class ImaAdsLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupPlayback(Timeline contentTimeline, Float[] cuePoints) {
|
private void setupPlayback(Timeline contentTimeline, Float[] cuePoints) {
|
||||||
fakeExoPlayer = new FakePlayer();
|
setupPlayback(
|
||||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
|
contentTimeline,
|
||||||
when(mockAdsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
|
cuePoints,
|
||||||
imaAdsLoader =
|
|
||||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
.setImaFactory(mockImaFactory)
|
.setImaFactory(mockImaFactory)
|
||||||
.setImaSdkSettings(mockImaSdkSettings)
|
.setImaSdkSettings(mockImaSdkSettings)
|
||||||
.buildForAdTag(TEST_URI);
|
.buildForAdTag(TEST_URI));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPlayback(
|
||||||
|
Timeline contentTimeline, Float[] cuePoints, ImaAdsLoader imaAdsLoader) {
|
||||||
|
fakeExoPlayer = new FakePlayer();
|
||||||
|
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
|
||||||
|
when(mockAdsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
|
||||||
|
this.imaAdsLoader = imaAdsLoader;
|
||||||
imaAdsLoader.setPlayer(fakeExoPlayer);
|
imaAdsLoader.setPlayer(fakeExoPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user