Skip to content

Commit

Permalink
Add method to reset ad group from final states to be playable again.
Browse files Browse the repository at this point in the history
The player will not play ads in final states (played, skipped, error)
again. To allow ads loader customizations to play ads again, we can
add a method that resets the state back to available or unavailable
(depending on whether we have the URI for the ad).

Issue: #9615
PiperOrigin-RevId: 411042842
  • Loading branch information
tonihei committed Nov 19, 2021
1 parent e64faf5 commit 4fd6d67
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
When a `DrmSessionManager` is used by an app in a custom `MediaSource`,
the `playbackLooper` needs to be passed to `DrmSessionManager.setPlayer`
instead.
* IMA:
* Add a method to `AdPlaybackState` to allow resetting an ad group so that
it can be played again
([#9615](https://github.com/google/ExoPlayer/issues/9615)).

### 2.16.1 (2021-11-18)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,28 @@ public AdGroup withAllAdsSkipped() {
timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}

/**
* Returns an instance with all ads in final states (played, skipped, error) reset to either
* available or unavailable, which allows to play them again.
*/
@CheckResult
public AdGroup withAllAdsReset() {
if (count == C.LENGTH_UNSET) {
return this;
}
int count = this.states.length;
@AdState int[] states = Arrays.copyOf(this.states, count);
for (int i = 0; i < count; i++) {
if (states[i] == AD_STATE_PLAYED
|| states[i] == AD_STATE_SKIPPED
|| states[i] == AD_STATE_ERROR) {
states[i] = uris[i] == null ? AD_STATE_UNAVAILABLE : AD_STATE_AVAILABLE;
}
}
return new AdGroup(
timeUs, count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}

@CheckResult
private static @AdState int[] copyStatesWithSpaceForAdCount(@AdState int[] states, int count) {
int oldStateCount = states.length;
Expand Down Expand Up @@ -783,6 +805,19 @@ public AdPlaybackState withIsServerSideInserted(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}

/**
* Returns an instance with all ads in the specified ad group reset from final states (played,
* skipped, error) to either available or unavailable, which allows to play them again.
*/
@CheckResult
public AdPlaybackState withResetAdGroup(@IntRange(from = 0) int adGroupIndex) {
int adjustedIndex = adGroupIndex - removedAdGroupCount;
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adjustedIndex] = adGroups[adjustedIndex].withAllAdsReset();
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}

@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
package com.google.android.exoplayer2.source.ads;

import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_AVAILABLE;
import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_ERROR;
import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_PLAYED;
import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_SKIPPED;
import static com.google.android.exoplayer2.source.ads.AdPlaybackState.AD_STATE_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -254,6 +257,60 @@ public void skipAllWithoutAdCount() {
assertThat(state.getAdGroup(1).count).isEqualTo(0);
}

@Test
public void withResetAdGroup_beforeSetAdCount_doesNothing() {
AdPlaybackState state = new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TIMES_US);

state = state.withResetAdGroup(/* adGroupIndex= */ 1);

assertThat(state.getAdGroup(1).count).isEqualTo(C.LENGTH_UNSET);
}

@Test
public void withResetAdGroup_resetsAdsInFinalStates() {
AdPlaybackState state = new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TIMES_US);
state = state.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 5);
state =
state.withAdDurationsUs(
/* adGroupIndex= */ 1, /* adDurationsUs...= */ 1_000L, 2_000L, 3_000L, 4_000L, 5_000L);
state = state.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1, Uri.EMPTY);
state = state.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2, Uri.EMPTY);
state = state.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 3, Uri.EMPTY);
state = state.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 4, Uri.EMPTY);
state = state.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2);
state = state.withSkippedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 3);
state = state.withAdLoadError(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 4);
// Verify setup.
assertThat(state.getAdGroup(/* adGroupIndex= */ 1).states)
.asList()
.containsExactly(
AD_STATE_UNAVAILABLE,
AD_STATE_AVAILABLE,
AD_STATE_PLAYED,
AD_STATE_SKIPPED,
AD_STATE_ERROR)
.inOrder();

state = state.withResetAdGroup(/* adGroupIndex= */ 1);

assertThat(state.getAdGroup(/* adGroupIndex= */ 1).states)
.asList()
.containsExactly(
AD_STATE_UNAVAILABLE,
AD_STATE_AVAILABLE,
AD_STATE_AVAILABLE,
AD_STATE_AVAILABLE,
AD_STATE_AVAILABLE)
.inOrder();
assertThat(state.getAdGroup(/* adGroupIndex= */ 1).uris)
.asList()
.containsExactly(null, Uri.EMPTY, Uri.EMPTY, Uri.EMPTY, Uri.EMPTY)
.inOrder();
assertThat(state.getAdGroup(/* adGroupIndex= */ 1).durationsUs)
.asList()
.containsExactly(1_000L, 2_000L, 3_000L, 4_000L, 5_000L);
}

@Test
public void roundTripViaBundle_yieldsEqualFieldsExceptAdsId() {
AdPlaybackState originalState =
Expand Down

0 comments on commit 4fd6d67

Please sign in to comment.