mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
commit
1a5b304b1f
11 changed files with 1079 additions and 460 deletions
|
|
@ -1,5 +1,27 @@
|
||||||
# Release notes #
|
# 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) ###
|
### 2.11.5 (2020-06-05) ###
|
||||||
|
|
||||||
* Improve the smoothness of video playback immediately after starting, seeking
|
* Improve the smoothness of video playback immediately after starting, seeking
|
||||||
|
|
@ -16,10 +38,10 @@
|
||||||
([#7306](https://github.com/google/ExoPlayer/issues/7306)).
|
([#7306](https://github.com/google/ExoPlayer/issues/7306)).
|
||||||
* Fix issue in `AudioTrackPositionTracker` that could cause negative positions
|
* Fix issue in `AudioTrackPositionTracker` that could cause negative positions
|
||||||
to be reported at the start of playback and immediately after seeking
|
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
|
* Fix further cases where downloads would sometimes not resume after their
|
||||||
network requirements are met
|
network requirements are met
|
||||||
([#7453](https://github.com/google/ExoPlayer/issues/7453).
|
([#7453](https://github.com/google/ExoPlayer/issues/7453)).
|
||||||
* DASH:
|
* DASH:
|
||||||
* Merge trick play adaptation sets (i.e., adaptation sets marked with
|
* Merge trick play adaptation sets (i.e., adaptation sets marked with
|
||||||
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
|
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
|
||||||
|
|
@ -99,10 +121,10 @@
|
||||||
`DefaultAudioSink` constructor
|
`DefaultAudioSink` constructor
|
||||||
([#7134](https://github.com/google/ExoPlayer/issues/7134)).
|
([#7134](https://github.com/google/ExoPlayer/issues/7134)).
|
||||||
* Workaround issue that could cause slower than realtime playback of AAC on
|
* 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
|
* Fix case where another app spuriously holding transient audio focus could
|
||||||
prevent ExoPlayer from acquiring audio focus for an indefinite period of
|
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
|
* Fix case where the player volume could be permanently ducked if audio focus
|
||||||
was released whilst ducking.
|
was released whilst ducking.
|
||||||
* Fix playback of WAV files with trailing non-media bytes
|
* Fix playback of WAV files with trailing non-media bytes
|
||||||
|
|
@ -1022,7 +1044,7 @@
|
||||||
([#4492](https://github.com/google/ExoPlayer/issues/4492) and
|
([#4492](https://github.com/google/ExoPlayer/issues/4492) and
|
||||||
[#4634](https://github.com/google/ExoPlayer/issues/4634)).
|
[#4634](https://github.com/google/ExoPlayer/issues/4634)).
|
||||||
* Fix issue where removing looping media from a playlist throws an exception
|
* 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
|
* Fix issue where the preferred audio or text track would not be selected if
|
||||||
mapped onto a secondary renderer of the corresponding type
|
mapped onto a secondary renderer of the corresponding type
|
||||||
([#4711](http://github.com/google/ExoPlayer/issues/4711)).
|
([#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
|
resources when the playback thread has quit by the time the loading task has
|
||||||
completed.
|
completed.
|
||||||
* ID3: Better handle malformed ID3 data
|
* 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
|
* Support 14-bit mode and little endianness in DTS PES packets
|
||||||
([#3340](https://github.com/google/ExoPlayer/issues/3340)).
|
([#3340](https://github.com/google/ExoPlayer/issues/3340)).
|
||||||
* Demo app: Add ability to download not DRM protected content.
|
* Demo app: Add ability to download not DRM protected content.
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
// ExoPlayer version and version code.
|
// ExoPlayer version and version code.
|
||||||
releaseVersion = '2.11.5'
|
releaseVersion = '2.11.6'
|
||||||
releaseVersionCode = 2011005
|
releaseVersionCode = 2011006
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
appTargetSdkVersion = 29
|
appTargetSdkVersion = 29
|
||||||
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
|
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,11 @@
|
||||||
"name": "VMAP full, empty, full midrolls",
|
"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",
|
"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"
|
"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 com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
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.AdsRequest;
|
||||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
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.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.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Player;
|
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;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
@ -91,8 +96,7 @@ public final class ImaAdsLoaderTest {
|
||||||
private static final Uri TEST_URI = Uri.EMPTY;
|
private static final Uri TEST_URI = Uri.EMPTY;
|
||||||
private static final AdMediaInfo TEST_AD_MEDIA_INFO = new AdMediaInfo(TEST_URI.toString());
|
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 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 ImmutableList<Float> PREROLL_CUE_POINTS_SECONDS = ImmutableList.of(0f);
|
||||||
private static final Float[] PREROLL_CUE_POINTS_SECONDS = new Float[] {0f};
|
|
||||||
|
|
||||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
|
@ -111,6 +115,9 @@ public final class ImaAdsLoaderTest {
|
||||||
private ViewGroup adViewGroup;
|
private ViewGroup adViewGroup;
|
||||||
private View adOverlayView;
|
private View adOverlayView;
|
||||||
private AdsLoader.AdViewProvider adViewProvider;
|
private AdsLoader.AdViewProvider adViewProvider;
|
||||||
|
private AdEvent.AdEventListener adEventListener;
|
||||||
|
private ContentProgressProvider contentProgressProvider;
|
||||||
|
private VideoAdPlayer videoAdPlayer;
|
||||||
private TestAdsLoaderListener adsLoaderListener;
|
private TestAdsLoaderListener adsLoaderListener;
|
||||||
private FakePlayer fakeExoPlayer;
|
private FakePlayer fakeExoPlayer;
|
||||||
private ImaAdsLoader imaAdsLoader;
|
private ImaAdsLoader imaAdsLoader;
|
||||||
|
|
@ -144,14 +151,14 @@ public final class ImaAdsLoaderTest {
|
||||||
@Test
|
@Test
|
||||||
public void builder_overridesPlayerType() {
|
public void builder_overridesPlayerType() {
|
||||||
when(mockImaSdkSettings.getPlayerType()).thenReturn("test player type");
|
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");
|
verify(mockImaSdkSettings).setPlayerType("google/exo.ext.ima");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_setsAdUiViewGroup() {
|
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);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
verify(mockAdDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup);
|
verify(mockAdDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup);
|
||||||
|
|
@ -161,7 +168,7 @@ public final class ImaAdsLoaderTest {
|
||||||
@Test
|
@Test
|
||||||
public void start_withPlaceholderContent_initializedAdsLoader() {
|
public void start_withPlaceholderContent_initializedAdsLoader() {
|
||||||
Timeline placeholderTimeline = new DummyTimeline(/* tag= */ null);
|
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);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
// We'll only create the rendering settings when initializing the ads loader.
|
// We'll only create the rendering settings when initializing the ads loader.
|
||||||
|
|
@ -170,26 +177,27 @@ public final class ImaAdsLoaderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_updatesAdPlaybackState() {
|
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);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
assertThat(adsLoaderListener.adPlaybackState)
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
new AdPlaybackState(/* adGroupTimesUs...= */ 0)
|
new AdPlaybackState(/* adGroupTimesUs...= */ 0)
|
||||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
|
||||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startAfterRelease() {
|
public void startAfterRelease() {
|
||||||
setupPlayback(CONTENT_TIMELINE, ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||||
imaAdsLoader.release();
|
imaAdsLoader.release();
|
||||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startAndCallbacksAfterRelease() {
|
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.release();
|
||||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
|
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
|
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
|
||||||
// SDK being proguarded.
|
// SDK being proguarded.
|
||||||
imaAdsLoader.requestAds(adViewGroup);
|
imaAdsLoader.requestAds(adViewGroup);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||||
imaAdsLoader.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||||
imaAdsLoader.playAd(TEST_AD_MEDIA_INFO);
|
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||||
imaAdsLoader.pauseAd(TEST_AD_MEDIA_INFO);
|
videoAdPlayer.pauseAd(TEST_AD_MEDIA_INFO);
|
||||||
imaAdsLoader.stopAd(TEST_AD_MEDIA_INFO);
|
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
|
||||||
imaAdsLoader.onPlayerError(ExoPlaybackException.createForSource(new IOException()));
|
imaAdsLoader.onPlayerError(ExoPlaybackException.createForSource(new IOException()));
|
||||||
imaAdsLoader.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
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(
|
imaAdsLoader.handlePrepareError(
|
||||||
/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, new IOException());
|
/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, new IOException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playback_withPrerollAd_marksAdAsPlayed() {
|
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.
|
// Load the preroll ad.
|
||||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||||
imaAdsLoader.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||||
|
|
||||||
// Play the preroll ad.
|
// Play the preroll ad.
|
||||||
imaAdsLoader.playAd(TEST_AD_MEDIA_INFO);
|
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
|
||||||
fakeExoPlayer.setPlayingAdPosition(
|
fakeExoPlayer.setPlayingAdPosition(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
/* position= */ 0,
|
/* position= */ 0,
|
||||||
/* contentPosition= */ 0);
|
/* contentPosition= */ 0);
|
||||||
fakeExoPlayer.setState(Player.STATE_READY, true);
|
fakeExoPlayer.setState(Player.STATE_READY, true);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
|
||||||
|
|
||||||
// Play the content.
|
// Play the content.
|
||||||
fakeExoPlayer.setPlayingContentPosition(0);
|
fakeExoPlayer.setPlayingContentPosition(0);
|
||||||
imaAdsLoader.stopAd(TEST_AD_MEDIA_INFO);
|
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||||
|
|
||||||
// Verify that the preroll ad has been marked as played.
|
// Verify that the preroll ad has been marked as played.
|
||||||
assertThat(adsLoaderListener.adPlaybackState)
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
|
|
@ -249,24 +257,24 @@ public final class ImaAdsLoaderTest {
|
||||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* uri= */ TEST_URI)
|
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* uri= */ TEST_URI)
|
||||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||||
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
|
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playback_withPostrollFetchError_marksAdAsInErrorState() {
|
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.
|
// Simulate loading an empty postroll ad.
|
||||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
imaAdsLoader.onAdEvent(mockPostrollFetchErrorAdEvent);
|
adEventListener.onAdEvent(mockPostrollFetchErrorAdEvent);
|
||||||
|
|
||||||
assertThat(adsLoaderListener.adPlaybackState)
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
new AdPlaybackState(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE)
|
new AdPlaybackState(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE)
|
||||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||||
}
|
}
|
||||||
|
|
@ -275,10 +283,9 @@ public final class ImaAdsLoaderTest {
|
||||||
public void playback_withAdNotPreloadingBeforeTimeout_hasNoError() {
|
public void playback_withAdNotPreloadingBeforeTimeout_hasNoError() {
|
||||||
// Simulate an ad at 2 seconds.
|
// Simulate an ad at 2 seconds.
|
||||||
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
||||||
setupPlayback(
|
long adGroupTimeUs = adGroupPositionInWindowUs;
|
||||||
CONTENT_TIMELINE,
|
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
|
||||||
ADS_DURATIONS_US,
|
setupPlayback(CONTENT_TIMELINE, cuePoints);
|
||||||
new Float[] {(float) adGroupPositionInWindowUs / C.MICROS_PER_SECOND});
|
|
||||||
|
|
||||||
// Advance playback to just before the midroll and simulate buffering.
|
// Advance playback to just before the midroll and simulate buffering.
|
||||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
@ -286,23 +293,21 @@ public final class ImaAdsLoaderTest {
|
||||||
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||||
// Advance before the timeout and simulating polling content progress.
|
// Advance before the timeout and simulating polling content progress.
|
||||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(1));
|
ShadowSystemClock.advanceBy(Duration.ofSeconds(1));
|
||||||
imaAdsLoader.getContentProgress();
|
contentProgressProvider.getContentProgress();
|
||||||
|
|
||||||
assertThat(adsLoaderListener.adPlaybackState)
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
new AdPlaybackState(/* adGroupTimesUs...= */ adGroupPositionInWindowUs)
|
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||||
.withAdDurationsUs(ADS_DURATIONS_US));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playback_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() {
|
public void playback_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() {
|
||||||
// Simulate an ad at 2 seconds.
|
// Simulate an ad at 2 seconds.
|
||||||
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
||||||
setupPlayback(
|
long adGroupTimeUs = adGroupPositionInWindowUs;
|
||||||
CONTENT_TIMELINE,
|
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
|
||||||
ADS_DURATIONS_US,
|
setupPlayback(CONTENT_TIMELINE, cuePoints);
|
||||||
new Float[] {(float) adGroupPositionInWindowUs / C.MICROS_PER_SECOND});
|
|
||||||
|
|
||||||
// Advance playback to just before the midroll and simulate buffering.
|
// Advance playback to just before the midroll and simulate buffering.
|
||||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
@ -310,20 +315,296 @@ public final class ImaAdsLoaderTest {
|
||||||
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||||
// Advance past the timeout and simulate polling content progress.
|
// Advance past the timeout and simulate polling content progress.
|
||||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
|
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
|
||||||
imaAdsLoader.getContentProgress();
|
contentProgressProvider.getContentProgress();
|
||||||
|
|
||||||
assertThat(adsLoaderListener.adPlaybackState)
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
new AdPlaybackState(/* adGroupTimesUs...= */ adGroupPositionInWindowUs)
|
AdPlaybackStateFactory.fromCuePoints(cuePoints)
|
||||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||||
.withAdDurationsUs(ADS_DURATIONS_US)
|
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
.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
|
@Test
|
||||||
public void stop_unregistersAllVideoControlOverlays() {
|
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.start(adsLoaderListener, adViewProvider);
|
||||||
imaAdsLoader.requestAds(adViewGroup);
|
imaAdsLoader.requestAds(adViewGroup);
|
||||||
imaAdsLoader.stop();
|
imaAdsLoader.stop();
|
||||||
|
|
@ -333,15 +614,71 @@ public final class ImaAdsLoaderTest {
|
||||||
inOrder.verify(mockAdDisplayContainer).unregisterAllVideoControlsOverlays();
|
inOrder.verify(mockAdDisplayContainer).unregisterAllVideoControlsOverlays();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupPlayback(Timeline contentTimeline, long[][] adDurationsUs, Float[] cuePoints) {
|
@Test
|
||||||
fakeExoPlayer = new FakePlayer();
|
public void loadAd_withLargeAdCuePoint_updatesAdPlaybackStateWithLoadedAd() {
|
||||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
|
float midrollTimeSecs = 1_765f;
|
||||||
when(mockAdsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
|
ImmutableList<Float> cuePoints = ImmutableList.of(midrollTimeSecs);
|
||||||
imaAdsLoader =
|
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())
|
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||||
.setImaFactory(mockImaFactory)
|
.setImaFactory(mockImaFactory)
|
||||||
.setImaSdkSettings(mockImaSdkSettings)
|
.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);
|
imaAdsLoader.setPlayer(fakeExoPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,9 +686,11 @@ public final class ImaAdsLoaderTest {
|
||||||
ArgumentCaptor<Object> userRequestContextCaptor = ArgumentCaptor.forClass(Object.class);
|
ArgumentCaptor<Object> userRequestContextCaptor = ArgumentCaptor.forClass(Object.class);
|
||||||
doNothing().when(mockAdsRequest).setUserRequestContext(userRequestContextCaptor.capture());
|
doNothing().when(mockAdsRequest).setUserRequestContext(userRequestContextCaptor.capture());
|
||||||
when(mockAdsRequest.getUserRequestContext())
|
when(mockAdsRequest.getUserRequestContext())
|
||||||
.thenAnswer((Answer<Object>) invocation -> userRequestContextCaptor.getValue());
|
.thenAnswer(invocation -> userRequestContextCaptor.getValue());
|
||||||
List<com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener> adsLoadedListeners =
|
List<com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener> adsLoadedListeners =
|
||||||
new ArrayList<>();
|
new ArrayList<>();
|
||||||
|
// Deliberately don't handle removeAdsLoadedListener to allow testing behavior if the IMA SDK
|
||||||
|
// invokes callbacks after release.
|
||||||
doAnswer(
|
doAnswer(
|
||||||
invocation -> {
|
invocation -> {
|
||||||
adsLoadedListeners.add(invocation.getArgument(0));
|
adsLoadedListeners.add(invocation.getArgument(0));
|
||||||
|
|
@ -359,13 +698,6 @@ public final class ImaAdsLoaderTest {
|
||||||
})
|
})
|
||||||
.when(mockAdsLoader)
|
.when(mockAdsLoader)
|
||||||
.addAdsLoadedListener(any());
|
.addAdsLoadedListener(any());
|
||||||
doAnswer(
|
|
||||||
invocation -> {
|
|
||||||
adsLoadedListeners.remove(invocation.getArgument(0));
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.when(mockAdsLoader)
|
|
||||||
.removeAdsLoadedListener(any());
|
|
||||||
when(mockAdsManagerLoadedEvent.getAdsManager()).thenReturn(mockAdsManager);
|
when(mockAdsManagerLoadedEvent.getAdsManager()).thenReturn(mockAdsManager);
|
||||||
when(mockAdsManagerLoadedEvent.getUserRequestContext())
|
when(mockAdsManagerLoadedEvent.getUserRequestContext())
|
||||||
.thenAnswer(invocation -> mockAdsRequest.getUserRequestContext());
|
.thenAnswer(invocation -> mockAdsRequest.getUserRequestContext());
|
||||||
|
|
@ -381,6 +713,30 @@ public final class ImaAdsLoaderTest {
|
||||||
.when(mockAdsLoader)
|
.when(mockAdsLoader)
|
||||||
.requestAds(mockAdsRequest);
|
.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.createAdDisplayContainer()).thenReturn(mockAdDisplayContainer);
|
||||||
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
|
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
|
||||||
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
|
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
|
||||||
|
|
@ -422,19 +778,21 @@ public final class ImaAdsLoaderTest {
|
||||||
|
|
||||||
private final FakePlayer fakeExoPlayer;
|
private final FakePlayer fakeExoPlayer;
|
||||||
private final Timeline contentTimeline;
|
private final Timeline contentTimeline;
|
||||||
private final long[][] adDurationsUs;
|
|
||||||
|
|
||||||
public AdPlaybackState adPlaybackState;
|
public AdPlaybackState adPlaybackState;
|
||||||
|
|
||||||
public TestAdsLoaderListener(
|
public TestAdsLoaderListener(FakePlayer fakeExoPlayer, Timeline contentTimeline) {
|
||||||
FakePlayer fakeExoPlayer, Timeline contentTimeline, long[][] adDurationsUs) {
|
|
||||||
this.fakeExoPlayer = fakeExoPlayer;
|
this.fakeExoPlayer = fakeExoPlayer;
|
||||||
this.contentTimeline = contentTimeline;
|
this.contentTimeline = contentTimeline;
|
||||||
this.adDurationsUs = adDurationsUs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdPlaybackState(AdPlaybackState adPlaybackState) {
|
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);
|
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
|
||||||
this.adPlaybackState = adPlaybackState;
|
this.adPlaybackState = adPlaybackState;
|
||||||
fakeExoPlayer.updateTimeline(
|
fakeExoPlayer.updateTimeline(
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
|
||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
/** 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.
|
// 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}. */
|
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// 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.
|
* 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).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// 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}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
|
|
||||||
|
|
@ -124,13 +124,9 @@ public final class AdPlaybackState {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns a new instance with the ad count set to {@code count}. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public AdGroup withAdCount(int count) {
|
public AdGroup withAdCount(int count) {
|
||||||
Assertions.checkArgument(this.count == C.LENGTH_UNSET && states.length <= count);
|
|
||||||
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
|
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
|
||||||
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
|
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
|
||||||
@NullableType Uri[] uris = Arrays.copyOf(this.uris, 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
|
* 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
|
* marked as {@link #AD_STATE_AVAILABLE}.
|
||||||
* #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.
|
|
||||||
*/
|
*/
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public AdGroup withAdUri(Uri uri, int index) {
|
public AdGroup withAdUri(Uri uri, int index) {
|
||||||
Assertions.checkArgument(count == C.LENGTH_UNSET || index < count);
|
|
||||||
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1);
|
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1);
|
||||||
Assertions.checkArgument(states[index] == AD_STATE_UNAVAILABLE);
|
|
||||||
long[] durationsUs =
|
long[] durationsUs =
|
||||||
this.durationsUs.length == states.length
|
this.durationsUs.length == states.length
|
||||||
? this.durationsUs
|
? this.durationsUs
|
||||||
|
|
@ -280,7 +270,9 @@ public final class AdPlaybackState {
|
||||||
public final AdGroup[] adGroups;
|
public final AdGroup[] adGroups;
|
||||||
/** The position offset in the first unplayed ad at which to begin playback, in microseconds. */
|
/** The position offset in the first unplayed ad at which to begin playback, in microseconds. */
|
||||||
public final long adResumePositionUs;
|
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;
|
public final long contentDurationUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -489,6 +481,54 @@ public final class AdPlaybackState {
|
||||||
return result;
|
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(
|
private boolean isPositionBeforeAdGroup(
|
||||||
long positionUs, long periodDurationUs, int adGroupIndex) {
|
long positionUs, long periodDurationUs, int adGroupIndex) {
|
||||||
if (positionUs == C.TIME_END_OF_SOURCE) {
|
if (positionUs == C.TIME_END_OF_SOURCE) {
|
||||||
|
|
|
||||||
|
|
@ -44,23 +44,16 @@ public final class SinglePeriodAdTimeline extends ForwardingTimeline {
|
||||||
@Override
|
@Override
|
||||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
timeline.getPeriod(periodIndex, period, setIds);
|
timeline.getPeriod(periodIndex, period, setIds);
|
||||||
|
long durationUs =
|
||||||
|
period.durationUs == C.TIME_UNSET ? adPlaybackState.contentDurationUs : period.durationUs;
|
||||||
period.set(
|
period.set(
|
||||||
period.id,
|
period.id,
|
||||||
period.uid,
|
period.uid,
|
||||||
period.windowIndex,
|
period.windowIndex,
|
||||||
period.durationUs,
|
durationUs,
|
||||||
period.getPositionInWindowUs(),
|
period.getPositionInWindowUs(),
|
||||||
adPlaybackState);
|
adPlaybackState);
|
||||||
return period;
|
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) {
|
if (mimeType == null) {
|
||||||
return false;
|
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) {
|
switch (mimeType) {
|
||||||
case AUDIO_AAC:
|
|
||||||
case AUDIO_MPEG:
|
case AUDIO_MPEG:
|
||||||
case AUDIO_MPEG_L1:
|
case AUDIO_MPEG_L1:
|
||||||
case AUDIO_MPEG_L2:
|
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;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.PlaybackPreparer;
|
import com.google.android.exoplayer2.PlaybackPreparer;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
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.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.flac.PictureFrame;
|
import com.google.android.exoplayer2.metadata.flac.PictureFrame;
|
||||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
||||||
|
|
@ -1506,6 +1508,13 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
|
||||||
SingleTapListener,
|
SingleTapListener,
|
||||||
PlayerControlView.VisibilityListener {
|
PlayerControlView.VisibilityListener {
|
||||||
|
|
||||||
|
private final Period period;
|
||||||
|
private @Nullable Object lastPeriodUidWithTracks;
|
||||||
|
|
||||||
|
public ComponentListener() {
|
||||||
|
period = new Period();
|
||||||
|
}
|
||||||
|
|
||||||
// TextOutput implementation
|
// TextOutput implementation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1554,6 +1563,29 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) {
|
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);
|
updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue