From 96fa66028467596e404ff491443329419efe1df3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 28 Jun 2017 01:43:54 -0700 Subject: [PATCH] Expose ad playback information on ExoPlayer Also update the time bar to show ad markers using in-period ads and remove support for periods being marked as ads. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160382805 --- .../exoplayer2/demo/PlayerActivity.java | 2 +- .../google/android/exoplayer2/ExoPlayer.java | 17 +++ .../android/exoplayer2/ExoPlayerImpl.java | 21 +++- .../android/exoplayer2/SimpleExoPlayer.java | 15 +++ .../android/exoplayer2/ui/DefaultTimeBar.java | 18 +-- .../exoplayer2/ui/PlaybackControlView.java | 112 +++++++++--------- .../google/android/exoplayer2/ui/TimeBar.java | 2 +- 7 files changed, 115 insertions(+), 72 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index a5e06fa184..0659041c8b 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -327,7 +327,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay mediaDataSourceFactory, this, adTagUri, adOverlayViewGroup); // The demo app has a non-null overlay frame layout. simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup); - // Show a multi-window time bar, which will include ad break position markers. + // Show a multi-window time bar, which will include ad position markers. simpleExoPlayerView.setShowMultiWindowTimeBar(true); } catch (Exception e) { // Throw if the media source class was not found, or there was an error instantiating it. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 4ef1caf8c7..067cb9fa3a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -546,4 +546,21 @@ public interface ExoPlayer { */ boolean isCurrentWindowSeekable(); + /** + * Returns whether the player is currently playing an ad. + */ + boolean isPlayingAd(); + + /** + * If {@link #isPlayingAd()} returns true, returns the index of the ad group in the period + * currently being played. Returns {@link C#INDEX_UNSET} otherwise. + */ + int getCurrentAdGroupIndex(); + + /** + * If {@link #isPlayingAd()} returns true, returns the index of the ad in its ad group. Returns + * {@link C#INDEX_UNSET} otherwise. + */ + int getCurrentAdIndexInAdGroup(); + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 500dd9a058..96dd0bd113 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -352,6 +352,21 @@ import java.util.concurrent.CopyOnWriteArraySet; return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; } + @Override + public boolean isPlayingAd() { + return pendingSeekAcks == 0 && playbackInfo.periodId.adGroupIndex != C.INDEX_UNSET; + } + + @Override + public int getCurrentAdGroupIndex() { + return pendingSeekAcks == 0 ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; + } + + @Override + public int getCurrentAdIndexInAdGroup() { + return pendingSeekAcks == 0 ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; + } + @Override public int getRendererCount() { return renderers.length; @@ -471,10 +486,4 @@ import java.util.concurrent.CopyOnWriteArraySet; } } - // TODO: Add to the public ExoPlayer interface. - - private boolean isPlayingAd() { - return pendingSeekAcks == 0 && playbackInfo.periodId.adGroupIndex != C.INDEX_UNSET; - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 9bf98ff0dc..97cc7d349f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -662,6 +662,21 @@ public class SimpleExoPlayer implements ExoPlayer { return player.isCurrentWindowSeekable(); } + @Override + public boolean isPlayingAd() { + return player.isPlayingAd(); + } + + @Override + public int getCurrentAdGroupIndex() { + return player.getCurrentAdGroupIndex(); + } + + @Override + public int getCurrentAdIndexInAdGroup() { + return player.getCurrentAdIndexInAdGroup(); + } + // Internal methods. private void removeSurfaceCallbacks() { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index 4ede786175..3683196a31 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -102,8 +102,8 @@ public class DefaultTimeBar extends View implements TimeBar { private long duration; private long position; private long bufferedPosition; - private int adBreakCount; - private long[] adBreakTimesMs; + private int adGroupCount; + private long[] adGroupTimesMs; /** * Creates a new time bar. @@ -241,10 +241,10 @@ public class DefaultTimeBar extends View implements TimeBar { } @Override - public void setAdBreakTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount) { - Assertions.checkArgument(adBreakCount == 0 || adBreakTimesMs != null); - this.adBreakCount = adBreakCount; - this.adBreakTimesMs = adBreakTimesMs; + public void setAdGroupTimesMs(@Nullable long[] adGroupTimesMs, int adGroupCount) { + Assertions.checkArgument(adGroupCount == 0 || adGroupTimesMs != null); + this.adGroupCount = adGroupCount; + this.adGroupTimesMs = adGroupTimesMs; update(); } @@ -529,10 +529,10 @@ public class DefaultTimeBar extends View implements TimeBar { canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, playedPaint); } int adMarkerOffset = adMarkerWidth / 2; - for (int i = 0; i < adBreakCount; i++) { - long adBreakTimeMs = Util.constrainValue(adBreakTimesMs[i], 0, duration); + for (int i = 0; i < adGroupCount; i++) { + long adGroupTimeMs = Util.constrainValue(adGroupTimesMs[i], 0, duration); int markerPositionOffset = - (int) (progressBar.width() * adBreakTimeMs / duration) - adMarkerOffset; + (int) (progressBar.width() * adGroupTimeMs / duration) - adMarkerOffset; int markerLeft = progressBar.left + Math.min(progressBar.width() - adMarkerWidth, Math.max(0, markerPositionOffset)); canvas.drawRect(markerLeft, barTop, markerLeft + adMarkerWidth, barBottom, adMarkerPaint); 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 b06bbf9735..ce047fbbcd 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 @@ -312,7 +312,7 @@ public class PlaybackControlView extends FrameLayout { private int showTimeoutMs; private @RepeatToggleModes int repeatToggleModes; private long hideAtMs; - private long[] adBreakTimesMs; + private long[] adGroupTimesMs; private final Runnable updateProgressAction = new Runnable() { @Override @@ -363,7 +363,7 @@ public class PlaybackControlView extends FrameLayout { window = new Timeline.Window(); formatBuilder = new StringBuilder(); formatter = new Formatter(formatBuilder, Locale.getDefault()); - adBreakTimesMs = new long[0]; + adGroupTimesMs = new long[0]; componentListener = new ComponentListener(); controlDispatcher = DEFAULT_CONTROL_DISPATCHER; @@ -649,7 +649,7 @@ public class PlaybackControlView extends FrameLayout { enablePrevious = !timeline.isFirstWindow(windowIndex, player.getRepeatMode()) || isSeekable || !window.isDynamic; enableNext = !timeline.isLastWindow(windowIndex, player.getRepeatMode()) || window.isDynamic; - if (timeline.getPeriod(player.getCurrentPeriodIndex(), period).isAd) { + if (player.isPlayingAd()) { // Always hide player controls during ads. hide(); } @@ -712,47 +712,52 @@ public class PlaybackControlView extends FrameLayout { long positionUs = 0; long bufferedPositionUs = 0; long durationUs = 0; - boolean isInAdBreak = false; - boolean isPlayingAd = false; - int adBreakCount = 0; + int adGroupTimesMsCount = 0; for (int i = 0; i < windowCount; i++) { timeline.getWindow(i, window); for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) { - if (timeline.getPeriod(j, period).isAd) { - isPlayingAd |= j == periodIndex; - if (!isInAdBreak) { - isInAdBreak = true; - if (adBreakCount == adBreakTimesMs.length) { - adBreakTimesMs = Arrays.copyOf(adBreakTimesMs, - adBreakTimesMs.length == 0 ? 1 : adBreakTimesMs.length * 2); - } - adBreakTimesMs[adBreakCount++] = C.usToMs(durationUs); - } - } else { - isInAdBreak = false; - long periodDurationUs = period.getDurationUs(); - Assertions.checkState(periodDurationUs != C.TIME_UNSET); - long periodDurationInWindowUs = periodDurationUs; - if (j == window.firstPeriodIndex) { - periodDurationInWindowUs -= window.positionInFirstPeriodUs; - } - if (i < periodIndex) { - positionUs += periodDurationInWindowUs; - bufferedPositionUs += periodDurationInWindowUs; - } - durationUs += periodDurationInWindowUs; + long periodDurationUs = timeline.getPeriod(j, period).getDurationUs(); + Assertions.checkState(periodDurationUs != C.TIME_UNSET); + long periodDurationInWindowUs = periodDurationUs; + if (j == window.firstPeriodIndex) { + periodDurationInWindowUs -= window.positionInFirstPeriodUs; } + for (int adGroupIndex = 0; adGroupIndex < period.getAdGroupCount(); adGroupIndex++) { + long adGroupTimeUs = period.getAdGroupTimeUs(adGroupIndex); + if (period.hasPlayedAdGroup(adGroupIndex)) { + // Don't show played ad groups. + continue; + } + if (adGroupTimeUs == C.TIME_END_OF_SOURCE) { + adGroupTimeUs = periodDurationUs; + } + if (j == window.firstPeriodIndex) { + adGroupTimeUs -= window.positionInFirstPeriodUs; + } + if (adGroupTimeUs >= 0 && adGroupTimeUs <= window.durationUs) { + if (adGroupTimesMsCount == adGroupTimesMs.length) { + adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, + adGroupTimesMs.length == 0 ? 1 : adGroupTimesMs.length * 2); + } + adGroupTimesMs[adGroupTimesMsCount++] = C.usToMs(durationUs + adGroupTimeUs); + } + } + if (i < periodIndex) { + positionUs += periodDurationInWindowUs; + bufferedPositionUs += periodDurationInWindowUs; + } + durationUs += periodDurationInWindowUs; } } position = C.usToMs(positionUs); bufferedPosition = C.usToMs(bufferedPositionUs); duration = C.usToMs(durationUs); - if (!isPlayingAd) { + if (!player.isPlayingAd()) { position += player.getCurrentPosition(); bufferedPosition += player.getBufferedPosition(); } if (timeBar != null) { - timeBar.setAdBreakTimesMs(adBreakTimesMs, adBreakCount); + timeBar.setAdGroupTimesMs(adGroupTimesMs, adGroupTimesMsCount); } } else { position = player.getCurrentPosition(); @@ -898,7 +903,7 @@ public class PlaybackControlView extends FrameLayout { } } - private void seekToTimebarPosition(long timebarPositionMs) { + private void seekToTimeBarPosition(long timebarPositionMs) { if (multiWindowTimeBar) { Timeline timeline = player.getCurrentTimeline(); int windowCount = timeline.getWindowCount(); @@ -906,27 +911,25 @@ public class PlaybackControlView extends FrameLayout { for (int i = 0; i < windowCount; i++) { timeline.getWindow(i, window); for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) { - if (!timeline.getPeriod(j, period).isAd) { - long periodDurationMs = period.getDurationMs(); - if (periodDurationMs == C.TIME_UNSET) { - // Should never happen as canShowMultiWindowTimeBar is true. - throw new IllegalStateException(); - } - if (j == window.firstPeriodIndex) { - periodDurationMs -= window.getPositionInFirstPeriodMs(); - } - if (i == windowCount - 1 && j == window.lastPeriodIndex - && remainingMs >= periodDurationMs) { - // Seeking past the end of the last window should seek to the end of the timeline. - seekTo(i, window.getDurationMs()); - return; - } - if (remainingMs < periodDurationMs) { - seekTo(i, period.getPositionInWindowMs() + remainingMs); - return; - } - remainingMs -= periodDurationMs; + long periodDurationMs = timeline.getPeriod(j, period).getDurationMs(); + if (periodDurationMs == C.TIME_UNSET) { + // Should never happen as canShowMultiWindowTimeBar is true. + throw new IllegalStateException(); } + if (j == window.firstPeriodIndex) { + periodDurationMs -= window.getPositionInFirstPeriodMs(); + } + if (i == windowCount - 1 && j == window.lastPeriodIndex + && remainingMs >= periodDurationMs) { + // Seeking past the end of the last window should seek to the end of the timeline. + seekTo(i, window.getDurationMs()); + return; + } + if (remainingMs < periodDurationMs) { + seekTo(i, period.getPositionInWindowMs() + remainingMs); + return; + } + remainingMs -= periodDurationMs; } } } else { @@ -1028,8 +1031,7 @@ public class PlaybackControlView extends FrameLayout { } int periodCount = timeline.getPeriodCount(); for (int i = 0; i < periodCount; i++) { - timeline.getPeriod(i, period); - if (!period.isAd && period.durationUs == C.TIME_UNSET) { + if (timeline.getPeriod(i, period).durationUs == C.TIME_UNSET) { return false; } } @@ -1056,7 +1058,7 @@ public class PlaybackControlView extends FrameLayout { public void onScrubStop(TimeBar timeBar, long position, boolean canceled) { scrubbing = false; if (!canceled && player != null) { - seekToTimebarPosition(position); + seekToTimeBarPosition(position); } hideAfterTimeout(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java index 2fd5bff5eb..215688083d 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java @@ -84,7 +84,7 @@ public interface TimeBar { * ad breaks in milliseconds. May be {@code null} if there are no ad breaks. * @param adBreakCount The number of ad breaks. */ - void setAdBreakTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount); + void setAdGroupTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount); /** * Listener for scrubbing events.