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 index 973bd8754a..d05232da2e 100644 --- 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 @@ -51,6 +51,11 @@ import java.util.Arrays; */ public final Uri[][] adUris; + /** + * The position offset in the first unplayed ad at which to begin playback, in microseconds. + */ + public long adResumePositionUs; + /** * Creates a new ad playback state with the specified ad group times. * @@ -69,12 +74,13 @@ import java.util.Arrays; } private AdPlaybackState(long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts, - int[] adsPlayedCounts, Uri[][] adUris) { + int[] adsPlayedCounts, Uri[][] adUris, long adResumePositionUs) { this.adGroupTimesUs = adGroupTimesUs; this.adCounts = adCounts; this.adsLoadedCounts = adsLoadedCounts; this.adsPlayedCounts = adsPlayedCounts; this.adUris = adUris; + this.adResumePositionUs = adResumePositionUs; adGroupCount = adGroupTimesUs.length; } @@ -87,10 +93,8 @@ import java.util.Arrays; 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); + Arrays.copyOf(adCounts, adGroupCount), Arrays.copyOf(adsLoadedCounts, adGroupCount), + Arrays.copyOf(adsPlayedCounts, adGroupCount), adUris, adResumePositionUs); } /** @@ -114,7 +118,15 @@ import java.util.Arrays; * Marks the last ad in the specified ad group as played. */ public void playedAd(int adGroupIndex) { + adResumePositionUs = 0; adsPlayedCounts[adGroupIndex]++; } + /** + * Sets the position offset in the first unplayed ad at which to begin playback, in microseconds. + */ + public void setAdResumePositionUs(long adResumePositionUs) { + this.adResumePositionUs = adResumePositionUs; + } + } 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 cf1bf255dd..65f772f729 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 @@ -226,7 +226,9 @@ public final class ImaAdsLoader implements ExoPlayer.EventListener, VideoAdPlaye player.addListener(this); if (adPlaybackState != null) { eventListener.onAdPlaybackState(adPlaybackState); - // TODO: Call adsManager.resume if an ad is playing. + if (playingAd) { + adsManager.resume(); + } } else if (adTagUri != null) { requestAds(); } @@ -239,7 +241,8 @@ public final class ImaAdsLoader implements ExoPlayer.EventListener, VideoAdPlaye */ /* package */ void detachPlayer() { if (player != null) { - if (adsManager != null && player.isPlayingAd()) { + if (adsManager != null && playingAd) { + adPlaybackState.setAdResumePositionUs(C.msToUs(player.getCurrentPosition())); adsManager.pause(); } lastAdProgress = getAdProgress(); @@ -449,12 +452,14 @@ public final class ImaAdsLoader implements ExoPlayer.EventListener, VideoAdPlaye if (DEBUG) { Log.d(TAG, "pauseAd"); } - if (player == null || !imaPlayingAd) { - // This method is called after content is resumed, and may also be called after release. + if (!imaPlayingAd) { + // This method is called after content is resumed. return; } imaPausedInAd = true; - player.setPlayWhenReady(false); + if (player != null) { + player.setPlayWhenReady(false); + } for (VideoAdPlayerCallback callback : adCallbacks) { callback.onPause(); } 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 920f294d41..b8198176e3 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 @@ -210,7 +210,7 @@ public final class ImaAdsMediaSource implements MediaSource { if (adPlaybackState != null && contentTimeline != null) { SinglePeriodAdTimeline timeline = new SinglePeriodAdTimeline(contentTimeline, adPlaybackState.adGroupTimesUs, adPlaybackState.adCounts, adPlaybackState.adsLoadedCounts, - adPlaybackState.adsPlayedCounts, adDurationsUs); + adPlaybackState.adsPlayedCounts, adDurationsUs, adPlaybackState.adResumePositionUs); listener.onSourceInfoRefreshed(timeline, contentManifest); } } 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 2236c2c002..c93f1e8f28 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 @@ -30,6 +30,7 @@ public final class SinglePeriodAdTimeline extends Timeline { private final int[] adsLoadedCounts; private final int[] adsPlayedCounts; private final long[][] adDurationsUs; + private final long adResumePositionUs; /** * Creates a new timeline with a single period containing the specified ads. @@ -45,9 +46,12 @@ public final class SinglePeriodAdTimeline extends Timeline { * @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. + * @param adResumePositionUs The position offset in the earliest unplayed ad at which to begin + * playback, in microseconds. */ public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, int[] adCounts, - int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs) { + int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs, + long adResumePositionUs) { Assertions.checkState(contentTimeline.getPeriodCount() == 1); Assertions.checkState(contentTimeline.getWindowCount() == 1); this.contentTimeline = contentTimeline; @@ -56,6 +60,7 @@ public final class SinglePeriodAdTimeline extends Timeline { this.adsLoadedCounts = adsLoadedCounts; this.adsPlayedCounts = adsPlayedCounts; this.adDurationsUs = adDurationsUs; + this.adResumePositionUs = adResumePositionUs; } @Override @@ -79,7 +84,7 @@ public final class SinglePeriodAdTimeline extends Timeline { contentTimeline.getPeriod(periodIndex, period, setIds); period.set(period.id, period.uid, period.windowIndex, period.durationUs, period.getPositionInWindowUs(), adGroupTimesUs, adCounts, adsLoadedCounts, adsPlayedCounts, - adDurationsUs); + adDurationsUs, adResumePositionUs); 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 6050d6faf3..ca4696e34a 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 @@ -300,8 +300,10 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; 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, contentPositionUs, durationUs, - isLastInPeriod, isLastInTimeline); + long startPositionUs = adIndexInAdGroup == period.getPlayedAdCount(adGroupIndex) + ? period.getAdResumePositionUs() : 0; + return new MediaPeriodInfo(id, startPositionUs, C.TIME_END_OF_SOURCE, contentPositionUs, + durationUs, isLastInPeriod, isLastInTimeline); } private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs, 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 3514aa3bbd..60650f9990 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 @@ -270,6 +270,7 @@ public abstract class Timeline { private int[] adsLoadedCounts; private int[] adsPlayedCounts; private long[][] adDurationsUs; + private long adResumePositionUs; /** * Sets the data held by this period. @@ -287,7 +288,7 @@ public abstract class Timeline { public Period set(Object id, Object uid, int windowIndex, long durationUs, long positionInWindowUs) { return set(id, uid, windowIndex, durationUs, positionInWindowUs, null, null, null, null, - null); + null, C.TIME_UNSET); } /** @@ -310,11 +311,13 @@ public abstract class Timeline { * @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. + * @param adResumePositionUs The position offset in the first unplayed ad at which to begin + * playback, in microseconds. * @return This period, for convenience. */ public Period set(Object id, Object uid, int windowIndex, long durationUs, long positionInWindowUs, long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts, - int[] adsPlayedCounts, long[][] adDurationsUs) { + int[] adsPlayedCounts, long[][] adDurationsUs, long adResumePositionUs) { this.id = id; this.uid = uid; this.windowIndex = windowIndex; @@ -325,6 +328,7 @@ public abstract class Timeline { this.adsLoadedCounts = adsLoadedCounts; this.adsPlayedCounts = adsPlayedCounts; this.adDurationsUs = adDurationsUs; + this.adResumePositionUs = adResumePositionUs; return this; } @@ -479,6 +483,14 @@ public abstract class Timeline { return adDurationsUs[adGroupIndex][adIndexInAdGroup]; } + /** + * Returns the position offset in the first unplayed ad at which to begin playback, in + * microseconds. + */ + public long getAdResumePositionUs() { + return adResumePositionUs; + } + } /**