Fix postroll content complete notifications

On reaching the end of the content we would notify content complete
and skip unplayed ads, causing a timeline change. That timeline change
was handled in a way that caused a further timeline change in the
2.11.6 release, where we don't yet deduplicate no-op Timeline changes,
causing repeated timeline changes indefinitely.

At tip-of-tree, the timeline wouldn't refresh repeatedly. However the
code for sending content complete at the point of transitioning to
play a preloaded postroll ad was not correct in that it didn't mark
previous ads as skipped. Instead they happened to be marked as
skipped later on due to the timeline change handling content
completion code triggering again.

Fix this by only marking ads as skipped when content completes once,
to avoid the duplicate timeline change, and moving the skipped ad
marking so it happens in the same place as notifying content complete.

PiperOrigin-RevId: 318454908
This commit is contained in:
andrewlewis 2020-06-26 13:34:05 +01:00 committed by Andrew Lewis
parent 1a5b304b1f
commit ce8bb26802
2 changed files with 27 additions and 23 deletions

View File

@ -1,6 +1,10 @@
# Release notes # # Release notes #
### 2.11.6 (2020-06-24) ### ### 2.11.6 (2020-06-24) ###
* IMA extension: Fix the way 'content complete' is handled to avoid repeatedly
refreshing the timeline after playback ends.
### 2.11.6 (2020-06-19) ###
* UI: Prevent `PlayerView` from temporarily hiding the video surface when * UI: Prevent `PlayerView` from temporarily hiding the video surface when
seeking to an unprepared period within the current window. For example when seeking to an unprepared period within the current window. For example when

View File

@ -1026,7 +1026,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
if (imaAdState == IMA_AD_STATE_NONE if (imaAdState == IMA_AD_STATE_NONE
&& playbackState == Player.STATE_BUFFERING && playbackState == Player.STATE_BUFFERING
&& playWhenReady) { && playWhenReady) {
checkForContentComplete(); ensureSentContentCompleteIfAtEndOfStream();
} else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) { } else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) {
AdMediaInfo adMediaInfo = Assertions.checkNotNull(imaAdMediaInfo); AdMediaInfo adMediaInfo = Assertions.checkNotNull(imaAdMediaInfo);
if (adMediaInfo == null) { if (adMediaInfo == null) {
@ -1048,15 +1048,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
return; return;
} }
if (!playingAd && !player.isPlayingAd()) { if (!playingAd && !player.isPlayingAd()) {
checkForContentComplete(); ensureSentContentCompleteIfAtEndOfStream();
if (sentContentComplete) { if (!sentContentComplete && !timeline.isEmpty()) {
for (int i = 0; i < adPlaybackState.adGroupCount; i++) {
if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) {
adPlaybackState = adPlaybackState.withSkippedAdGroup(/* adGroupIndex= */ i);
}
}
updateAdPlaybackState();
} else if (!timeline.isEmpty()) {
long positionMs = getContentPeriodPositionMs(player, timeline, period); long positionMs = getContentPeriodPositionMs(player, timeline, period);
timeline.getPeriod(/* periodIndex= */ 0, period); timeline.getPeriod(/* periodIndex= */ 0, period);
int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs)); int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs));
@ -1090,11 +1083,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) {
int adGroupIndex = player.getCurrentAdGroupIndex(); int adGroupIndex = player.getCurrentAdGroupIndex();
if (adPlaybackState.adGroupTimesUs[adGroupIndex] == C.TIME_END_OF_SOURCE) { if (adPlaybackState.adGroupTimesUs[adGroupIndex] == C.TIME_END_OF_SOURCE) {
adsLoader.contentComplete(); sendContentComplete();
if (DEBUG) {
Log.d(TAG, "adsLoader.contentComplete from period transition");
}
sentContentComplete = true;
} else { } else {
// IMA hasn't called playAd yet, so fake the content position. // IMA hasn't called playAd yet, so fake the content position.
fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime();
@ -1212,18 +1201,29 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
updateAdPlaybackState(); updateAdPlaybackState();
} }
private void checkForContentComplete() { private void ensureSentContentCompleteIfAtEndOfStream() {
long positionMs = getContentPeriodPositionMs(Assertions.checkNotNull(player), timeline, period);
if (!sentContentComplete if (!sentContentComplete
&& contentDurationMs != C.TIME_UNSET && contentDurationMs != C.TIME_UNSET
&& pendingContentPositionMs == C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET
&& positionMs + THRESHOLD_END_OF_CONTENT_MS >= contentDurationMs) { && getContentPeriodPositionMs(Assertions.checkNotNull(player), timeline, period)
+ THRESHOLD_END_OF_CONTENT_MS
>= contentDurationMs) {
sendContentComplete();
}
}
private void sendContentComplete() {
adsLoader.contentComplete(); adsLoader.contentComplete();
if (DEBUG) {
Log.d(TAG, "adsLoader.contentComplete from content position check");
}
sentContentComplete = true; sentContentComplete = true;
if (DEBUG) {
Log.d(TAG, "adsLoader.contentComplete");
} }
for (int i = 0; i < adPlaybackState.adGroupCount; i++) {
if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) {
adPlaybackState = adPlaybackState.withSkippedAdGroup(/* adGroupIndex= */ i);
}
}
updateAdPlaybackState();
} }
private void updateAdPlaybackState() { private void updateAdPlaybackState() {