diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
index 6b20404a39..cbed5d166e 100644
--- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
+++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
@@ -150,10 +150,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
// Fields tracking the player/loader state.
- /**
- * Whether the player's play when ready flag has temporarily been set to true for playing ads.
- */
- private boolean playWhenReadyOverriddenForAds;
/**
* Whether the player is playing an ad.
*/
@@ -243,7 +239,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
player.addListener(this);
if (adPlaybackState != null) {
eventListener.onAdPlaybackState(adPlaybackState.copy());
- if (imaPausedContent) {
+ if (imaPausedContent && player.getPlayWhenReady()) {
adsManager.resume();
}
} else {
@@ -448,13 +444,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
if (DEBUG) {
Log.d(TAG, "playAd");
}
- if (player == null) {
- // Sometimes messages from IMA arrive after detaching the player. See [Internal: b/63801642].
- Log.w(TAG, "Unexpected playAd while detached");
- } else if (!player.getPlayWhenReady()) {
- playWhenReadyOverriddenForAds = true;
- player.setPlayWhenReady(true);
- }
switch (imaAdState) {
case IMA_AD_STATE_PLAYING:
// IMA does not always call stopAd before resuming content.
@@ -472,6 +461,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onResume();
}
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ if (player == null) {
+ // Sometimes messages from IMA arrive after detaching the player. See [Internal: b/63801642].
+ Log.w(TAG, "Unexpected playAd while detached");
+ } else if (!player.getPlayWhenReady()) {
+ adsManager.pause();
}
}
@@ -522,7 +520,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
}
Assertions.checkArgument(timeline.getPeriodCount() == 1);
this.timeline = timeline;
- contentDurationMs = C.usToMs(timeline.getPeriod(0, period).durationUs);
+ long contentDurationUs = timeline.getPeriod(0, period).durationUs;
+ contentDurationMs = C.usToMs(contentDurationUs);
+ if (contentDurationUs != C.TIME_UNSET) {
+ adPlaybackState.contentDurationUs = contentDurationUs;
+ }
updateImaStateForPlayerState();
}
@@ -532,6 +534,16 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
return;
}
+ if (imaAdState == IMA_AD_STATE_PLAYING && !playWhenReady) {
+ adsManager.pause();
+ return;
+ }
+
+ if (imaAdState == IMA_AD_STATE_PAUSED && playWhenReady) {
+ adsManager.resume();
+ return;
+ }
+
if (imaAdState == IMA_AD_STATE_NONE && playbackState == Player.STATE_BUFFERING
&& playWhenReady) {
checkForContentComplete();
@@ -593,10 +605,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private void updateImaStateForPlayerState() {
boolean wasPlayingAd = playingAd;
playingAd = player.isPlayingAd();
- if (!playingAd && playWhenReadyOverriddenForAds) {
- playWhenReadyOverriddenForAds = false;
- player.setPlayWhenReady(false);
- }
if (!sentContentComplete) {
boolean adFinished = (wasPlayingAd && !playingAd)
|| playingAdIndexInAdGroup != player.getCurrentAdIndexInAdGroup();
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
index 97c97dec8f..58fa149b59 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java
@@ -51,6 +51,10 @@ public final class AdPlaybackState {
*/
public final Uri[][] adUris;
+ /**
+ * The content duration in microseconds, if known. {@link C#TIME_UNSET} otherwise.
+ */
+ public long contentDurationUs;
/**
* The position offset in the first unplayed ad at which to begin playback, in microseconds.
*/
@@ -71,15 +75,17 @@ public final class AdPlaybackState {
adUris = new Uri[adGroupCount][];
Arrays.fill(adUris, new Uri[0]);
adsLoadedCounts = new int[adGroupTimesUs.length];
+ contentDurationUs = C.TIME_UNSET;
}
private AdPlaybackState(long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts,
- int[] adsPlayedCounts, Uri[][] adUris, long adResumePositionUs) {
+ int[] adsPlayedCounts, Uri[][] adUris, long contentDurationUs, long adResumePositionUs) {
this.adGroupTimesUs = adGroupTimesUs;
this.adCounts = adCounts;
this.adsLoadedCounts = adsLoadedCounts;
this.adsPlayedCounts = adsPlayedCounts;
this.adUris = adUris;
+ this.contentDurationUs = contentDurationUs;
this.adResumePositionUs = adResumePositionUs;
adGroupCount = adGroupTimesUs.length;
}
@@ -94,7 +100,8 @@ public final class AdPlaybackState {
}
return new AdPlaybackState(Arrays.copyOf(adGroupTimesUs, adGroupCount),
Arrays.copyOf(adCounts, adGroupCount), Arrays.copyOf(adsLoadedCounts, adGroupCount),
- Arrays.copyOf(adsPlayedCounts, adGroupCount), adUris, adResumePositionUs);
+ Arrays.copyOf(adsPlayedCounts, adGroupCount), adUris, contentDurationUs,
+ adResumePositionUs);
}
/**
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
index 41a856f83f..9c75b5ee5e 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
@@ -273,7 +273,8 @@ public final class AdsMediaSource implements MediaSource {
Timeline timeline = adPlaybackState.adGroupCount == 0 ? contentTimeline
: new SinglePeriodAdTimeline(contentTimeline, adPlaybackState.adGroupTimesUs,
adPlaybackState.adCounts, adPlaybackState.adsLoadedCounts,
- adPlaybackState.adsPlayedCounts, adDurationsUs, adPlaybackState.adResumePositionUs);
+ adPlaybackState.adsPlayedCounts, adDurationsUs, adPlaybackState.adResumePositionUs,
+ adPlaybackState.contentDurationUs);
listener.onSourceInfoRefreshed(timeline, contentManifest);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java
index c2974681db..0a04c9ab4b 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/SinglePeriodAdTimeline.java
@@ -31,6 +31,7 @@ import com.google.android.exoplayer2.util.Assertions;
private final int[] adsPlayedCounts;
private final long[][] adDurationsUs;
private final long adResumePositionUs;
+ private final long contentDurationUs;
/**
* Creates a new timeline with a single period containing the specified ads.
@@ -48,10 +49,12 @@ import com.google.android.exoplayer2.util.Assertions;
* may be {@link C#TIME_UNSET} if the duration is not yet known.
* @param adResumePositionUs The position offset in the earliest unplayed ad at which to begin
* playback, in microseconds.
+ * @param contentDurationUs The content duration in microseconds, if known. {@link C#TIME_UNSET}
+ * otherwise.
*/
public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, int[] adCounts,
- int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs,
- long adResumePositionUs) {
+ int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs, long adResumePositionUs,
+ long contentDurationUs) {
super(contentTimeline);
Assertions.checkState(contentTimeline.getPeriodCount() == 1);
Assertions.checkState(contentTimeline.getWindowCount() == 1);
@@ -61,6 +64,7 @@ import com.google.android.exoplayer2.util.Assertions;
this.adsPlayedCounts = adsPlayedCounts;
this.adDurationsUs = adDurationsUs;
this.adResumePositionUs = adResumePositionUs;
+ this.contentDurationUs = contentDurationUs;
}
@Override
@@ -72,4 +76,14 @@ import com.google.android.exoplayer2.util.Assertions;
return period;
}
+ @Override
+ public Window getWindow(int windowIndex, Window window, boolean setIds,
+ long defaultPositionProjectionUs) {
+ window = super.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
+ if (window.durationUs == C.TIME_UNSET) {
+ window.durationUs = contentDurationUs;
+ }
+ return window;
+ }
+
}
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
index e2ae1f732b..964eda7de0 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
@@ -656,17 +656,13 @@ public class PlaybackControlView extends FrameLayout {
boolean isSeekable = false;
boolean enablePrevious = false;
boolean enableNext = false;
- if (haveNonEmptyTimeline) {
+ if (haveNonEmptyTimeline && !player.isPlayingAd()) {
int windowIndex = player.getCurrentWindowIndex();
timeline.getWindow(windowIndex, window);
isSeekable = window.isSeekable;
enablePrevious = isSeekable || !window.isDynamic
|| player.getPreviousWindowIndex() != C.INDEX_UNSET;
enableNext = window.isDynamic || player.getNextWindowIndex() != C.INDEX_UNSET;
- if (player.isPlayingAd()) {
- // Always hide player controls during ads.
- hide();
- }
}
setButtonEnabled(enablePrevious, previousButton);
setButtonEnabled(enableNext, nextButton);
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
index 053bc26a6e..f34ede3e6d 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
@@ -37,6 +37,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
@@ -751,6 +752,10 @@ public final class SimpleExoPlayerView extends FrameLayout {
* Shows the playback controls, but only if forced or shown indefinitely.
*/
private void maybeShowController(boolean isForced) {
+ if (isPlayingAd()) {
+ // Never show the controller if an ad is currently playing.
+ return;
+ }
if (useController) {
boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0;
boolean shouldShowIndefinitely = shouldShowControllerIndefinitely();
@@ -777,6 +782,10 @@ public final class SimpleExoPlayerView extends FrameLayout {
controller.show();
}
+ private boolean isPlayingAd() {
+ return player != null && player.isPlayingAd() && player.getPlayWhenReady();
+ }
+
private void updateForCurrentTrackSelections() {
if (player == null) {
return;
@@ -907,7 +916,18 @@ public final class SimpleExoPlayerView extends FrameLayout {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
- maybeShowController(false);
+ if (isPlayingAd()) {
+ hideController();
+ } else {
+ maybeShowController(false);
+ }
+ }
+
+ @Override
+ public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
+ if (isPlayingAd()) {
+ hideController();
+ }
}
}
diff --git a/library/ui/src/main/res/layout/exo_simple_player_view.xml b/library/ui/src/main/res/layout/exo_simple_player_view.xml
index 1f59b7796d..340113da6c 100644
--- a/library/ui/src/main/res/layout/exo_simple_player_view.xml
+++ b/library/ui/src/main/res/layout/exo_simple_player_view.xml
@@ -38,12 +38,12 @@
-
-
+
+