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 @@ - - + +