Commit 07de4d1b by andrewlewis Committed by Oliver Woodman

Handle ad skipping and content resume

SKIPPED can't be handled as CONTENT_RESUME_REQUESTED because after skipping an
ad there may be further ads to play in its ad group.

Remove workaround for handling unexpected playAd without stopAd, as the player
can instead recover when IMA sends CONTENT_RESUME_REQUESTED. This in turn fixes
handling of the case where playAd is called twice but IMA expects only the
first ad to play, when skipping a particular ad. (Add an ad tag where this
occurs to internal samples.)

Check whether a currently playing ad has been marked as played in
ExoPlayerImplInternal, and handle this case as a seek. This ensures that any
loaded ad periods are discarded in the case of CONTENT_RESUME_REQUESTED.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=162610621
parent 94b08b27
......@@ -123,6 +123,17 @@ import java.util.Arrays;
}
/**
* Marks all ads in the specified ad group as played.
*/
public void playedAdGroup(int adGroupIndex) {
adResumePositionUs = 0;
if (adCounts[adGroupIndex] == C.LENGTH_UNSET) {
adCounts[adGroupIndex] = 0;
}
adsPlayedCounts[adGroupIndex] = adCounts[adGroupIndex];
}
/**
* Sets the position offset in the first unplayed ad at which to begin playback, in microseconds.
*/
public void setAdResumePositionUs(long adResumePositionUs) {
......
......@@ -337,7 +337,6 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
imaPausedContent = true;
pauseContentInternal();
break;
case SKIPPED: // Fall through.
case CONTENT_RESUME_REQUESTED:
imaPausedContent = false;
resumeContentInternal();
......@@ -432,9 +431,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
}
if (imaPlayingAd && !imaPausedInAd) {
// Work around an issue where IMA does not always call stopAd before resuming content.
// See [Internal: b/38354028].
// See [Internal: b/38354028, b/63320878].
Log.w(TAG, "Unexpected playAd without stopAd");
stopAdInternal();
}
if (!imaPlayingAd) {
imaPlayingAd = true;
......@@ -497,8 +495,7 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
Assertions.checkArgument(timeline.getPeriodCount() == 1);
this.timeline = timeline;
contentDurationMs = C.usToMs(timeline.getPeriod(0, period).durationUs);
playingAd = player.isPlayingAd();
playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET;
updateImaStateForPlayerState();
}
@Override
......@@ -547,9 +544,7 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
if (adsManager == null) {
return;
}
boolean wasPlayingAd = playingAd;
playingAd = player.isPlayingAd();
if (!wasPlayingAd && !playingAd) {
if (!playingAd && !player.isPlayingAd()) {
long positionUs = C.msToUs(player.getCurrentPosition());
int adGroupIndex = timeline.getPeriod(0, period).getAdGroupIndexForPositionUs(positionUs);
if (adGroupIndex != C.INDEX_UNSET) {
......@@ -558,13 +553,34 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
}
return;
}
updateImaStateForPlayerState();
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
// Internal methods.
private void requestAds() {
AdsRequest request = imaSdkFactory.createAdsRequest();
request.setAdTagUrl(adTagUri.toString());
request.setAdDisplayContainer(adDisplayContainer);
request.setContentProgressProvider(this);
adsLoader.requestAds(request);
}
private void updateImaStateForPlayerState() {
boolean wasPlayingAd = playingAd;
playingAd = player.isPlayingAd();
if (!playingAd && playWhenReadyOverriddenForAds) {
playWhenReadyOverriddenForAds = false;
player.setPlayWhenReady(false);
}
if (!sentContentComplete) {
boolean adFinished =
!playingAd || playingAdIndexInAdGroup != player.getCurrentAdIndexInAdGroup();
boolean adFinished = (wasPlayingAd && !playingAd)
|| playingAdIndexInAdGroup != player.getCurrentAdIndexInAdGroup();
if (adFinished) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
......@@ -572,7 +588,7 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
callback.onEnded();
}
}
if (playingAd && !wasPlayingAd) {
if (!wasPlayingAd && playingAd) {
int adGroupIndex = player.getCurrentAdGroupIndex();
// IMA hasn't sent CONTENT_PAUSE_REQUESTED yet, so fake the content position.
Assertions.checkState(fakeContentProgressElapsedRealtimeMs == C.TIME_UNSET);
......@@ -586,29 +602,16 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET;
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
// Internal methods.
private void requestAds() {
AdsRequest request = imaSdkFactory.createAdsRequest();
request.setAdTagUrl(adTagUri.toString());
request.setAdDisplayContainer(adDisplayContainer);
request.setContentProgressProvider(this);
adsLoader.requestAds(request);
}
private void resumeContentInternal() {
if (contentDurationMs != C.TIME_UNSET && imaPlayingAd) {
// Work around an issue where IMA does not always call stopAd before resuming content.
// See [Internal: b/38354028].
if (imaPlayingAd) {
if (DEBUG) {
Log.d(TAG, "Unexpected CONTENT_RESUME_REQUESTED without stopAd");
}
stopAdInternal();
}
if (playingAd && adGroupIndex != C.INDEX_UNSET) {
adPlaybackState.playedAdGroup(adGroupIndex);
adGroupIndex = C.INDEX_UNSET;
updateAdPlaybackState();
}
clearFlags();
}
......
......@@ -1054,6 +1054,19 @@ import java.io.IOException;
return;
}
// If playing an ad, check that it hasn't been marked as played. If it has, skip forward.
if (playbackInfo.periodId.isAd()) {
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex,
playbackInfo.contentPositionUs);
if (!periodId.isAd() || periodId.adIndexInAdGroup != playbackInfo.periodId.adIndexInAdGroup) {
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.contentPositionUs);
long contentPositionUs = periodId.isAd() ? playbackInfo.contentPositionUs : C.TIME_UNSET;
playbackInfo = new PlaybackInfo(periodId, newPositionUs, contentPositionUs);
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
return;
}
}
// The current period is in the new timeline. Update the holder and playbackInfo.
periodHolder = updatePeriodInfo(periodHolder, periodIndex);
if (periodIndex != playbackInfo.periodId.periodIndex) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment