Support resuming content after ads

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161775394
This commit is contained in:
andrewlewis 2017-07-13 01:10:51 -07:00 committed by Oliver Woodman
parent 4f2fae4fba
commit 6c74a31556
8 changed files with 101 additions and 35 deletions

View File

@ -406,7 +406,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
private void updateResumePosition() {
resumeWindow = player.getCurrentWindowIndex();
resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition())
resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getContentPosition())
: C.TIME_UNSET;
}

View File

@ -536,6 +536,11 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
throw new UnsupportedOperationException();
}
@Override
public long getContentPosition() {
throw new UnsupportedOperationException();
}
@Override
public boolean handleMessage(Message msg) {
ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj;

View File

@ -563,4 +563,11 @@ public interface ExoPlayer {
*/
int getCurrentAdIndexInAdGroup();
/**
* If {@link #isPlayingAd()} returns {@code true}, returns the content position that will be
* played once all ads in the ad group have finished playing, in milliseconds. If there is no ad
* playing, the returned position is the same as that returned by {@link #getCurrentPosition()}.
*/
long getContentPosition();
}

View File

@ -367,6 +367,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
return pendingSeekAcks == 0 ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET;
}
@Override
public long getContentPosition() {
if (isPlayingAd()) {
timeline.getPeriod(playbackInfo.periodId.periodIndex, period);
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs);
} else {
return getCurrentPosition();
}
}
@Override
public int getRendererCount() {
return renderers.length;

View File

@ -54,6 +54,7 @@ import java.io.IOException;
public final MediaPeriodId periodId;
public final long startPositionUs;
public final long contentPositionUs;
public volatile long positionUs;
public volatile long bufferedPositionUs;
@ -63,14 +64,20 @@ import java.io.IOException;
}
public PlaybackInfo(MediaPeriodId periodId, long startPositionUs) {
this(periodId, startPositionUs, C.TIME_UNSET);
}
public PlaybackInfo(MediaPeriodId periodId, long startPositionUs, long contentPositionUs) {
this.periodId = periodId;
this.startPositionUs = startPositionUs;
this.contentPositionUs = contentPositionUs;
positionUs = startPositionUs;
bufferedPositionUs = startPositionUs;
}
public PlaybackInfo copyWithPeriodId(MediaPeriodId periodId) {
PlaybackInfo playbackInfo = new PlaybackInfo(periodId.periodIndex, startPositionUs);
public PlaybackInfo copyWithPeriodIndex(int periodIndex) {
PlaybackInfo playbackInfo = new PlaybackInfo(periodId.copyWithPeriodIndex(periodIndex),
startPositionUs, contentPositionUs);
playbackInfo.positionUs = positionUs;
playbackInfo.bufferedPositionUs = bufferedPositionUs;
return playbackInfo;
@ -486,7 +493,7 @@ import java.io.IOException;
// position of the playing period to make sure none of the removed period is played.
MediaPeriodId periodId = playingPeriodHolder.info.id;
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(periodId, newPositionUs);
playbackInfo = new PlaybackInfo(periodId, newPositionUs, playbackInfo.contentPositionUs);
}
}
@ -663,11 +670,11 @@ import java.io.IOException;
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
int periodIndex = periodPosition.first;
long periodPositionUs = periodPosition.second;
long contentPositionUs = periodPositionUs;
MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, periodPositionUs);
if (periodId.isAd()) {
seekPositionAdjusted = true;
// TODO: Resume content at periodPositionUs after the ad plays.
periodPositionUs = 0;
}
try {
@ -680,7 +687,7 @@ import java.io.IOException;
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs;
} finally {
playbackInfo = new PlaybackInfo(periodId, periodPositionUs);
playbackInfo = new PlaybackInfo(periodId, periodPositionUs, contentPositionUs);
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
.sendToTarget();
}
@ -985,16 +992,19 @@ import java.io.IOException;
long positionUs = periodPosition.second;
MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs);
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return;
}
Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(
defaultPosition.first, defaultPosition.second);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : defaultPosition.second);
int periodIndex = defaultPosition.first;
long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex,
startPositionUs);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : startPositionUs,
startPositionUs);
}
}
@ -1047,8 +1057,7 @@ import java.io.IOException;
// The current period is in the new timeline. Update the holder and playbackInfo.
periodHolder = updatePeriodInfo(periodHolder, periodIndex);
if (periodIndex != playbackInfo.periodId.periodIndex) {
playbackInfo =
playbackInfo.copyWithPeriodId(playbackInfo.periodId.copyWithPeriodIndex(periodIndex));
playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex);
}
// If there are subsequent holders, update the index for each of them. If we find a holder
@ -1070,7 +1079,8 @@ import java.io.IOException;
// position of the playing period to make sure none of the removed period is played.
long newPositionUs =
seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs,
playbackInfo.contentPositionUs);
} else {
// Update the loading period to be the last period that's still valid, and release all
// subsequent periods.
@ -1223,7 +1233,7 @@ import java.io.IOException;
playingPeriodHolder.release();
setPlayingPeriodHolder(playingPeriodHolder.next);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id,
playingPeriodHolder.info.startPositionUs);
playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
}

