mirror of
https://github.com/samsonjs/media.git
synced 2026-03-29 10:05:48 +00:00
commit
1a5b304b1f
11 changed files with 1079 additions and 460 deletions
|
|
@ -1,5 +1,27 @@
|
|||
# Release notes #
|
||||
|
||||
### 2.11.6 (2020-06-24) ###
|
||||
|
||||
* UI: Prevent `PlayerView` from temporarily hiding the video surface when
|
||||
seeking to an unprepared period within the current window. For example when
|
||||
seeking over an ad group, or to the next period in a multi-period DASH
|
||||
stream ([#5507](https://github.com/google/ExoPlayer/issues/5507)).
|
||||
* IMA extension:
|
||||
* Add option to skip ads before the start position.
|
||||
* Catch unexpected errors in `stopAd` to avoid a crash
|
||||
([#7492](https://github.com/google/ExoPlayer/issues/7492)).
|
||||
* Fix a bug that caused playback to be stuck buffering on resuming from
|
||||
the background after all ads had played to the end
|
||||
([#7508](https://github.com/google/ExoPlayer/issues/7508)).
|
||||
* Fix a bug where the number of ads in an ad group couldn't change
|
||||
([#7477](https://github.com/google/ExoPlayer/issues/7477)).
|
||||
* Work around unexpected `pauseAd`/`stopAd` for ads that have preloaded
|
||||
on seeking to another position
|
||||
([#7492](https://github.com/google/ExoPlayer/issues/7492)).
|
||||
* Fix incorrect rounding of ad cue points.
|
||||
* Fix handling of postrolls preloading
|
||||
([#7518](https://github.com/google/ExoPlayer/issues/7518)).
|
||||
|
||||
### 2.11.5 (2020-06-05) ###
|
||||
|
||||
* Improve the smoothness of video playback immediately after starting, seeking
|
||||
|
|
@ -16,10 +38,10 @@
|
|||
([#7306](https://github.com/google/ExoPlayer/issues/7306)).
|
||||
* Fix issue in `AudioTrackPositionTracker` that could cause negative positions
|
||||
to be reported at the start of playback and immediately after seeking
|
||||
([#7456](https://github.com/google/ExoPlayer/issues/7456).
|
||||
([#7456](https://github.com/google/ExoPlayer/issues/7456)).
|
||||
* Fix further cases where downloads would sometimes not resume after their
|
||||
network requirements are met
|
||||
([#7453](https://github.com/google/ExoPlayer/issues/7453).
|
||||
([#7453](https://github.com/google/ExoPlayer/issues/7453)).
|
||||
* DASH:
|
||||
* Merge trick play adaptation sets (i.e., adaptation sets marked with
|
||||
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
|
||||
|
|
@ -99,10 +121,10 @@
|
|||
`DefaultAudioSink` constructor
|
||||
([#7134](https://github.com/google/ExoPlayer/issues/7134)).
|
||||
* Workaround issue that could cause slower than realtime playback of AAC on
|
||||
Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671).
|
||||
Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671)).
|
||||
* Fix case where another app spuriously holding transient audio focus could
|
||||
prevent ExoPlayer from acquiring audio focus for an indefinite period of
|
||||
time ([#7182](https://github.com/google/ExoPlayer/issues/7182).
|
||||
time ([#7182](https://github.com/google/ExoPlayer/issues/7182)).
|
||||
* Fix case where the player volume could be permanently ducked if audio focus
|
||||
was released whilst ducking.
|
||||
* Fix playback of WAV files with trailing non-media bytes
|
||||
|
|
@ -1022,7 +1044,7 @@
|
|||
([#4492](https://github.com/google/ExoPlayer/issues/4492) and
|
||||
[#4634](https://github.com/google/ExoPlayer/issues/4634)).
|
||||
* Fix issue where removing looping media from a playlist throws an exception
|
||||
([#4871](https://github.com/google/ExoPlayer/issues/4871).
|
||||
([#4871](https://github.com/google/ExoPlayer/issues/4871)).
|
||||
* Fix issue where the preferred audio or text track would not be selected if
|
||||
mapped onto a secondary renderer of the corresponding type
|
||||
([#4711](http://github.com/google/ExoPlayer/issues/4711)).
|
||||
|
|
@ -1439,7 +1461,7 @@
|
|||
resources when the playback thread has quit by the time the loading task has
|
||||
completed.
|
||||
* ID3: Better handle malformed ID3 data
|
||||
([#3792](https://github.com/google/ExoPlayer/issues/3792).
|
||||
([#3792](https://github.com/google/ExoPlayer/issues/3792)).
|
||||
* Support 14-bit mode and little endianness in DTS PES packets
|
||||
([#3340](https://github.com/google/ExoPlayer/issues/3340)).
|
||||
* Demo app: Add ability to download not DRM protected content.
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
// limitations under the License.
|
||||
project.ext {
|
||||
// ExoPlayer version and version code.
|
||||
releaseVersion = '2.11.5'
|
||||
releaseVersionCode = 2011005
|
||||
releaseVersion = '2.11.6'
|
||||
releaseVersionCode = 2011006
|
||||
minSdkVersion = 16
|
||||
appTargetSdkVersion = 29
|
||||
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
|
||||
|
|
|
|||
|
|
@ -476,6 +476,11 @@
|
|||
"name": "VMAP full, empty, full midrolls",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll-2"
|
||||
},
|
||||
{
|
||||
"name": "VMAP midroll at 1765 s",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4",
|
||||
"ad_tag_uri": "https://vastsynthesizer.appspot.com/midroll-large"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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++] = Math.round(C.MICROS_PER_SECOND * cuePoint);
|
||||
}
|
||||
}
|
||||
// Cue points may be out of order, so sort them.
|
||||
Arrays.sort(adGroupTimesUs, 0, adGroupIndex);
|
||||
return new AdPlaybackState(adGroupTimesUs);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -17,10 +17,12 @@ package com.google.android.exoplayer2.ext.ima;
|
|||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
|
@ -42,6 +44,8 @@ import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
|
|||
import com.google.ads.interactivemedia.v3.api.AdsRequest;
|
||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||
import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo;
|
||||
import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider;
|
||||
import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
|
|
@ -56,6 +60,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;
|
||||
|
|
@ -91,8 +96,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 long[][] ADS_DURATIONS_US = new long[][] {{TEST_AD_DURATION_US}};
|
||||
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();
|
||||
|
||||
|
|
@ -111,6 +115,9 @@ public final class ImaAdsLoaderTest {
|
|||
private ViewGroup adViewGroup;
|
||||
private View adOverlayView;
|
||||
private AdsLoader.AdViewProvider adViewProvider;
|
||||
private AdEvent.AdEventListener adEventListener;
|
||||
private ContentProgressProvider contentProgressProvider;
|
||||
private VideoAdPlayer videoAdPlayer;
|
||||
private TestAdsLoaderListener adsLoaderListener;
|
||||
private FakePlayer fakeExoPlayer;
|
||||
private ImaAdsLoader imaAdsLoader;
|
||||
|
|
@ -144,14 +151,14 @@ public final class ImaAdsLoaderTest {
|
|||
@Test
|
||||
public void builder_overridesPlayerType() {
|
||||
when(mockImaSdkSettings.getPlayerType()).thenReturn("test player type");
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
|
||||
verify(mockImaSdkSettings).setPlayerType("google/exo.ext.ima");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void start_setsAdUiViewGroup() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
verify(mockAdDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup);
|
||||
|
|
@ -161,7 +168,7 @@ public final class ImaAdsLoaderTest {
|
|||
@Test
|
||||
public void start_withPlaceholderContent_initializedAdsLoader() {
|
||||
Timeline placeholderTimeline = new DummyTimeline(/* tag= */ null);
|
||||
setupPlayback(placeholderTimeline, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(placeholderTimeline, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
// We'll only create the rendering settings when initializing the ads loader.
|
||||
|
|
@ -170,26 +177,27 @@ public final class ImaAdsLoaderTest {
|
|||
|
||||
@Test
|
||||
public void start_updatesAdPlaybackState() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
new AdPlaybackState(/* adGroupTimesUs...= */ 0)
|
||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startAfterRelease() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.release();
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startAndCallbacksAfterRelease() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
// Request ads in order to get a reference to the ad event listener.
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.release();
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
|
||||
|
|
@ -200,47 +208,47 @@ public final class ImaAdsLoaderTest {
|
|||
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
|
||||
// SDK being proguarded.
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
imaAdsLoader.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
imaAdsLoader.playAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
imaAdsLoader.pauseAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.stopAd(TEST_AD_MEDIA_INFO);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
videoAdPlayer.pauseAd(TEST_AD_MEDIA_INFO);
|
||||
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.onPlayerError(ExoPlaybackException.createForSource(new IOException()));
|
||||
imaAdsLoader.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
imaAdsLoader.handlePrepareError(
|
||||
/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, new IOException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_withPrerollAd_marksAdAsPlayed() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
|
||||
// Load the preroll ad.
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
imaAdsLoader.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
|
||||
// Play the preroll ad.
|
||||
imaAdsLoader.playAd(TEST_AD_MEDIA_INFO);
|
||||
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
|
||||
fakeExoPlayer.setPlayingAdPosition(
|
||||
/* adGroupIndex= */ 0,
|
||||
/* adIndexInAdGroup= */ 0,
|
||||
/* position= */ 0,
|
||||
/* contentPosition= */ 0);
|
||||
fakeExoPlayer.setState(Player.STATE_READY, true);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
|
||||
|
||||
// Play the content.
|
||||
fakeExoPlayer.setPlayingContentPosition(0);
|
||||
imaAdsLoader.stopAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
|
||||
// Verify that the preroll ad has been marked as played.
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
|
|
@ -249,24 +257,24 @@ public final class ImaAdsLoaderTest {
|
|||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* uri= */ TEST_URI)
|
||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
||||
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_withPostrollFetchError_marksAdAsInErrorState() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, new Float[] {-1f});
|
||||
setupPlayback(CONTENT_TIMELINE, ImmutableList.of(-1f));
|
||||
|
||||
// Simulate loading an empty postroll ad.
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.onAdEvent(mockPostrollFetchErrorAdEvent);
|
||||
adEventListener.onAdEvent(mockPostrollFetchErrorAdEvent);
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
new AdPlaybackState(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
||||
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||
}
|
||||
|
|
@ -275,10 +283,9 @@ public final class ImaAdsLoaderTest {
|
|||
public void playback_withAdNotPreloadingBeforeTimeout_hasNoError() {
|
||||
// Simulate an ad at 2 seconds.
|
||||
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
ADS_DURATIONS_US,
|
||||
new Float[] {(float) adGroupPositionInWindowUs / C.MICROS_PER_SECOND});
|
||||
long adGroupTimeUs = adGroupPositionInWindowUs;
|
||||
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);
|
||||
|
|
@ -286,23 +293,21 @@ public final class ImaAdsLoaderTest {
|
|||
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||
// Advance before the timeout and simulating polling content progress.
|
||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(1));
|
||||
imaAdsLoader.getContentProgress();
|
||||
contentProgressProvider.getContentProgress();
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
new AdPlaybackState(/* adGroupTimesUs...= */ adGroupPositionInWindowUs)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdDurationsUs(ADS_DURATIONS_US));
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() {
|
||||
// Simulate an ad at 2 seconds.
|
||||
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
ADS_DURATIONS_US,
|
||||
new Float[] {(float) adGroupPositionInWindowUs / C.MICROS_PER_SECOND});
|
||||
long adGroupTimeUs = adGroupPositionInWindowUs;
|
||||
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);
|
||||
|
|
@ -310,20 +315,296 @@ public final class ImaAdsLoaderTest {
|
|||
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||
// Advance past the timeout and simulate polling content progress.
|
||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
|
||||
imaAdsLoader.getContentProgress();
|
||||
contentProgressProvider.getContentProgress();
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
new AdPlaybackState(/* adGroupTimesUs...= */ adGroupPositionInWindowUs)
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
||||
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackBeforeMidroll_playsPreroll() {
|
||||
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long midrollPeriodTimeUs = midrollWindowTimeUs;
|
||||
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);
|
||||
|
||||
verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble());
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackAtMidroll_skipsPreroll() {
|
||||
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long midrollPeriodTimeUs = midrollWindowTimeUs;
|
||||
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);
|
||||
|
||||
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||
double expectedPlayAdsAfterTimeUs = midrollPeriodTimeUs / 2d;
|
||||
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||
.isWithin(0.1)
|
||||
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackAfterMidroll_skipsPreroll() {
|
||||
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long midrollPeriodTimeUs = midrollWindowTimeUs;
|
||||
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);
|
||||
|
||||
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||
double expectedPlayAdsAfterTimeUs = midrollPeriodTimeUs / 2d;
|
||||
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||
.isWithin(0.1)
|
||||
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackBeforeSecondMidroll_playsFirstMidroll() {
|
||||
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long firstMidrollPeriodTimeUs = firstMidrollWindowTimeUs;
|
||||
long secondMidrollWindowTimeUs = 4 * C.MICROS_PER_SECOND;
|
||||
long secondMidrollPeriodTimeUs = secondMidrollWindowTimeUs;
|
||||
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);
|
||||
|
||||
verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble());
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackAtSecondMidroll_skipsFirstMidroll() {
|
||||
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long firstMidrollPeriodTimeUs = firstMidrollWindowTimeUs;
|
||||
long secondMidrollWindowTimeUs = 4 * C.MICROS_PER_SECOND;
|
||||
long secondMidrollPeriodTimeUs = secondMidrollWindowTimeUs;
|
||||
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);
|
||||
|
||||
ArgumentCaptor<Double> playAdsAfterTimeCaptor = ArgumentCaptor.forClass(Double.class);
|
||||
verify(mockAdsRenderingSettings).setPlayAdsAfterTime(playAdsAfterTimeCaptor.capture());
|
||||
double expectedPlayAdsAfterTimeUs = (firstMidrollPeriodTimeUs + secondMidrollPeriodTimeUs) / 2d;
|
||||
assertThat(playAdsAfterTimeCaptor.getValue())
|
||||
.isWithin(0.1)
|
||||
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackBeforeMidroll_withoutPlayAdBeforeStartPosition_skipsPreroll() {
|
||||
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long midrollPeriodTimeUs = midrollWindowTimeUs;
|
||||
ImmutableList<Float> cuePoints =
|
||||
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
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(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackAtMidroll_withoutPlayAdBeforeStartPosition_skipsPreroll() {
|
||||
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long midrollPeriodTimeUs = midrollWindowTimeUs;
|
||||
ImmutableList<Float> cuePoints =
|
||||
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
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(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackAfterMidroll_withoutPlayAdBeforeStartPosition_skipsMidroll() {
|
||||
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long midrollPeriodTimeUs = midrollWindowTimeUs;
|
||||
ImmutableList<Float> cuePoints =
|
||||
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
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(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.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;
|
||||
long secondMidrollWindowTimeUs = 4 * C.MICROS_PER_SECOND;
|
||||
long secondMidrollPeriodTimeUs = secondMidrollWindowTimeUs;
|
||||
ImmutableList<Float> cuePoints =
|
||||
ImmutableList.of(
|
||||
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
|
||||
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
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(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumePlaybackAtSecondMidroll_withoutPlayAdBeforeStartPosition_skipsFirstMidroll() {
|
||||
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
|
||||
long firstMidrollPeriodTimeUs = firstMidrollWindowTimeUs;
|
||||
long secondMidrollWindowTimeUs = 4 * C.MICROS_PER_SECOND;
|
||||
long secondMidrollPeriodTimeUs = secondMidrollWindowTimeUs;
|
||||
ImmutableList<Float> cuePoints =
|
||||
ImmutableList.of(
|
||||
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
|
||||
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
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(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stop_unregistersAllVideoControlOverlays() {
|
||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.stop();
|
||||
|
|
@ -333,15 +614,71 @@ public final class ImaAdsLoaderTest {
|
|||
inOrder.verify(mockAdDisplayContainer).unregisterAllVideoControlsOverlays();
|
||||
}
|
||||
|
||||
private void setupPlayback(Timeline contentTimeline, long[][] adDurationsUs, Float[] cuePoints) {
|
||||
fakeExoPlayer = new FakePlayer();
|
||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
|
||||
imaAdsLoader =
|
||||
@Test
|
||||
public void loadAd_withLargeAdCuePoint_updatesAdPlaybackStateWithLoadedAd() {
|
||||
float midrollTimeSecs = 1_765f;
|
||||
ImmutableList<Float> cuePoints = ImmutableList.of(midrollTimeSecs);
|
||||
setupPlayback(CONTENT_TIMELINE, cuePoints);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
videoAdPlayer.loadAd(
|
||||
TEST_AD_MEDIA_INFO,
|
||||
new AdPodInfo() {
|
||||
@Override
|
||||
public int getTotalAds() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAdPosition() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBumper() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxDuration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPodIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTimeOffset() {
|
||||
return midrollTimeSecs;
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI)
|
||||
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}}));
|
||||
}
|
||||
|
||||
private void setupPlayback(Timeline contentTimeline, List<Float> cuePoints) {
|
||||
setupPlayback(
|
||||
contentTimeline,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI);
|
||||
.buildForAdTag(TEST_URI));
|
||||
}
|
||||
|
||||
private void setupPlayback(
|
||||
Timeline contentTimeline, List<Float> cuePoints, ImaAdsLoader imaAdsLoader) {
|
||||
fakeExoPlayer = new FakePlayer();
|
||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
|
||||
this.imaAdsLoader = imaAdsLoader;
|
||||
imaAdsLoader.setPlayer(fakeExoPlayer);
|
||||
}
|
||||
|
||||
|
|
@ -349,9 +686,11 @@ public final class ImaAdsLoaderTest {
|
|||
ArgumentCaptor<Object> userRequestContextCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
doNothing().when(mockAdsRequest).setUserRequestContext(userRequestContextCaptor.capture());
|
||||
when(mockAdsRequest.getUserRequestContext())
|
||||
.thenAnswer((Answer<Object>) invocation -> userRequestContextCaptor.getValue());
|
||||
.thenAnswer(invocation -> userRequestContextCaptor.getValue());
|
||||
List<com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener> adsLoadedListeners =
|
||||
new ArrayList<>();
|
||||
// Deliberately don't handle removeAdsLoadedListener to allow testing behavior if the IMA SDK
|
||||
// invokes callbacks after release.
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
adsLoadedListeners.add(invocation.getArgument(0));
|
||||
|
|
@ -359,13 +698,6 @@ public final class ImaAdsLoaderTest {
|
|||
})
|
||||
.when(mockAdsLoader)
|
||||
.addAdsLoadedListener(any());
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
adsLoadedListeners.remove(invocation.getArgument(0));
|
||||
return null;
|
||||
})
|
||||
.when(mockAdsLoader)
|
||||
.removeAdsLoadedListener(any());
|
||||
when(mockAdsManagerLoadedEvent.getAdsManager()).thenReturn(mockAdsManager);
|
||||
when(mockAdsManagerLoadedEvent.getUserRequestContext())
|
||||
.thenAnswer(invocation -> mockAdsRequest.getUserRequestContext());
|
||||
|
|
@ -381,6 +713,30 @@ public final class ImaAdsLoaderTest {
|
|||
.when(mockAdsLoader)
|
||||
.requestAds(mockAdsRequest);
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
adEventListener = invocation.getArgument(0);
|
||||
return null;
|
||||
})
|
||||
.when(mockAdsManager)
|
||||
.addAdEventListener(any());
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
contentProgressProvider = invocation.getArgument(0);
|
||||
return null;
|
||||
})
|
||||
.when(mockAdsRequest)
|
||||
.setContentProgressProvider(any());
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
videoAdPlayer = invocation.getArgument(0);
|
||||
return null;
|
||||
})
|
||||
.when(mockAdDisplayContainer)
|
||||
.setPlayer(any());
|
||||
|
||||
when(mockImaFactory.createAdDisplayContainer()).thenReturn(mockAdDisplayContainer);
|
||||
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
|
||||
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
|
||||
|
|
@ -422,19 +778,21 @@ public final class ImaAdsLoaderTest {
|
|||
|
||||
private final FakePlayer fakeExoPlayer;
|
||||
private final Timeline contentTimeline;
|
||||
private final long[][] adDurationsUs;
|
||||
|
||||
public AdPlaybackState adPlaybackState;
|
||||
|
||||
public TestAdsLoaderListener(
|
||||
FakePlayer fakeExoPlayer, Timeline contentTimeline, long[][] adDurationsUs) {
|
||||
public TestAdsLoaderListener(FakePlayer fakeExoPlayer, Timeline contentTimeline) {
|
||||
this.fakeExoPlayer = fakeExoPlayer;
|
||||
this.contentTimeline = contentTimeline;
|
||||
this.adDurationsUs = adDurationsUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdPlaybackState(AdPlaybackState adPlaybackState) {
|
||||
long[][] adDurationsUs = new long[adPlaybackState.adGroupCount][];
|
||||
for (int adGroupIndex = 0; adGroupIndex < adPlaybackState.adGroupCount; adGroupIndex++) {
|
||||
adDurationsUs[adGroupIndex] = new long[adPlaybackState.adGroups[adGroupIndex].uris.length];
|
||||
Arrays.fill(adDurationsUs[adGroupIndex], TEST_AD_DURATION_US);
|
||||
}
|
||||
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
|
||||
this.adPlaybackState = adPlaybackState;
|
||||
fakeExoPlayer.updateTimeline(
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
|
|||
|
||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||
public static final String VERSION = "2.11.5";
|
||||
public static final String VERSION = "2.11.6";
|
||||
|
||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.5";
|
||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.6";
|
||||
|
||||
/**
|
||||
* The version of the library expressed as an integer, for example 1002003.
|
||||
|
|
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
|
|||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final int VERSION_INT = 2011005;
|
||||
public static final int VERSION_INT = 2011006;
|
||||
|
||||
/**
|
||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||
|
|
|
|||
|
|
@ -124,13 +124,9 @@ public final class AdPlaybackState {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance with the ad count set to {@code count}. This method may only be called
|
||||
* if this instance's ad count has not yet been specified.
|
||||
*/
|
||||
/** Returns a new instance with the ad count set to {@code count}. */
|
||||
@CheckResult
|
||||
public AdGroup withAdCount(int count) {
|
||||
Assertions.checkArgument(this.count == C.LENGTH_UNSET && states.length <= count);
|
||||
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
|
||||
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
|
||||
@NullableType Uri[] uris = Arrays.copyOf(this.uris, count);
|
||||
|
|
@ -139,17 +135,11 @@ public final class AdPlaybackState {
|
|||
|
||||
/**
|
||||
* Returns a new instance with the specified {@code uri} set for the specified ad, and the ad
|
||||
* marked as {@link #AD_STATE_AVAILABLE}. The specified ad must currently be in {@link
|
||||
* #AD_STATE_UNAVAILABLE}, which is the default state.
|
||||
*
|
||||
* <p>This instance's ad count may be unknown, in which case {@code index} must be less than the
|
||||
* ad count specified later. Otherwise, {@code index} must be less than the current ad count.
|
||||
* marked as {@link #AD_STATE_AVAILABLE}.
|
||||
*/
|
||||
@CheckResult
|
||||
public AdGroup withAdUri(Uri uri, int index) {
|
||||
Assertions.checkArgument(count == C.LENGTH_UNSET || index < count);
|
||||
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1);
|
||||
Assertions.checkArgument(states[index] == AD_STATE_UNAVAILABLE);
|
||||
long[] durationsUs =
|
||||
this.durationsUs.length == states.length
|
||||
? this.durationsUs
|
||||
|
|
@ -280,7 +270,9 @@ public final class AdPlaybackState {
|
|||
public final AdGroup[] adGroups;
|
||||
/** The position offset in the first unplayed ad at which to begin playback, in microseconds. */
|
||||
public final long adResumePositionUs;
|
||||
/** The content duration in microseconds, if known. {@link C#TIME_UNSET} otherwise. */
|
||||
/**
|
||||
* The duration of the content period in microseconds, if known. {@link C#TIME_UNSET} otherwise.
|
||||
*/
|
||||
public final long contentDurationUs;
|
||||
|
||||
/**
|
||||
|
|
@ -489,6 +481,54 @@ public final class AdPlaybackState {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("AdPlaybackState(adResumePositionUs=");
|
||||
sb.append(adResumePositionUs);
|
||||
sb.append(", adGroups=[");
|
||||
for (int i = 0; i < adGroups.length; i++) {
|
||||
sb.append("adGroup(timeUs=");
|
||||
sb.append(adGroupTimesUs[i]);
|
||||
sb.append(", ads=[");
|
||||
for (int j = 0; j < adGroups[i].states.length; j++) {
|
||||
sb.append("ad(state=");
|
||||
switch (adGroups[i].states[j]) {
|
||||
case AD_STATE_UNAVAILABLE:
|
||||
sb.append('_');
|
||||
break;
|
||||
case AD_STATE_ERROR:
|
||||
sb.append('!');
|
||||
break;
|
||||
case AD_STATE_AVAILABLE:
|
||||
sb.append('R');
|
||||
break;
|
||||
case AD_STATE_PLAYED:
|
||||
sb.append('P');
|
||||
break;
|
||||
case AD_STATE_SKIPPED:
|
||||
sb.append('S');
|
||||
break;
|
||||
default:
|
||||
sb.append('?');
|
||||
break;
|
||||
}
|
||||
sb.append(", durationUs=");
|
||||
sb.append(adGroups[i].durationsUs[j]);
|
||||
sb.append(')');
|
||||
if (j < adGroups[i].states.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("])");
|
||||
if (i < adGroups.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("])");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean isPositionBeforeAdGroup(
|
||||
long positionUs, long periodDurationUs, int adGroupIndex) {
|
||||
if (positionUs == C.TIME_END_OF_SOURCE) {
|
||||
|
|
|
|||
|
|
@ -44,23 +44,16 @@ public final class SinglePeriodAdTimeline extends ForwardingTimeline {
|
|||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
timeline.getPeriod(periodIndex, period, setIds);
|
||||
long durationUs =
|
||||
period.durationUs == C.TIME_UNSET ? adPlaybackState.contentDurationUs : period.durationUs;
|
||||
period.set(
|
||||
period.id,
|
||||
period.uid,
|
||||
period.windowIndex,
|
||||
period.durationUs,
|
||||
durationUs,
|
||||
period.getPositionInWindowUs(),
|
||||
adPlaybackState);
|
||||
return period;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||
window = super.getWindow(windowIndex, window, defaultPositionProjectionUs);
|
||||
if (window.durationUs == C.TIME_UNSET) {
|
||||
window.durationUs = adPlaybackState.contentDurationUs;
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,12 +155,22 @@ public final class MimeTypes {
|
|||
if (mimeType == null) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Consider adding additional audio MIME types here.
|
||||
// TODO: Add additional audio MIME types. Also consider evaluating based on Format rather than
|
||||
// just MIME type, since in some cases the property is true for a subset of the profiles
|
||||
// belonging to a single MIME type. If we do this, we should move the method to a different
|
||||
// class. See [Internal ref: http://go/exo-audio-format-random-access].
|
||||
switch (mimeType) {
|
||||
case AUDIO_AAC:
|
||||
case AUDIO_MPEG:
|
||||
case AUDIO_MPEG_L1:
|
||||
case AUDIO_MPEG_L2:
|
||||
case AUDIO_RAW:
|
||||
case AUDIO_ALAW:
|
||||
case AUDIO_MLAW:
|
||||
case AUDIO_OPUS:
|
||||
case AUDIO_FLAC:
|
||||
case AUDIO_AC3:
|
||||
case AUDIO_E_AC3:
|
||||
case AUDIO_E_AC3_JOC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
|||
import com.google.android.exoplayer2.PlaybackPreparer;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Timeline.Period;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.flac.PictureFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
||||
|
|
@ -1506,6 +1508,13 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
|
|||
SingleTapListener,
|
||||
PlayerControlView.VisibilityListener {
|
||||
|
||||
private final Period period;
|
||||
private @Nullable Object lastPeriodUidWithTracks;
|
||||
|
||||
public ComponentListener() {
|
||||
period = new Period();
|
||||
}
|
||||
|
||||
// TextOutput implementation
|
||||
|
||||
@Override
|
||||
|
|
@ -1554,6 +1563,29 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
|
|||
|
||||
@Override
|
||||
public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) {
|
||||
// Suppress the update if transitioning to an unprepared period within the same window. This
|
||||
// is necessary to avoid closing the shutter when such a transition occurs. See:
|
||||
// https://github.com/google/ExoPlayer/issues/5507.
|
||||
Player player = Assertions.checkNotNull(PlayerView.this.player);
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
if (timeline.isEmpty()) {
|
||||
lastPeriodUidWithTracks = null;
|
||||
} else if (!player.getCurrentTrackGroups().isEmpty()) {
|
||||
lastPeriodUidWithTracks =
|
||||
timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid;
|
||||
} else if (lastPeriodUidWithTracks != null) {
|
||||
int lastPeriodIndexWithTracks = timeline.getIndexOfPeriod(lastPeriodUidWithTracks);
|
||||
if (lastPeriodIndexWithTracks != C.INDEX_UNSET) {
|
||||
int lastWindowIndexWithTracks =
|
||||
timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex;
|
||||
if (player.getCurrentWindowIndex() == lastWindowIndexWithTracks) {
|
||||
// We're in the same window. Suppress the update.
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastPeriodUidWithTracks = null;
|
||||
}
|
||||
|
||||
updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue