diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdPlaybackState.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdPlaybackState.java new file mode 100644 index 00000000000..973bd8754ab --- /dev/null +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdPlaybackState.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.ima; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import java.util.Arrays; + +/** + * Represents the structure of ads to play and the state of loaded/played ads. + */ +/* package */ final class AdPlaybackState { + + /** + * The number of ad groups. + */ + public final int adGroupCount; + /** + * The times of ad groups, in microseconds. A final element with the value + * {@link C#TIME_END_OF_SOURCE} indicates a postroll ad. + */ + public final long[] adGroupTimesUs; + /** + * The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET} if the number of + * ads is not yet known. + */ + public final int[] adCounts; + /** + * The number of ads loaded so far in each ad group. + */ + public final int[] adsLoadedCounts; + /** + * The number of ads played so far in each ad group. + */ + public final int[] adsPlayedCounts; + /** + * The URI of each ad in each ad group. + */ + public final Uri[][] adUris; + + /** + * Creates a new ad playback state with the specified ad group times. + * + * @param adGroupTimesUs The times of ad groups in microseconds. A final element with the value + * {@link C#TIME_END_OF_SOURCE} indicates that there is a postroll ad. + */ + public AdPlaybackState(long[] adGroupTimesUs) { + this.adGroupTimesUs = adGroupTimesUs; + adGroupCount = adGroupTimesUs.length; + adsPlayedCounts = new int[adGroupCount]; + adCounts = new int[adGroupCount]; + Arrays.fill(adCounts, C.LENGTH_UNSET); + adUris = new Uri[adGroupCount][]; + Arrays.fill(adUris, new Uri[0]); + adsLoadedCounts = new int[adGroupTimesUs.length]; + } + + private AdPlaybackState(long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts, + int[] adsPlayedCounts, Uri[][] adUris) { + this.adGroupTimesUs = adGroupTimesUs; + this.adCounts = adCounts; + this.adsLoadedCounts = adsLoadedCounts; + this.adsPlayedCounts = adsPlayedCounts; + this.adUris = adUris; + adGroupCount = adGroupTimesUs.length; + } + + /** + * Returns a deep copy of this instance. + */ + public AdPlaybackState copy() { + Uri[][] adUris = new Uri[adGroupTimesUs.length][]; + for (int i = 0; i < this.adUris.length; i++) { + adUris[i] = Arrays.copyOf(this.adUris[i], this.adUris[i].length); + } + return new AdPlaybackState(Arrays.copyOf(adGroupTimesUs, adGroupCount), + Arrays.copyOf(adCounts, adGroupCount), + Arrays.copyOf(adsLoadedCounts, adGroupCount), + Arrays.copyOf(adsPlayedCounts, adGroupCount), + adUris); + } + + /** + * Sets the number of ads in the specified ad group. + */ + public void setAdCount(int adGroupIndex, int adCount) { + adCounts[adGroupIndex] = adCount; + } + + /** + * Adds an ad to the specified ad group. + */ + public void addAdUri(int adGroupIndex, Uri uri) { + int adIndexInAdGroup = adUris[adGroupIndex].length; + adUris[adGroupIndex] = Arrays.copyOf(adUris[adGroupIndex], adIndexInAdGroup + 1); + adUris[adGroupIndex][adIndexInAdGroup] = uri; + adsLoadedCounts[adGroupIndex]++; + } + + /** + * Marks the last ad in the specified ad group as played. + */ + public void playedAd(int adGroupIndex) { + adsPlayedCounts[adGroupIndex]++; + } + +} diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 0b14f16256c..89c8e61b7fe 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -66,35 +66,11 @@ public interface EventListener { /** - * Called when the times of ad groups are known. + * Called when the ad playback state has been updated. * - * @param adGroupTimesUs The times of ad groups, in microseconds. + * @param adPlaybackState The new ad playback state. */ - void onAdGroupTimesUsLoaded(long[] adGroupTimesUs); - - /** - * Called when an ad group has been played to the end. - * - * @param adGroupIndex The index of the ad group. - */ - void onAdGroupPlayedToEnd(int adGroupIndex); - - /** - * Called when the URI for the media of an ad has been loaded. - * - * @param adGroupIndex The index of the ad group containing the ad with the media URI. - * @param adIndexInAdGroup The index of the ad in its ad group. - * @param uri The URI for the ad's media. - */ - void onAdUriLoaded(int adGroupIndex, int adIndexInAdGroup, Uri uri); - - /** - * Called when an ad group has loaded. - * - * @param adGroupIndex The index of the ad group containing the ad. - * @param adCountInAdGroup The number of ads in the ad group. - */ - void onAdGroupLoaded(int adGroupIndex, int adCountInAdGroup); + void onAdPlaybackState(AdPlaybackState adPlaybackState); /** * Called when there was an error loading ads. @@ -126,10 +102,9 @@ public interface EventListener { private final AdsLoader adsLoader; private AdsManager adsManager; - private long[] adGroupTimesUs; - private int[] adsLoadedInAdGroup; private Timeline timeline; private long contentDurationMs; + private AdPlaybackState adPlaybackState; private boolean released; @@ -263,9 +238,9 @@ public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) { Log.d(TAG, "Initialized without preloading"); } } - adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); - adsLoadedInAdGroup = new int[adGroupTimesUs.length]; - eventListener.onAdGroupTimesUsLoaded(adGroupTimesUs); + long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); + adPlaybackState = new AdPlaybackState(adGroupTimesUs); + updateAdPlaybackState(); } // AdEvent.AdEventListener implementation. @@ -285,7 +260,7 @@ public void onAdEvent(AdEvent adEvent) { // The ad position is not always accurate when using preloading. See [Internal: b/62613240]. AdPodInfo adPodInfo = ad.getAdPodInfo(); int podIndex = adPodInfo.getPodIndex(); - adGroupIndex = podIndex == -1 ? adGroupTimesUs.length - 1 : podIndex; + adGroupIndex = podIndex == -1 ? adPlaybackState.adGroupCount - 1 : podIndex; int adPosition = adPodInfo.getAdPosition(); int adCountInAdGroup = adPodInfo.getTotalAds(); adsManager.start(); @@ -293,7 +268,8 @@ public void onAdEvent(AdEvent adEvent) { Log.d(TAG, "Loaded ad " + adPosition + " of " + adCountInAdGroup + " in ad group " + adGroupIndex); } - eventListener.onAdGroupLoaded(adGroupIndex, adCountInAdGroup); + adPlaybackState.setAdCount(adGroupIndex, adCountInAdGroup); + updateAdPlaybackState(); break; case CONTENT_PAUSE_REQUESTED: // After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads @@ -332,7 +308,7 @@ public VideoProgressUpdate getContentProgress() { return new VideoProgressUpdate(pendingContentPositionMs, contentDurationMs); } if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { - long adGroupTimeMs = C.usToMs(adGroupTimesUs[adGroupIndex]); + long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); if (adGroupTimeMs == C.TIME_END_OF_SOURCE) { adGroupTimeMs = contentDurationMs; } @@ -357,11 +333,11 @@ public VideoProgressUpdate getAdProgress() { @Override public void loadAd(String adUriString) { - int adIndexInAdGroup = adsLoadedInAdGroup[adGroupIndex]++; if (DEBUG) { - Log.d(TAG, "loadAd at index " + adIndexInAdGroup + " in ad group " + adGroupIndex); + Log.d(TAG, "loadAd in ad group " + adGroupIndex); } - eventListener.onAdUriLoaded(adGroupIndex, adIndexInAdGroup, Uri.parse(adUriString)); + adPlaybackState.addAdUri(adGroupIndex, Uri.parse(adUriString)); + updateAdPlaybackState(); } @Override @@ -518,7 +494,7 @@ public void onPositionDiscontinuity() { // IMA hasn't sent CONTENT_PAUSE_REQUESTED yet, so fake the content position. Assertions.checkState(fakeContentProgressElapsedRealtimeMs == C.TIME_UNSET); fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - if (adGroupIndex == adGroupTimesUs.length - 1) { + if (adGroupIndex == adPlaybackState.adGroupCount - 1) { adsLoader.contentComplete(); if (DEBUG) { Log.d(TAG, "adsLoader.contentComplete"); @@ -569,8 +545,9 @@ private void pauseContentInternal() { private void stopAdInternal() { Assertions.checkState(playingAd); player.setPlayWhenReady(false); + adPlaybackState.playedAd(adGroupIndex); + updateAdPlaybackState(); if (!player.isPlayingAd()) { - eventListener.onAdGroupPlayedToEnd(adGroupIndex); adGroupIndex = C.INDEX_UNSET; } clearFlags(); @@ -595,6 +572,10 @@ private void checkForContentComplete() { } } + private void updateAdPlaybackState() { + eventListener.onAdPlaybackState(adPlaybackState.copy()); + } + private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index 5e96bd26dc9..5a54a59cac5 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -49,7 +49,7 @@ public final class ImaAdsMediaSource implements MediaSource { private final ViewGroup adUiViewGroup; private final ImaSdkSettings imaSdkSettings; private final Handler mainHandler; - private final AdListener adLoaderListener; + private final AdsLoaderListener adsLoaderListener; private final Map adMediaSourceByMediaPeriod; private final Timeline.Period period; @@ -60,11 +60,8 @@ public final class ImaAdsMediaSource implements MediaSource { // Accessed on the player thread. private Timeline contentTimeline; private Object contentManifest; - private long[] adGroupTimesUs; - private boolean[] hasPlayedAdGroup; - private int[] adCounts; + private AdPlaybackState adPlaybackState; private MediaSource[][] adGroupMediaSources; - private boolean[][] isAdAvailable; private long[][] adDurationsUs; private MediaSource.Listener listener; private IOException adLoadError; @@ -113,11 +110,10 @@ public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory data this.adUiViewGroup = adUiViewGroup; this.imaSdkSettings = imaSdkSettings; mainHandler = new Handler(Looper.getMainLooper()); - adLoaderListener = new AdListener(); + adsLoaderListener = new AdsLoaderListener(); adMediaSourceByMediaPeriod = new HashMap<>(); period = new Timeline.Period(); adGroupMediaSources = new MediaSource[0][]; - isAdAvailable = new boolean[0][]; adDurationsUs = new long[0][]; } @@ -131,7 +127,7 @@ public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener l @Override public void run() { imaAdsLoader = new ImaAdsLoader(context, adTagUri, adUiViewGroup, imaSdkSettings, - ImaAdsMediaSource.this.player, adLoaderListener); + ImaAdsMediaSource.this.player, adsLoaderListener); } }); contentMediaSource.prepareSource(player, false, new Listener() { @@ -158,7 +154,29 @@ public void maybeThrowSourceInfoRefreshError() throws IOException { @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { if (id.isAd()) { - MediaSource mediaSource = adGroupMediaSources[id.adGroupIndex][id.adIndexInAdGroup]; + final int adGroupIndex = id.adGroupIndex; + final int adIndexInAdGroup = id.adIndexInAdGroup; + if (adGroupMediaSources[adGroupIndex].length <= adIndexInAdGroup) { + MediaSource adMediaSource = new ExtractorMediaSource( + adPlaybackState.adUris[id.adGroupIndex][id.adIndexInAdGroup], dataSourceFactory, + new DefaultExtractorsFactory(), mainHandler, adsLoaderListener); + int oldAdCount = adGroupMediaSources[id.adGroupIndex].length; + if (adIndexInAdGroup >= oldAdCount) { + int adCount = adIndexInAdGroup + 1; + adGroupMediaSources[adGroupIndex] = + Arrays.copyOf(adGroupMediaSources[adGroupIndex], adCount); + adDurationsUs[adGroupIndex] = Arrays.copyOf(adDurationsUs[adGroupIndex], adCount); + Arrays.fill(adDurationsUs[adGroupIndex], oldAdCount, adCount, C.TIME_UNSET); + } + adGroupMediaSources[adGroupIndex][adIndexInAdGroup] = adMediaSource; + adMediaSource.prepareSource(player, false, new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + onAdSourceInfoRefreshed(adGroupIndex, adIndexInAdGroup, timeline); + } + }); + } + MediaSource mediaSource = adGroupMediaSources[adGroupIndex][adIndexInAdGroup]; MediaPeriod mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(0), allocator); adMediaSourceByMediaPeriod.put(mediaPeriod, mediaSource); return mediaPeriod; @@ -200,19 +218,14 @@ public void run() { // Internal methods. - private void onAdGroupTimesUsLoaded(long[] adGroupTimesUs) { - Assertions.checkState(this.adGroupTimesUs == null); - int adGroupCount = adGroupTimesUs.length; - this.adGroupTimesUs = adGroupTimesUs; - hasPlayedAdGroup = new boolean[adGroupCount]; - adCounts = new int[adGroupCount]; - Arrays.fill(adCounts, C.LENGTH_UNSET); - adGroupMediaSources = new MediaSource[adGroupCount][]; - Arrays.fill(adGroupMediaSources, new MediaSource[0]); - isAdAvailable = new boolean[adGroupCount][]; - Arrays.fill(isAdAvailable, new boolean[0]); - adDurationsUs = new long[adGroupCount][]; - Arrays.fill(adDurationsUs, new long[0]); + private void onAdPlaybackState(AdPlaybackState adPlaybackState) { + if (this.adPlaybackState == null) { + adGroupMediaSources = new MediaSource[adPlaybackState.adGroupCount][]; + Arrays.fill(adGroupMediaSources, new MediaSource[0]); + adDurationsUs = new long[adPlaybackState.adGroupCount][]; + Arrays.fill(adDurationsUs, new long[0]); + } + this.adPlaybackState = adPlaybackState; maybeUpdateSourceInfo(); } @@ -222,49 +235,17 @@ private void onContentSourceInfoRefreshed(Timeline timeline, Object manifest) { maybeUpdateSourceInfo(); } - private void onAdGroupPlayedToEnd(int adGroupIndex) { - hasPlayedAdGroup[adGroupIndex] = true; - maybeUpdateSourceInfo(); - } - - private void onAdUriLoaded(final int adGroupIndex, final int adIndexInAdGroup, Uri uri) { - MediaSource adMediaSource = new ExtractorMediaSource(uri, dataSourceFactory, - new DefaultExtractorsFactory(), mainHandler, adLoaderListener); - int oldAdCount = adGroupMediaSources[adGroupIndex].length; - if (adIndexInAdGroup >= oldAdCount) { - int adCount = adIndexInAdGroup + 1; - adGroupMediaSources[adGroupIndex] = Arrays.copyOf(adGroupMediaSources[adGroupIndex], adCount); - isAdAvailable[adGroupIndex] = Arrays.copyOf(isAdAvailable[adGroupIndex], adCount); - adDurationsUs[adGroupIndex] = Arrays.copyOf(adDurationsUs[adGroupIndex], adCount); - Arrays.fill(adDurationsUs[adGroupIndex], oldAdCount, adCount, C.TIME_UNSET); - } - adGroupMediaSources[adGroupIndex][adIndexInAdGroup] = adMediaSource; - isAdAvailable[adGroupIndex][adIndexInAdGroup] = true; - adMediaSource.prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { - onAdSourceInfoRefreshed(adGroupIndex, adIndexInAdGroup, timeline); - } - }); - } - private void onAdSourceInfoRefreshed(int adGroupIndex, int adIndexInAdGroup, Timeline timeline) { Assertions.checkArgument(timeline.getPeriodCount() == 1); adDurationsUs[adGroupIndex][adIndexInAdGroup] = timeline.getPeriod(0, period).getDurationUs(); maybeUpdateSourceInfo(); } - private void onAdGroupLoaded(int adGroupIndex, int adCountInAdGroup) { - if (adCounts[adGroupIndex] == C.LENGTH_UNSET) { - adCounts[adGroupIndex] = adCountInAdGroup; - maybeUpdateSourceInfo(); - } - } - private void maybeUpdateSourceInfo() { - if (adGroupTimesUs != null && contentTimeline != null) { - SinglePeriodAdTimeline timeline = new SinglePeriodAdTimeline(contentTimeline, adGroupTimesUs, - hasPlayedAdGroup, adCounts, isAdAvailable, adDurationsUs); + if (adPlaybackState != null && contentTimeline != null) { + SinglePeriodAdTimeline timeline = new SinglePeriodAdTimeline(contentTimeline, + adPlaybackState.adGroupTimesUs, adPlaybackState.adCounts, adPlaybackState.adsLoadedCounts, + adPlaybackState.adsPlayedCounts, adDurationsUs); listener.onSourceInfoRefreshed(timeline, contentManifest); } } @@ -272,59 +253,11 @@ private void maybeUpdateSourceInfo() { /** * Listener for ad loading events. All methods are called on the main thread. */ - private final class AdListener implements ImaAdsLoader.EventListener, + private final class AdsLoaderListener implements ImaAdsLoader.EventListener, ExtractorMediaSource.EventListener { @Override - public void onAdGroupTimesUsLoaded(final long[] adGroupTimesUs) { - if (released) { - return; - } - playerHandler.post(new Runnable() { - @Override - public void run() { - if (released) { - return; - } - ImaAdsMediaSource.this.onAdGroupTimesUsLoaded(adGroupTimesUs); - } - }); - } - - @Override - public void onAdGroupPlayedToEnd(final int adGroupIndex) { - if (released) { - return; - } - playerHandler.post(new Runnable() { - @Override - public void run() { - if (released) { - return; - } - ImaAdsMediaSource.this.onAdGroupPlayedToEnd(adGroupIndex); - } - }); - } - - @Override - public void onAdUriLoaded(final int adGroupIndex, final int adIndexInAdGroup, final Uri uri) { - if (released) { - return; - } - playerHandler.post(new Runnable() { - @Override - public void run() { - if (released) { - return; - } - ImaAdsMediaSource.this.onAdUriLoaded(adGroupIndex, adIndexInAdGroup, uri); - } - }); - } - - @Override - public void onAdGroupLoaded(final int adGroupIndex, final int adCountInAdGroup) { + public void onAdPlaybackState(final AdPlaybackState adPlaybackState) { if (released) { return; } @@ -334,7 +267,7 @@ public void run() { if (released) { return; } - ImaAdsMediaSource.this.onAdGroupLoaded(adGroupIndex, adCountInAdGroup); + ImaAdsMediaSource.this.onAdPlaybackState(adPlaybackState); } }); } diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/SinglePeriodAdTimeline.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/SinglePeriodAdTimeline.java index 78d3bb9e739..2236c2c0024 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/SinglePeriodAdTimeline.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/SinglePeriodAdTimeline.java @@ -26,9 +26,9 @@ public final class SinglePeriodAdTimeline extends Timeline { private final Timeline contentTimeline; private final long[] adGroupTimesUs; - private final boolean[] hasPlayedAdGroup; private final int[] adCounts; - private final boolean[][] isAdAvailable; + private final int[] adsLoadedCounts; + private final int[] adsPlayedCounts; private final long[][] adDurationsUs; /** @@ -39,23 +39,22 @@ public final class SinglePeriodAdTimeline extends Timeline { * @param adGroupTimesUs The times of ad groups relative to the start of the period, in * microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that * the period has a postroll ad. - * @param hasPlayedAdGroup Whether each ad group has been played. * @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET} * if the number of ads is not yet known. - * @param isAdAvailable Whether each ad in each ad group is available. + * @param adsLoadedCounts The number of ads loaded so far in each ad group. + * @param adsPlayedCounts The number of ads played so far in each ad group. * @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element * may be {@link C#TIME_UNSET} if the duration is not yet known. */ - public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, - boolean[] hasPlayedAdGroup, int[] adCounts, boolean[][] isAdAvailable, - long[][] adDurationsUs) { + public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, int[] adCounts, + int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs) { Assertions.checkState(contentTimeline.getPeriodCount() == 1); Assertions.checkState(contentTimeline.getWindowCount() == 1); this.contentTimeline = contentTimeline; this.adGroupTimesUs = adGroupTimesUs; - this.hasPlayedAdGroup = hasPlayedAdGroup; this.adCounts = adCounts; - this.isAdAvailable = isAdAvailable; + this.adsLoadedCounts = adsLoadedCounts; + this.adsPlayedCounts = adsPlayedCounts; this.adDurationsUs = adDurationsUs; } @@ -79,8 +78,8 @@ public int getPeriodCount() { public Period getPeriod(int periodIndex, Period period, boolean setIds) { contentTimeline.getPeriod(periodIndex, period, setIds); period.set(period.id, period.uid, period.windowIndex, period.durationUs, - period.getPositionInWindowUs(), adGroupTimesUs, hasPlayedAdGroup, adCounts, - isAdAvailable, adDurationsUs); + period.getPositionInWindowUs(), adGroupTimesUs, adCounts, adsLoadedCounts, adsPlayedCounts, + adDurationsUs); return period; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfoSequence.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfoSequence.java index 953736d58be..d2b9c2fefe5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfoSequence.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfoSequence.java @@ -181,8 +181,7 @@ public MediaPeriodInfo getNextMediaPeriodInfo(MediaPeriodInfo currentMediaPeriod if (currentPeriodId.isAd()) { int currentAdGroupIndex = currentPeriodId.adGroupIndex; timeline.getPeriod(currentPeriodId.periodIndex, period); - int adCountInCurrentAdGroup = period.getAdGroupCount() == C.LENGTH_UNSET ? C.LENGTH_UNSET - : period.getAdCountInAdGroup(currentAdGroupIndex); + int adCountInCurrentAdGroup = period.getAdCountInAdGroup(currentAdGroupIndex); if (adCountInCurrentAdGroup == C.LENGTH_UNSET) { return null; } @@ -206,7 +205,7 @@ public MediaPeriodInfo getNextMediaPeriodInfo(MediaPeriodInfo currentMediaPeriod } else { // Check if the postroll ad should be played. int adGroupCount = period.getAdGroupCount(); - if (adGroupCount == C.LENGTH_UNSET || adGroupCount == 0 + if (adGroupCount == 0 || period.getAdGroupTimeUs(adGroupCount - 1) != C.TIME_END_OF_SOURCE || period.hasPlayedAdGroup(adGroupCount - 1) || !period.isAdAvailable(adGroupCount - 1, 0)) { @@ -302,9 +301,6 @@ private boolean isLastInPeriod(MediaPeriodId id, long endPositionUs) { if (adGroupCount == 0) { return true; } - if (adGroupCount == C.LENGTH_UNSET) { - return false; - } int lastAdGroupIndex = adGroupCount - 1; boolean periodHasPostrollAd = period.getAdGroupTimeUs(lastAdGroupIndex) == C.TIME_END_OF_SOURCE; if (!id.isAd()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 19e66f90312..de66b5775bb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -266,9 +266,9 @@ public static final class Period { private long positionInWindowUs; private long[] adGroupTimesUs; - private boolean[] hasPlayedAdGroup; private int[] adCounts; - private boolean[][] isAdAvailable; + private int[] adsLoadedCounts; + private int[] adsPlayedCounts; private long[][] adDurationsUs; /** @@ -304,26 +304,26 @@ public Period set(Object id, Object uid, int windowIndex, long durationUs, * @param adGroupTimesUs The times of ad groups relative to the start of the period, in * microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that * the period has a postroll ad. - * @param hasPlayedAdGroup Whether each ad group has been played. * @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET} * if the number of ads is not yet known. - * @param isAdAvailable Whether each ad in each ad group is available. + * @param adsLoadedCounts The number of ads loaded so far in each ad group. + * @param adsPlayedCounts The number of ads played so far in each ad group. * @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element * may be {@link C#TIME_UNSET} if the duration is not yet known. * @return This period, for convenience. */ public Period set(Object id, Object uid, int windowIndex, long durationUs, - long positionInWindowUs, long[] adGroupTimesUs, boolean[] hasPlayedAdGroup, int[] adCounts, - boolean[][] isAdAvailable, long[][] adDurationsUs) { + long positionInWindowUs, long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts, + int[] adsPlayedCounts, long[][] adDurationsUs) { this.id = id; this.uid = uid; this.windowIndex = windowIndex; this.durationUs = durationUs; this.positionInWindowUs = positionInWindowUs; this.adGroupTimesUs = adGroupTimesUs; - this.hasPlayedAdGroup = hasPlayedAdGroup; this.adCounts = adCounts; - this.isAdAvailable = isAdAvailable; + this.adsLoadedCounts = adsLoadedCounts; + this.adsPlayedCounts = adsPlayedCounts; this.adDurationsUs = adDurationsUs; return this; } @@ -375,9 +375,6 @@ public int getAdGroupCount() { * @return The time of the ad group at the index, in microseconds. */ public long getAdGroupTimeUs(int adGroupIndex) { - if (adGroupTimesUs == null) { - throw new IndexOutOfBoundsException(); - } return adGroupTimesUs[adGroupIndex]; } @@ -388,10 +385,8 @@ public long getAdGroupTimeUs(int adGroupIndex) { * @return Whether the ad group at index {@code adGroupIndex} has been played. */ public boolean hasPlayedAdGroup(int adGroupIndex) { - if (hasPlayedAdGroup == null) { - throw new IndexOutOfBoundsException(); - } - return hasPlayedAdGroup[adGroupIndex]; + return adCounts[adGroupIndex] != C.INDEX_UNSET + && adsPlayedCounts[adGroupIndex] == adCounts[adGroupIndex]; } /** @@ -445,9 +440,6 @@ public int getAdGroupIndexAfterPositionUs(long positionUs) { * @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known. */ public int getAdCountInAdGroup(int adGroupIndex) { - if (adCounts == null) { - throw new IndexOutOfBoundsException(); - } return adCounts[adGroupIndex]; } @@ -459,9 +451,7 @@ public int getAdCountInAdGroup(int adGroupIndex) { * @return Whether the URL for the specified ad is known. */ public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) { - return isAdAvailable != null && adGroupIndex < isAdAvailable.length - && adIndexInAdGroup < isAdAvailable[adGroupIndex].length - && isAdAvailable[adGroupIndex][adIndexInAdGroup]; + return adIndexInAdGroup < adsLoadedCounts[adGroupIndex]; } /** @@ -473,9 +463,6 @@ public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) { * @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known. */ public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) { - if (adDurationsUs == null) { - throw new IndexOutOfBoundsException(); - } if (adIndexInAdGroup >= adDurationsUs[adGroupIndex].length) { return C.TIME_UNSET; }