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
This commit is contained in:
andrewlewis 2017-06-28 01:43:54 -07:00 committed by Oliver Woodman
parent 66d122710e
commit 96fa660284
7 changed files with 115 additions and 72 deletions

View file

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

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -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);

View file

@ -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();
}

View file

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