View File

@ -47,6 +47,11 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
* {@link C#TIME_END_OF_SOURCE} if the end position is the end of the media period.
*/
public final long endPositionUs;
/**
* If this is an ad, the position to play in the next content media period. {@link C#TIME_UNSET}
* otherwise.
*/
public final long contentPositionUs;
/**
* The duration of the media to play within the media period, in microseconds, or
* {@link C#TIME_UNSET} if not known.
@ -64,10 +69,11 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
public final boolean isFinal;
private MediaPeriodInfo(MediaPeriodId id, long startPositionUs, long endPositionUs,
long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) {
long contentPositionUs, long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) {
this.id = id;
this.startPositionUs = startPositionUs;
this.endPositionUs = endPositionUs;
this.contentPositionUs = contentPositionUs;
this.durationUs = durationUs;
this.isLastInTimelinePeriod = isLastInTimelinePeriod;
this.isFinal = isFinal;
@ -79,14 +85,14 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
*/
public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) {
return new MediaPeriodInfo(id.copyWithPeriodIndex(periodIndex), startPositionUs,
endPositionUs, durationUs, isLastInTimelinePeriod, isFinal);
endPositionUs, contentPositionUs, durationUs, isLastInTimelinePeriod, isFinal);
}
/**
* Returns a copy of this instance with the start position set to the specified value.
*/
public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
return new MediaPeriodInfo(id, startPositionUs, endPositionUs, durationUs,
return new MediaPeriodInfo(id, startPositionUs, endPositionUs, contentPositionUs, durationUs,
isLastInTimelinePeriod, isFinal);
}
@ -127,7 +133,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
* Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position.
*/
public MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
return getMediaPeriodInfo(playbackInfo.periodId, playbackInfo.startPositionUs);
return getMediaPeriodInfo(playbackInfo.periodId, playbackInfo.contentPositionUs,
playbackInfo.startPositionUs);
}
/**
@ -173,8 +180,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
} else {
startPositionUs = 0;
}
return getMediaPeriodInfo(resolvePeriodPositionForAds(nextPeriodIndex, startPositionUs),
startPositionUs);
MediaPeriodId periodId = resolvePeriodPositionForAds(nextPeriodIndex, startPositionUs);
return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs);
}
MediaPeriodId currentPeriodId = currentMediaPeriodInfo.id;
@ -190,18 +197,23 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
// Play the next ad in the ad group if it's available.
return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) ? null
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, currentAdGroupIndex,
nextAdIndexInAdGroup);
nextAdIndexInAdGroup, currentMediaPeriodInfo.contentPositionUs);
} else {
// Play content from the ad group position.
return getMediaPeriodInfo(new MediaPeriodId(currentPeriodId.periodIndex),
period.getAdGroupTimeUs(currentAdGroupIndex));
int nextAdGroupIndex =
period.getAdGroupIndexAfterPositionUs(currentMediaPeriodInfo.contentPositionUs);
long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE
: period.getAdGroupTimeUs(nextAdGroupIndex);
return getMediaPeriodInfoForContent(currentPeriodId.periodIndex,
currentMediaPeriodInfo.contentPositionUs, endUs);
}
} else if (currentMediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) {
// Play the next ad group if it's available.
int nextAdGroupIndex =
period.getAdGroupIndexForPositionUs(currentMediaPeriodInfo.endPositionUs);
return !period.isAdAvailable(nextAdGroupIndex, 0) ? null
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, nextAdGroupIndex, 0);
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, nextAdGroupIndex, 0,
currentMediaPeriodInfo.endPositionUs);
} else {
// Check if the postroll ad should be played.
int adGroupCount = period.getAdGroupCount();
@ -211,7 +223,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|| !period.isAdAvailable(adGroupCount - 1, 0)) {
return null;
}
return getMediaPeriodInfoForAd(currentPeriodId.periodIndex, adGroupCount - 1, 0);
return getMediaPeriodInfoForAd(currentPeriodId.periodIndex, adGroupCount - 1, 0,
currentMediaPeriodInfo.endPositionUs);
}
}
@ -223,8 +236,12 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
public MediaPeriodId resolvePeriodPositionForAds(int periodIndex, long positionUs) {
timeline.getPeriod(periodIndex, period);
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
return adGroupIndex == C.INDEX_UNSET ? new MediaPeriodId(periodIndex)
: new MediaPeriodId(periodIndex, adGroupIndex, 0);
if (adGroupIndex == C.INDEX_UNSET) {
return new MediaPeriodId(periodIndex);
} else {
int adIndexInAdGroup = period.getPlayedAdCount(adGroupIndex);
return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup);
}
}
/**
@ -255,17 +272,19 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
long durationUs = newId.isAd()
? period.getAdDurationUs(newId.adGroupIndex, newId.adIndexInAdGroup)
: (endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs);
return new MediaPeriodInfo(newId, startPositionUs, endPositionUs, durationUs, isLastInPeriod,
isLastInTimeline);
return new MediaPeriodInfo(newId, startPositionUs, endPositionUs, info.contentPositionUs,
durationUs, isLastInPeriod, isLastInTimeline);
}
private MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId id, long startPositionUs) {
private MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId id, long contentPositionUs,
long startPositionUs) {
timeline.getPeriod(id.periodIndex, period);
if (id.isAd()) {
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
return null;
}
return getMediaPeriodInfoForAd(id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup);
return getMediaPeriodInfoForAd(id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup,
contentPositionUs);
} else {
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE
@ -275,14 +294,14 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
}
private MediaPeriodInfo getMediaPeriodInfoForAd(int periodIndex, int adGroupIndex,
int adIndexInAdGroup) {
int adIndexInAdGroup, long contentPositionUs) {
MediaPeriodId id = new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup);
boolean isLastInPeriod = isLastInPeriod(id, C.TIME_END_OF_SOURCE);
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
long durationUs = timeline.getPeriod(id.periodIndex, period)
.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup);
return new MediaPeriodInfo(id, 0, C.TIME_END_OF_SOURCE, durationUs, isLastInPeriod,
isLastInTimeline);
return new MediaPeriodInfo(id, 0, C.TIME_END_OF_SOURCE, contentPositionUs, durationUs,
isLastInPeriod, isLastInTimeline);
}
private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs,
@ -292,7 +311,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
timeline.getPeriod(id.periodIndex, period);
long durationUs = endUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endUs;
return new MediaPeriodInfo(id, startPositionUs, endUs, durationUs, isLastInPeriod,
return new MediaPeriodInfo(id, startPositionUs, endUs, C.TIME_UNSET, durationUs, isLastInPeriod,
isLastInTimeline);
}

View File

@ -715,6 +715,11 @@ public class SimpleExoPlayer implements ExoPlayer {
return player.getCurrentAdIndexInAdGroup();
}
@Override
public long getContentPosition() {
return player.getContentPosition();
}
// Internal methods.
private void removeSurfaceCallbacks() {

View File

@ -378,6 +378,16 @@ public abstract class Timeline {
return adGroupTimesUs[adGroupIndex];
}
/**
* Returns the number of ads that have been played in the specified ad group in the period.
*
* @param adGroupIndex The ad group index.
* @return The number of ads that have been played.
*/
public int getPlayedAdCount(int adGroupIndex) {
return adsPlayedCounts[adGroupIndex];
}
/**
* Returns whether the ad group at index {@code adGroupIndex} has been played.
*