Switch the IMA extension to use in-period ads
This also adds support for seeking in periods with midroll ads. Remove Timeline.Period.isAd. Issue: #2617 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160510702
This commit is contained in:
parent
6509dce6b7
commit
1f815db367
@ -36,9 +36,6 @@ section of the app.
|
||||
|
||||
This is a preview version with some known issues:
|
||||
|
||||
* Seeking is not yet ad aware. This means that it's possible to seek back into
|
||||
ads that have already been played, and also seek past midroll ads without
|
||||
them being played. Seeking will be made ad aware for the first stable release.
|
||||
* Midroll ads are not yet fully supported. `playAd` and `AD_STARTED` events are
|
||||
sometimes delayed, meaning that midroll ads take a long time to start and the
|
||||
ad overlay does not show immediately.
|
||||
|
@ -1,275 +0,0 @@
|
||||
/*
|
||||
* 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.util.Pair;
|
||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A {@link Timeline} for {@link ImaAdsMediaSource}.
|
||||
*/
|
||||
/* package */ final class AdTimeline extends Timeline {
|
||||
|
||||
private static final Object AD_ID = new Object();
|
||||
|
||||
/**
|
||||
* Builder for ad timelines.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final Timeline contentTimeline;
|
||||
private final long contentDurationUs;
|
||||
private final ArrayList<Boolean> isAd;
|
||||
private final ArrayList<Ad> ads;
|
||||
private final ArrayList<Long> startTimesUs;
|
||||
private final ArrayList<Long> endTimesUs;
|
||||
private final ArrayList<Object> uids;
|
||||
|
||||
/**
|
||||
* Creates a new ad timeline builder using the specified {@code contentTimeline} as the timeline
|
||||
* of the content within which to insert ad breaks.
|
||||
*
|
||||
* @param contentTimeline The timeline of the content within which to insert ad breaks.
|
||||
*/
|
||||
public Builder(Timeline contentTimeline) {
|
||||
this.contentTimeline = contentTimeline;
|
||||
contentDurationUs = contentTimeline.getPeriod(0, new Period()).durationUs;
|
||||
isAd = new ArrayList<>();
|
||||
ads = new ArrayList<>();
|
||||
startTimesUs = new ArrayList<>();
|
||||
endTimesUs = new ArrayList<>();
|
||||
uids = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an ad period. Each individual ad in an ad pod is represented by a separate ad period.
|
||||
*
|
||||
* @param ad The {@link Ad} instance representing the ad break, or {@code null} if not known.
|
||||
* @param adBreakIndex The index of the ad break that contains the ad in the timeline.
|
||||
* @param adIndexInAdBreak The index of the ad in its ad break.
|
||||
* @param durationUs The duration of the ad, in microseconds. May be {@link C#TIME_UNSET}.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder addAdPeriod(Ad ad, int adBreakIndex, int adIndexInAdBreak, long durationUs) {
|
||||
isAd.add(true);
|
||||
ads.add(ad);
|
||||
startTimesUs.add(0L);
|
||||
endTimesUs.add(durationUs);
|
||||
uids.add(Pair.create(adBreakIndex, adIndexInAdBreak));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a content period.
|
||||
*
|
||||
* @param startTimeUs The start time of the period relative to the start of the content
|
||||
* timeline, in microseconds.
|
||||
* @param endTimeUs The end time of the period relative to the start of the content timeline, in
|
||||
* microseconds. May be {@link C#TIME_UNSET} to include the rest of the content.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder addContent(long startTimeUs, long endTimeUs) {
|
||||
ads.add(null);
|
||||
isAd.add(false);
|
||||
startTimesUs.add(startTimeUs);
|
||||
endTimesUs.add(endTimeUs == C.TIME_UNSET ? contentDurationUs : endTimeUs);
|
||||
uids.add(Pair.create(startTimeUs, endTimeUs));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the ad timeline.
|
||||
*/
|
||||
public AdTimeline build() {
|
||||
int periodCount = uids.size();
|
||||
Assertions.checkState(periodCount > 0);
|
||||
Ad[] ads = new Ad[periodCount];
|
||||
boolean[] isAd = new boolean[periodCount];
|
||||
long[] startTimesUs = new long[periodCount];
|
||||
long[] endTimesUs = new long[periodCount];
|
||||
for (int i = 0; i < periodCount; i++) {
|
||||
ads[i] = this.ads.get(i);
|
||||
isAd[i] = this.isAd.get(i);
|
||||
startTimesUs[i] = this.startTimesUs.get(i);
|
||||
endTimesUs[i] = this.endTimesUs.get(i);
|
||||
}
|
||||
Object[] uids = this.uids.toArray(new Object[periodCount]);
|
||||
return new AdTimeline(contentTimeline, isAd, ads, startTimesUs, endTimesUs, uids);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Period contentPeriod;
|
||||
private final Window contentWindow;
|
||||
private final boolean[] isAd;
|
||||
private final Ad[] ads;
|
||||
private final long[] startTimesUs;
|
||||
private final long[] endTimesUs;
|
||||
private final Object[] uids;
|
||||
|
||||
private AdTimeline(Timeline contentTimeline, boolean[] isAd, Ad[] ads, long[] startTimesUs,
|
||||
long[] endTimesUs, Object[] uids) {
|
||||
contentWindow = contentTimeline.getWindow(0, new Window(), true);
|
||||
contentPeriod = contentTimeline.getPeriod(0, new Period(), true);
|
||||
this.isAd = isAd;
|
||||
this.ads = ads;
|
||||
this.startTimesUs = startTimesUs;
|
||||
this.endTimesUs = endTimesUs;
|
||||
this.uids = uids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the period at {@code index} contains ad media.
|
||||
*/
|
||||
public boolean isPeriodAd(int index) {
|
||||
return isAd[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of the content within which ads have been inserted, in microseconds.
|
||||
*/
|
||||
public long getContentDurationUs() {
|
||||
return contentPeriod.durationUs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start time of the period at {@code periodIndex} relative to the start of the
|
||||
* content, in microseconds.
|
||||
*
|
||||
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not a content
|
||||
* period.
|
||||
*/
|
||||
public long getContentStartTimeUs(int periodIndex) {
|
||||
Assertions.checkArgument(!isAd[periodIndex]);
|
||||
return startTimesUs[periodIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end time of the period at {@code periodIndex} relative to the start of the content,
|
||||
* in microseconds.
|
||||
*
|
||||
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not a content
|
||||
* period.
|
||||
*/
|
||||
public long getContentEndTimeUs(int periodIndex) {
|
||||
Assertions.checkArgument(!isAd[periodIndex]);
|
||||
return endTimesUs[periodIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the ad break to which the period at {@code periodIndex} belongs.
|
||||
*
|
||||
* @param periodIndex The period index.
|
||||
* @return The index of the ad break to which the period belongs.
|
||||
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not an ad.
|
||||
*/
|
||||
public int getAdBreakIndex(int periodIndex) {
|
||||
Assertions.checkArgument(isAd[periodIndex]);
|
||||
int adBreakIndex = 0;
|
||||
for (int i = 1; i < periodIndex; i++) {
|
||||
if (!isAd[i] && isAd[i - 1]) {
|
||||
adBreakIndex++;
|
||||
}
|
||||
}
|
||||
return adBreakIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the ad at {@code periodIndex} in its ad break.
|
||||
*
|
||||
* @param periodIndex The period index.
|
||||
* @return The index of the ad at {@code periodIndex} in its ad break.
|
||||
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not an ad.
|
||||
*/
|
||||
public int getAdIndexInAdBreak(int periodIndex) {
|
||||
Assertions.checkArgument(isAd[periodIndex]);
|
||||
int adIndex = 0;
|
||||
for (int i = 0; i < periodIndex; i++) {
|
||||
if (isAd[i]) {
|
||||
adIndex++;
|
||||
} else {
|
||||
adIndex = 0;
|
||||
}
|
||||
}
|
||||
return adIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowCount() {
|
||||
return uids.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
|
||||
if (repeatMode == ExoPlayer.REPEAT_MODE_ONE) {
|
||||
repeatMode = ExoPlayer.REPEAT_MODE_ALL;
|
||||
}
|
||||
return super.getNextWindowIndex(windowIndex, repeatMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
|
||||
if (repeatMode == ExoPlayer.REPEAT_MODE_ONE) {
|
||||
repeatMode = ExoPlayer.REPEAT_MODE_ALL;
|
||||
}
|
||||
return super.getPreviousWindowIndex(windowIndex, repeatMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getWindow(int index, Window window, boolean setIds,
|
||||
long defaultPositionProjectionUs) {
|
||||
long startTimeUs = startTimesUs[index];
|
||||
long durationUs = endTimesUs[index] - startTimeUs;
|
||||
if (isAd[index]) {
|
||||
window.set(ads[index], C.TIME_UNSET, C.TIME_UNSET, false, false, 0L, durationUs, index, index,
|
||||
0L);
|
||||
} else {
|
||||
window.set(contentWindow.id, contentWindow.presentationStartTimeMs + C.usToMs(startTimeUs),
|
||||
contentWindow.windowStartTimeMs + C.usToMs(startTimeUs), contentWindow.isSeekable, false,
|
||||
0L, durationUs, index, index, 0L);
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return uids.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Period getPeriod(int index, Period period, boolean setIds) {
|
||||
Object id = setIds ? (isAd[index] ? AD_ID : contentPeriod.id) : null;
|
||||
return period.set(id, uids[index], index, endTimesUs[index] - startTimesUs[index], 0,
|
||||
isAd[index]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexOfPeriod(Object uid) {
|
||||
for (int i = 0; i < uids.length; i++) {
|
||||
if (Util.areEqual(uid, uids[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
}
|
@ -66,36 +66,35 @@ import java.util.List;
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Called when the timestamps of ad breaks are known.
|
||||
* Called when the times of ad groups are known.
|
||||
*
|
||||
* @param adBreakTimesUs The times of ad breaks, in microseconds.
|
||||
* @param adGroupTimesUs The times of ad groups, in microseconds.
|
||||
*/
|
||||
void onAdBreakTimesUsLoaded(long[] adBreakTimesUs);
|
||||
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 adBreakIndex The index of the ad break containing the ad with the media URI.
|
||||
* @param adIndexInAdBreak The index of the ad in its ad break.
|
||||
* @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 onUriLoaded(int adBreakIndex, int adIndexInAdBreak, Uri uri);
|
||||
void onAdUriLoaded(int adGroupIndex, int adIndexInAdGroup, Uri uri);
|
||||
|
||||
/**
|
||||
* Called when the {@link Ad} instance for a specified ad has been loaded.
|
||||
* Called when an ad group has loaded.
|
||||
*
|
||||
* @param adBreakIndex The index of the ad break containing the ad.
|
||||
* @param adIndexInAdBreak The index of the ad in its ad break.
|
||||
* @param ad The {@link Ad} instance for the ad.
|
||||
* @param adGroupIndex The index of the ad group containing the ad.
|
||||
* @param adCountInAdGroup The number of ads in the ad group.
|
||||
*/
|
||||
void onAdLoaded(int adBreakIndex, int adIndexInAdBreak, Ad ad);
|
||||
|
||||
/**
|
||||
* Called when the specified ad break has been played to the end.
|
||||
*
|
||||
* @param adBreakIndex The index of the ad break.
|
||||
*/
|
||||
void onAdBreakPlayedToEnd(int adBreakIndex);
|
||||
void onAdGroupLoaded(int adGroupIndex, int adCountInAdGroup);
|
||||
|
||||
/**
|
||||
* Called when there was an error loading ads.
|
||||
@ -127,51 +126,45 @@ import java.util.List;
|
||||
private final AdsLoader adsLoader;
|
||||
|
||||
private AdsManager adsManager;
|
||||
private AdTimeline adTimeline;
|
||||
private long[] adGroupTimesUs;
|
||||
private int[] adsLoadedInAdGroup;
|
||||
private Timeline timeline;
|
||||
private long contentDurationMs;
|
||||
private int lastContentPeriodIndex;
|
||||
|
||||
private int playerPeriodIndex;
|
||||
|
||||
private boolean released;
|
||||
|
||||
// Fields tracking IMA's state.
|
||||
|
||||
/**
|
||||
* The index of the current ad break that IMA is loading.
|
||||
* The index of the current ad group that IMA is loading.
|
||||
*/
|
||||
private int adBreakIndex;
|
||||
private int adGroupIndex;
|
||||
/**
|
||||
* The index of the ad within its ad break, in {@link #loadAd(String)}.
|
||||
*/
|
||||
private int adIndexInAdBreak;
|
||||
/**
|
||||
* The total number of ads in the current ad break, or {@link C#INDEX_UNSET} if unknown.
|
||||
*/
|
||||
private int adCountInAdBreak;
|
||||
|
||||
/**
|
||||
* Tracks the period currently being played in IMA's model of playback.
|
||||
*/
|
||||
private int imaPeriodIndex;
|
||||
/**
|
||||
* Whether the period at {@link #imaPeriodIndex} is an ad.
|
||||
*/
|
||||
private boolean isAdDisplayed;
|
||||
/**
|
||||
* Whether {@link AdsLoader#contentComplete()} has been called since starting ad playback.
|
||||
*/
|
||||
private boolean sentContentComplete;
|
||||
/**
|
||||
* If {@link #isAdDisplayed} is set, stores whether IMA has called {@link #playAd()} and not
|
||||
* If {@link #playingAdGroupIndex} is set, stores whether IMA has called {@link #playAd()} and not
|
||||
* {@link #stopAd()}.
|
||||
*/
|
||||
private boolean playingAd;
|
||||
/**
|
||||
* If {@link #isAdDisplayed} is set, stores whether IMA has called {@link #pauseAd()} since a
|
||||
* preceding call to {@link #playAd()} for the current ad.
|
||||
* If {@link #playingAdGroupIndex} is set, stores whether IMA has called {@link #pauseAd()} since
|
||||
* a preceding call to {@link #playAd()} for the current ad.
|
||||
*/
|
||||
private boolean pausedInAd;
|
||||
/**
|
||||
* Whether {@link AdsLoader#contentComplete()} has been called since starting ad playback.
|
||||
*/
|
||||
private boolean sentContentComplete;
|
||||
|
||||
// Fields tracking the player/loader state.
|
||||
|
||||
/**
|
||||
* If the player is playing an ad, stores the ad group index. {@link C#INDEX_UNSET} otherwise.
|
||||
*/
|
||||
private int playingAdGroupIndex;
|
||||
/**
|
||||
* If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET}
|
||||
* otherwise.
|
||||
*/
|
||||
private int playingAdIndexInAdGroup;
|
||||
/**
|
||||
* If a content period has finished but IMA has not yet sent an ad event with
|
||||
* {@link AdEvent.AdEventType#CONTENT_PAUSE_REQUESTED}, stores the value of
|
||||
@ -179,6 +172,14 @@ import java.util.List;
|
||||
* determine a fake, increasing content position. {@link C#TIME_UNSET} otherwise.
|
||||
*/
|
||||
private long fakeContentProgressElapsedRealtimeMs;
|
||||
/**
|
||||
* Stores the pending content position when a seek operation was intercepted to play an ad.
|
||||
*/
|
||||
private long pendingContentPositionMs;
|
||||
/**
|
||||
* Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA.
|
||||
*/
|
||||
private boolean sentPendingContentPositionMs;
|
||||
|
||||
/**
|
||||
* Creates a new IMA ads loader.
|
||||
@ -190,8 +191,7 @@ import java.util.List;
|
||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
||||
* @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to
|
||||
* use the default settings. If set, the player type and version fields may be overwritten.
|
||||
* @param player The player instance that will play the loaded ad schedule. The player's timeline
|
||||
* must be an {@link AdTimeline} matching the loaded ad schedule.
|
||||
* @param player The player instance that will play the loaded ad schedule.
|
||||
* @param eventListener Listener for ad loader events.
|
||||
*/
|
||||
public ImaAdsLoader(Context context, Uri adTagUri, ViewGroup adUiViewGroup,
|
||||
@ -201,9 +201,10 @@ import java.util.List;
|
||||
period = new Timeline.Period();
|
||||
adCallbacks = new ArrayList<>(1);
|
||||
|
||||
lastContentPeriodIndex = C.INDEX_UNSET;
|
||||
adCountInAdBreak = C.INDEX_UNSET;
|
||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||
pendingContentPositionMs = C.TIME_UNSET;
|
||||
adGroupIndex = C.INDEX_UNSET;
|
||||
contentDurationMs = C.TIME_UNSET;
|
||||
|
||||
player.addListener(this);
|
||||
|
||||
@ -262,13 +263,16 @@ import java.util.List;
|
||||
Log.d(TAG, "Initialized without preloading");
|
||||
}
|
||||
}
|
||||
eventListener.onAdBreakTimesUsLoaded(getAdBreakTimesUs(adsManager.getAdCuePoints()));
|
||||
adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints());
|
||||
adsLoadedInAdGroup = new int[adGroupTimesUs.length];
|
||||
eventListener.onAdGroupTimesUsLoaded(adGroupTimesUs);
|
||||
}
|
||||
|
||||
// AdEvent.AdEventListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdEvent(AdEvent adEvent) {
|
||||
Ad ad = adEvent.getAd();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAdEvent " + adEvent.getType());
|
||||
}
|
||||
@ -278,20 +282,18 @@ import java.util.List;
|
||||
}
|
||||
switch (adEvent.getType()) {
|
||||
case LOADED:
|
||||
adsManager.start();
|
||||
break;
|
||||
case STARTED:
|
||||
// Note: This event is sometimes delivered several seconds after playAd is called.
|
||||
// See [Internal: b/37775441].
|
||||
Ad ad = adEvent.getAd();
|
||||
// The ad position is not always accurate when using preloading. See [Internal: b/62613240].
|
||||
AdPodInfo adPodInfo = ad.getAdPodInfo();
|
||||
adCountInAdBreak = adPodInfo.getTotalAds();
|
||||
int podIndex = adPodInfo.getPodIndex();
|
||||
adGroupIndex = podIndex == -1 ? adGroupTimesUs.length - 1 : podIndex;
|
||||
int adPosition = adPodInfo.getAdPosition();
|
||||
eventListener.onAdLoaded(adBreakIndex, adPosition - 1, ad);
|
||||
int adCountInAdGroup = adPodInfo.getTotalAds();
|
||||
adsManager.start();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Started ad " + adPosition + " of " + adCountInAdBreak + " in ad break "
|
||||
+ adBreakIndex);
|
||||
Log.d(TAG, "Loaded ad " + adPosition + " of " + adCountInAdGroup + " in ad group "
|
||||
+ adGroupIndex);
|
||||
}
|
||||
eventListener.onAdGroupLoaded(adGroupIndex, adCountInAdGroup);
|
||||
break;
|
||||
case CONTENT_PAUSE_REQUESTED:
|
||||
// After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads
|
||||
@ -325,40 +327,41 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public VideoProgressUpdate getContentProgress() {
|
||||
if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
|
||||
long contentEndTimeMs = C.usToMs(adTimeline.getContentEndTimeUs(imaPeriodIndex));
|
||||
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
|
||||
return new VideoProgressUpdate(contentEndTimeMs + elapsedSinceEndMs, contentDurationMs);
|
||||
if (pendingContentPositionMs != C.TIME_UNSET) {
|
||||
sentPendingContentPositionMs = true;
|
||||
return new VideoProgressUpdate(pendingContentPositionMs, contentDurationMs);
|
||||
}
|
||||
|
||||
if (adTimeline == null || isAdDisplayed || imaPeriodIndex != playerPeriodIndex
|
||||
|| contentDurationMs == C.TIME_UNSET) {
|
||||
if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
|
||||
long adGroupTimeMs = C.usToMs(adGroupTimesUs[adGroupIndex]);
|
||||
if (adGroupTimeMs == C.TIME_END_OF_SOURCE) {
|
||||
adGroupTimeMs = contentDurationMs;
|
||||
}
|
||||
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
|
||||
return new VideoProgressUpdate(adGroupTimeMs + elapsedSinceEndMs, contentDurationMs);
|
||||
}
|
||||
if (player.isPlayingAd() || contentDurationMs == C.TIME_UNSET) {
|
||||
return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
}
|
||||
checkForContentComplete();
|
||||
long positionMs = C.usToMs(adTimeline.getContentStartTimeUs(imaPeriodIndex))
|
||||
+ player.getCurrentPosition();
|
||||
return new VideoProgressUpdate(positionMs, contentDurationMs);
|
||||
return new VideoProgressUpdate(player.getCurrentPosition(), contentDurationMs);
|
||||
}
|
||||
|
||||
// VideoAdPlayer implementation.
|
||||
|
||||
@Override
|
||||
public VideoProgressUpdate getAdProgress() {
|
||||
if (adTimeline == null || !isAdDisplayed || imaPeriodIndex != playerPeriodIndex
|
||||
|| adTimeline.getPeriod(imaPeriodIndex, period).getDurationUs() == C.TIME_UNSET) {
|
||||
if (!player.isPlayingAd()) {
|
||||
return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
}
|
||||
return new VideoProgressUpdate(player.getCurrentPosition(), period.getDurationMs());
|
||||
return new VideoProgressUpdate(player.getCurrentPosition(), player.getDuration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAd(String adUriString) {
|
||||
int adIndexInAdGroup = adsLoadedInAdGroup[adGroupIndex]++;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "loadAd at index " + adIndexInAdBreak + " in ad break " + adBreakIndex);
|
||||
Log.d(TAG, "loadAd at index " + adIndexInAdGroup + " in ad group " + adGroupIndex);
|
||||
}
|
||||
eventListener.onUriLoaded(adBreakIndex, adIndexInAdBreak, Uri.parse(adUriString));
|
||||
adIndexInAdBreak++;
|
||||
eventListener.onAdUriLoaded(adGroupIndex, adIndexInAdGroup, Uri.parse(adUriString));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -376,7 +379,6 @@ import java.util.List;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "playAd");
|
||||
}
|
||||
Assertions.checkState(isAdDisplayed);
|
||||
if (playingAd && !pausedInAd) {
|
||||
// Work around an issue where IMA does not always call stopAd before resuming content.
|
||||
// See [Internal: b/38354028].
|
||||
@ -443,18 +445,12 @@ import java.util.List;
|
||||
// The player is being re-prepared and this source will be released.
|
||||
return;
|
||||
}
|
||||
if (adTimeline == null) {
|
||||
// TODO: Handle initial seeks after the first period.
|
||||
isAdDisplayed = timeline.getPeriod(0, period).isAd;
|
||||
imaPeriodIndex = 0;
|
||||
player.seekTo(0, 0);
|
||||
}
|
||||
adTimeline = (AdTimeline) timeline;
|
||||
contentDurationMs = C.usToMs(adTimeline.getContentDurationUs());
|
||||
lastContentPeriodIndex = adTimeline.getPeriodCount() - 1;
|
||||
while (adTimeline.isPeriodAd(lastContentPeriodIndex)) {
|
||||
// All timelines have at least one content period.
|
||||
lastContentPeriodIndex--;
|
||||
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||
this.timeline = timeline;
|
||||
contentDurationMs = C.usToMs(timeline.getPeriod(0, period).durationUs);
|
||||
if (player.isPlayingAd()) {
|
||||
playingAdGroupIndex = player.getCurrentAdGroupIndex();
|
||||
playingAdIndexInAdGroup = player.getCurrentAdIndexInAdGroup();
|
||||
}
|
||||
}
|
||||
|
||||
@ -470,9 +466,9 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
if (playbackState == ExoPlayer.STATE_BUFFERING && playWhenReady) {
|
||||
if (!playingAd && playbackState == ExoPlayer.STATE_BUFFERING && playWhenReady) {
|
||||
checkForContentComplete();
|
||||
} else if (playbackState == ExoPlayer.STATE_ENDED && isAdDisplayed) {
|
||||
} else if (playingAd && playbackState == ExoPlayer.STATE_ENDED) {
|
||||
// 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.
|
||||
for (VideoAdPlayerCallback callback : adCallbacks) {
|
||||
@ -488,7 +484,7 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
if (isAdDisplayed && adTimeline.isPeriodAd(playerPeriodIndex)) {
|
||||
if (player.isPlayingAd()) {
|
||||
for (VideoAdPlayerCallback callback : adCallbacks) {
|
||||
callback.onError();
|
||||
}
|
||||
@ -497,23 +493,41 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity() {
|
||||
if (player.getCurrentPeriodIndex() == playerPeriodIndex + 1) {
|
||||
if (isAdDisplayed) {
|
||||
// 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.
|
||||
for (VideoAdPlayerCallback callback : adCallbacks) {
|
||||
callback.onEnded();
|
||||
}
|
||||
} else {
|
||||
player.setPlayWhenReady(false);
|
||||
if (imaPeriodIndex == playerPeriodIndex) {
|
||||
// IMA hasn't sent CONTENT_PAUSE_REQUESTED yet, so fake the content position.
|
||||
Assertions.checkState(fakeContentProgressElapsedRealtimeMs == C.TIME_UNSET);
|
||||
fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||
if (!player.isPlayingAd() && playingAdGroupIndex == C.INDEX_UNSET) {
|
||||
long positionUs = C.msToUs(player.getCurrentPosition());
|
||||
int adGroupIndex = timeline.getPeriod(0, period).getAdGroupIndexForPositionUs(positionUs);
|
||||
if (adGroupIndex != C.INDEX_UNSET) {
|
||||
sentPendingContentPositionMs = false;
|
||||
pendingContentPositionMs = player.getCurrentPosition();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean adFinished = (!player.isPlayingAd() && playingAdGroupIndex != C.INDEX_UNSET)
|
||||
|| (player.isPlayingAd() && 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.
|
||||
for (VideoAdPlayerCallback callback : adCallbacks) {
|
||||
callback.onEnded();
|
||||
}
|
||||
}
|
||||
|
||||
if (player.isPlayingAd() && playingAdGroupIndex == C.INDEX_UNSET) {
|
||||
player.setPlayWhenReady(false);
|
||||
// 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) {
|
||||
adsLoader.contentComplete();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "adsLoader.contentComplete");
|
||||
}
|
||||
}
|
||||
}
|
||||
playerPeriodIndex = player.getCurrentPeriodIndex();
|
||||
boolean isPlayingAd = player.isPlayingAd();
|
||||
playingAdGroupIndex = isPlayingAd ? player.getCurrentAdGroupIndex() : C.INDEX_UNSET;
|
||||
playingAdIndexInAdGroup = isPlayingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -527,70 +541,42 @@ import java.util.List;
|
||||
* Resumes the player, ensuring the current period is a content period by seeking if necessary.
|
||||
*/
|
||||
private void resumeContentInternal() {
|
||||
if (adTimeline != null) {
|
||||
if (imaPeriodIndex < lastContentPeriodIndex) {
|
||||
if (playingAd) {
|
||||
// Work around an issue where IMA does not always call stopAd before resuming content.
|
||||
// See [Internal: b/38354028].
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Unexpected CONTENT_RESUME_REQUESTED without stopAd");
|
||||
}
|
||||
stopAdInternal();
|
||||
if (contentDurationMs != C.TIME_UNSET) {
|
||||
if (playingAd) {
|
||||
// Work around an issue where IMA does not always call stopAd before resuming content.
|
||||
// See [Internal: b/38354028].
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Unexpected CONTENT_RESUME_REQUESTED without stopAd");
|
||||
}
|
||||
while (adTimeline.isPeriodAd(imaPeriodIndex)) {
|
||||
imaPeriodIndex++;
|
||||
}
|
||||
synchronizePlayerToIma();
|
||||
stopAdInternal();
|
||||
}
|
||||
}
|
||||
player.setPlayWhenReady(true);
|
||||
clearFlags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the player, and ensures that the current period is an ad period by seeking if necessary.
|
||||
*/
|
||||
private void pauseContentInternal() {
|
||||
if (sentPendingContentPositionMs) {
|
||||
pendingContentPositionMs = C.TIME_UNSET;
|
||||
sentPendingContentPositionMs = false;
|
||||
}
|
||||
// IMA is requesting to pause content, so stop faking the content position.
|
||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||
if (adTimeline != null && !isAdDisplayed) {
|
||||
// Seek to the next ad.
|
||||
while (!adTimeline.isPeriodAd(imaPeriodIndex)) {
|
||||
imaPeriodIndex++;
|
||||
}
|
||||
synchronizePlayerToIma();
|
||||
} else {
|
||||
// IMA is sending an initial CONTENT_PAUSE_REQUESTED before a pre-roll ad.
|
||||
Assertions.checkState(playerPeriodIndex == 0 && imaPeriodIndex == 0);
|
||||
}
|
||||
player.setPlayWhenReady(false);
|
||||
clearFlags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the currently playing ad, seeking to the next content period if there is one. May only be
|
||||
* called when {@link #playingAd} is {@code true}.
|
||||
*/
|
||||
private void stopAdInternal() {
|
||||
Assertions.checkState(playingAd);
|
||||
if (imaPeriodIndex != adTimeline.getPeriodCount() - 1) {
|
||||
player.setPlayWhenReady(false);
|
||||
imaPeriodIndex++;
|
||||
if (!adTimeline.isPeriodAd(imaPeriodIndex)) {
|
||||
eventListener.onAdBreakPlayedToEnd(adBreakIndex);
|
||||
adBreakIndex++;
|
||||
adIndexInAdBreak = 0;
|
||||
}
|
||||
synchronizePlayerToIma();
|
||||
} else {
|
||||
eventListener.onAdBreakPlayedToEnd(adTimeline.getAdBreakIndex(imaPeriodIndex));
|
||||
player.setPlayWhenReady(false);
|
||||
if (!player.isPlayingAd()) {
|
||||
eventListener.onAdGroupPlayedToEnd(adGroupIndex);
|
||||
adGroupIndex = C.INDEX_UNSET;
|
||||
}
|
||||
clearFlags();
|
||||
}
|
||||
|
||||
private void synchronizePlayerToIma() {
|
||||
if (playerPeriodIndex != imaPeriodIndex) {
|
||||
player.seekTo(imaPeriodIndex, 0);
|
||||
}
|
||||
|
||||
isAdDisplayed = adTimeline.isPeriodAd(imaPeriodIndex);
|
||||
private void clearFlags() {
|
||||
// If an ad is displayed, these flags will be updated in response to playAd/pauseAd/stopAd until
|
||||
// the content is resumed.
|
||||
playingAd = false;
|
||||
@ -598,14 +584,9 @@ import java.util.List;
|
||||
}
|
||||
|
||||
private void checkForContentComplete() {
|
||||
if (adTimeline == null || isAdDisplayed || sentContentComplete) {
|
||||
return;
|
||||
}
|
||||
long positionMs = C.usToMs(adTimeline.getContentStartTimeUs(imaPeriodIndex))
|
||||
+ player.getCurrentPosition();
|
||||
if (playerPeriodIndex == lastContentPeriodIndex
|
||||
&& positionMs + END_OF_CONTENT_POSITION_THRESHOLD_MS
|
||||
>= C.usToMs(adTimeline.getContentEndTimeUs(playerPeriodIndex))) {
|
||||
if (contentDurationMs != C.TIME_UNSET
|
||||
&& player.getCurrentPosition() + END_OF_CONTENT_POSITION_THRESHOLD_MS >= contentDurationMs
|
||||
&& !sentContentComplete) {
|
||||
adsLoader.contentComplete();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "adsLoader.contentComplete");
|
||||
@ -614,19 +595,20 @@ import java.util.List;
|
||||
}
|
||||
}
|
||||
|
||||
private static long[] getAdBreakTimesUs(List<Float> cuePoints) {
|
||||
private static long[] getAdGroupTimesUs(List<Float> cuePoints) {
|
||||
if (cuePoints.isEmpty()) {
|
||||
// If no cue points are specified, there is a preroll ad break.
|
||||
// If no cue points are specified, there is a preroll ad.
|
||||
return new long[] {0};
|
||||
}
|
||||
|
||||
int count = cuePoints.size();
|
||||
long[] adBreakTimesUs = new long[count];
|
||||
long[] adGroupTimesUs = new long[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
double cuePoint = cuePoints.get(i);
|
||||
adBreakTimesUs[i] = cuePoint == -1.0 ? C.TIME_UNSET : (long) (C.MICROS_PER_SECOND * cuePoint);
|
||||
adGroupTimesUs[i] =
|
||||
cuePoint == -1.0 ? C.TIME_END_OF_SOURCE : (long) (C.MICROS_PER_SECOND * cuePoint);
|
||||
}
|
||||
return adBreakTimesUs;
|
||||
return adGroupTimesUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,20 +20,14 @@ import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.ViewGroup;
|
||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||
import com.google.ads.interactivemedia.v3.api.AdPodInfo;
|
||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.ClippingMediaPeriod;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -56,7 +50,8 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
private final ImaSdkSettings imaSdkSettings;
|
||||
private final Handler mainHandler;
|
||||
private final AdListener adLoaderListener;
|
||||
private final Map<MediaPeriod, MediaSource> mediaSourceByMediaPeriod;
|
||||
private final Map<MediaPeriod, MediaSource> adMediaSourceByMediaPeriod;
|
||||
private final Timeline.Period period;
|
||||
|
||||
private Handler playerHandler;
|
||||
private ExoPlayer player;
|
||||
@ -65,13 +60,12 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
// Accessed on the player thread.
|
||||
private Timeline contentTimeline;
|
||||
private Object contentManifest;
|
||||
private long[] adBreakTimesUs;
|
||||
private boolean[] playedAdBreak;
|
||||
private Ad[][] adBreakAds;
|
||||
private Timeline[][] adBreakTimelines;
|
||||
private MediaSource[][] adBreakMediaSources;
|
||||
private DeferredMediaPeriod[][] adBreakDeferredMediaPeriods;
|
||||
private AdTimeline timeline;
|
||||
private long[] adGroupTimesUs;
|
||||
private boolean[] hasPlayedAdGroup;
|
||||
private int[] adCounts;
|
||||
private MediaSource[][] adGroupMediaSources;
|
||||
private boolean[][] isAdAvailable;
|
||||
private long[][] adDurationsUs;
|
||||
private MediaSource.Listener listener;
|
||||
private IOException adLoadError;
|
||||
|
||||
@ -120,8 +114,11 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
this.imaSdkSettings = imaSdkSettings;
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
adLoaderListener = new AdListener();
|
||||
mediaSourceByMediaPeriod = new HashMap<>();
|
||||
adBreakMediaSources = new MediaSource[0][];
|
||||
adMediaSourceByMediaPeriod = new HashMap<>();
|
||||
period = new Timeline.Period();
|
||||
adGroupMediaSources = new MediaSource[0][];
|
||||
isAdAvailable = new boolean[0][];
|
||||
adDurationsUs = new long[0][];
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -151,7 +148,7 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
throw adLoadError;
|
||||
}
|
||||
contentMediaSource.maybeThrowSourceInfoRefreshError();
|
||||
for (MediaSource[] mediaSources : adBreakMediaSources) {
|
||||
for (MediaSource[] mediaSources : adGroupMediaSources) {
|
||||
for (MediaSource mediaSource : mediaSources) {
|
||||
mediaSource.maybeThrowSourceInfoRefreshError();
|
||||
}
|
||||
@ -160,49 +157,23 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
int index = id.periodIndex;
|
||||
if (timeline.isPeriodAd(index)) {
|
||||
int adBreakIndex = timeline.getAdBreakIndex(index);
|
||||
int adIndexInAdBreak = timeline.getAdIndexInAdBreak(index);
|
||||
if (adIndexInAdBreak >= adBreakMediaSources[adBreakIndex].length) {
|
||||
DeferredMediaPeriod deferredPeriod = new DeferredMediaPeriod(0, allocator);
|
||||
if (adIndexInAdBreak >= adBreakDeferredMediaPeriods[adBreakIndex].length) {
|
||||
adBreakDeferredMediaPeriods[adBreakIndex] = Arrays.copyOf(
|
||||
adBreakDeferredMediaPeriods[adBreakIndex], adIndexInAdBreak + 1);
|
||||
}
|
||||
adBreakDeferredMediaPeriods[adBreakIndex][adIndexInAdBreak] = deferredPeriod;
|
||||
return deferredPeriod;
|
||||
}
|
||||
|
||||
MediaSource adBreakMediaSource = adBreakMediaSources[adBreakIndex][adIndexInAdBreak];
|
||||
MediaPeriod adBreakMediaPeriod =
|
||||
adBreakMediaSource.createPeriod(new MediaPeriodId(0), allocator);
|
||||
mediaSourceByMediaPeriod.put(adBreakMediaPeriod, adBreakMediaSource);
|
||||
return adBreakMediaPeriod;
|
||||
if (id.isAd()) {
|
||||
MediaSource mediaSource = adGroupMediaSources[id.adGroupIndex][id.adIndexInAdGroup];
|
||||
MediaPeriod mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(0), allocator);
|
||||
adMediaSourceByMediaPeriod.put(mediaPeriod, mediaSource);
|
||||
return mediaPeriod;
|
||||
} else {
|
||||
long startUs = timeline.getContentStartTimeUs(index);
|
||||
long endUs = timeline.getContentEndTimeUs(index);
|
||||
MediaPeriod contentMediaPeriod =
|
||||
contentMediaSource.createPeriod(new MediaPeriodId(0), allocator);
|
||||
ClippingMediaPeriod clippingPeriod = new ClippingMediaPeriod(contentMediaPeriod, true);
|
||||
clippingPeriod.setClipping(startUs, endUs == C.TIME_UNSET ? C.TIME_END_OF_SOURCE : endUs);
|
||||
mediaSourceByMediaPeriod.put(contentMediaPeriod, contentMediaSource);
|
||||
return clippingPeriod;
|
||||
return contentMediaSource.createPeriod(id, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
if (mediaPeriod instanceof DeferredMediaPeriod) {
|
||||
mediaPeriod = ((DeferredMediaPeriod) mediaPeriod).mediaPeriod;
|
||||
if (mediaPeriod == null) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
} else if (mediaPeriod instanceof ClippingMediaPeriod) {
|
||||
mediaPeriod = ((ClippingMediaPeriod) mediaPeriod).mediaPeriod;
|
||||
if (adMediaSourceByMediaPeriod.containsKey(mediaPeriod)) {
|
||||
adMediaSourceByMediaPeriod.remove(mediaPeriod).releasePeriod(mediaPeriod);
|
||||
} else {
|
||||
contentMediaSource.releasePeriod(mediaPeriod);
|
||||
}
|
||||
mediaSourceByMediaPeriod.remove(mediaPeriod).releasePeriod(mediaPeriod);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -210,7 +181,7 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
released = true;
|
||||
adLoadError = null;
|
||||
contentMediaSource.releaseSource();
|
||||
for (MediaSource[] mediaSources : adBreakMediaSources) {
|
||||
for (MediaSource[] mediaSources : adGroupMediaSources) {
|
||||
for (MediaSource mediaSource : mediaSources) {
|
||||
mediaSource.releaseSource();
|
||||
}
|
||||
@ -229,19 +200,19 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void onAdBreakTimesUsLoaded(long[] adBreakTimesUs) {
|
||||
Assertions.checkState(this.adBreakTimesUs == null);
|
||||
this.adBreakTimesUs = adBreakTimesUs;
|
||||
int adBreakCount = adBreakTimesUs.length;
|
||||
adBreakAds = new Ad[adBreakCount][];
|
||||
Arrays.fill(adBreakAds, new Ad[0]);
|
||||
adBreakTimelines = new Timeline[adBreakCount][];
|
||||
Arrays.fill(adBreakTimelines, new Timeline[0]);
|
||||
adBreakMediaSources = new MediaSource[adBreakCount][];
|
||||
Arrays.fill(adBreakMediaSources, new MediaSource[0]);
|
||||
adBreakDeferredMediaPeriods = new DeferredMediaPeriod[adBreakCount][];
|
||||
Arrays.fill(adBreakDeferredMediaPeriods, new DeferredMediaPeriod[0]);
|
||||
playedAdBreak = new boolean[adBreakCount];
|
||||
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]);
|
||||
maybeUpdateSourceInfo();
|
||||
}
|
||||
|
||||
@ -251,98 +222,51 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
maybeUpdateSourceInfo();
|
||||
}
|
||||
|
||||
private void onAdUriLoaded(final int adBreakIndex, final int adIndexInAdBreak, Uri uri) {
|
||||
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);
|
||||
if (adBreakMediaSources[adBreakIndex].length <= adIndexInAdBreak) {
|
||||
int adCount = adIndexInAdBreak + 1;
|
||||
adBreakMediaSources[adBreakIndex] = Arrays.copyOf(adBreakMediaSources[adBreakIndex], adCount);
|
||||
adBreakTimelines[adBreakIndex] = Arrays.copyOf(adBreakTimelines[adBreakIndex], adCount);
|
||||
}
|
||||
adBreakMediaSources[adBreakIndex][adIndexInAdBreak] = adMediaSource;
|
||||
if (adIndexInAdBreak < adBreakDeferredMediaPeriods[adBreakIndex].length
|
||||
&& adBreakDeferredMediaPeriods[adBreakIndex][adIndexInAdBreak] != null) {
|
||||
adBreakDeferredMediaPeriods[adBreakIndex][adIndexInAdBreak].setMediaSource(
|
||||
adBreakMediaSources[adBreakIndex][adIndexInAdBreak]);
|
||||
mediaSourceByMediaPeriod.put(
|
||||
adBreakDeferredMediaPeriods[adBreakIndex][adIndexInAdBreak].mediaPeriod, adMediaSource);
|
||||
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(adBreakIndex, adIndexInAdBreak, timeline);
|
||||
onAdSourceInfoRefreshed(adGroupIndex, adIndexInAdGroup, timeline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onAdSourceInfoRefreshed(int adBreakIndex, int adIndexInAdBreak, Timeline timeline) {
|
||||
adBreakTimelines[adBreakIndex][adIndexInAdBreak] = 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 onAdLoaded(int adBreakIndex, int adIndexInAdBreak, Ad ad) {
|
||||
if (adBreakAds[adBreakIndex].length <= adIndexInAdBreak) {
|
||||
int adCount = adIndexInAdBreak + 1;
|
||||
adBreakAds[adBreakIndex] = Arrays.copyOf(adBreakAds[adBreakIndex], adCount);
|
||||
private void onAdGroupLoaded(int adGroupIndex, int adCountInAdGroup) {
|
||||
if (adCounts[adGroupIndex] == C.LENGTH_UNSET) {
|
||||
adCounts[adGroupIndex] = adCountInAdGroup;
|
||||
maybeUpdateSourceInfo();
|
||||
}
|
||||
adBreakAds[adBreakIndex][adIndexInAdBreak] = ad;
|
||||
maybeUpdateSourceInfo();
|
||||
}
|
||||
|
||||
private void maybeUpdateSourceInfo() {
|
||||
if (adBreakTimesUs == null || contentTimeline == null) {
|
||||
// We don't have enough information to start building the timeline yet.
|
||||
return;
|
||||
if (adGroupTimesUs != null && contentTimeline != null) {
|
||||
SinglePeriodAdTimeline timeline = new SinglePeriodAdTimeline(contentTimeline, adGroupTimesUs,
|
||||
hasPlayedAdGroup, adCounts, isAdAvailable, adDurationsUs);
|
||||
listener.onSourceInfoRefreshed(timeline, contentManifest);
|
||||
}
|
||||
|
||||
AdTimeline.Builder builder = new AdTimeline.Builder(contentTimeline);
|
||||
int count = adBreakTimesUs.length;
|
||||
boolean preroll = adBreakTimesUs[0] == 0;
|
||||
boolean postroll = adBreakTimesUs[count - 1] == C.TIME_UNSET;
|
||||
int midrollCount = count - (preroll ? 1 : 0) - (postroll ? 1 : 0);
|
||||
|
||||
int adBreakIndex = 0;
|
||||
long contentTimeUs = 0;
|
||||
if (preroll) {
|
||||
addAdBreak(builder, adBreakIndex++);
|
||||
}
|
||||
for (int i = 0; i < midrollCount; i++) {
|
||||
long startTimeUs = contentTimeUs;
|
||||
contentTimeUs = adBreakTimesUs[adBreakIndex];
|
||||
builder.addContent(startTimeUs, contentTimeUs);
|
||||
addAdBreak(builder, adBreakIndex++);
|
||||
}
|
||||
builder.addContent(contentTimeUs, C.TIME_UNSET);
|
||||
if (postroll) {
|
||||
addAdBreak(builder, adBreakIndex);
|
||||
}
|
||||
|
||||
timeline = builder.build();
|
||||
listener.onSourceInfoRefreshed(timeline, contentManifest);
|
||||
}
|
||||
|
||||
private void addAdBreak(AdTimeline.Builder builder, int adBreakIndex) {
|
||||
int adCount = adBreakMediaSources[adBreakIndex].length;
|
||||
AdPodInfo adPodInfo = null;
|
||||
for (int adIndex = 0; adIndex < adCount; adIndex++) {
|
||||
Timeline adTimeline = adBreakTimelines[adBreakIndex][adIndex];
|
||||
long adDurationUs = adTimeline != null
|
||||
? adTimeline.getPeriod(0, new Timeline.Period()).getDurationUs() : C.TIME_UNSET;
|
||||
Ad ad = adIndex < adBreakAds[adBreakIndex].length
|
||||
? adBreakAds[adBreakIndex][adIndex] : null;
|
||||
builder.addAdPeriod(ad, adBreakIndex, adIndex, adDurationUs);
|
||||
if (ad != null) {
|
||||
adPodInfo = ad.getAdPodInfo();
|
||||
}
|
||||
}
|
||||
if (adPodInfo == null || adPodInfo.getTotalAds() > adCount) {
|
||||
// We don't know how many ads are in the ad break, or they have not loaded yet.
|
||||
builder.addAdPeriod(null, adBreakIndex, adCount, C.TIME_UNSET);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAdBreakPlayedToEnd(int adBreakIndex) {
|
||||
playedAdBreak[adBreakIndex] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -352,7 +276,7 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
ExtractorMediaSource.EventListener {
|
||||
|
||||
@Override
|
||||
public void onAdBreakTimesUsLoaded(final long[] adBreakTimesUs) {
|
||||
public void onAdGroupTimesUsLoaded(final long[] adGroupTimesUs) {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
@ -362,13 +286,13 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
ImaAdsMediaSource.this.onAdBreakTimesUsLoaded(adBreakTimesUs);
|
||||
ImaAdsMediaSource.this.onAdGroupTimesUsLoaded(adGroupTimesUs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUriLoaded(final int adBreakIndex, final int adIndexInAdBreak, final Uri uri) {
|
||||
public void onAdGroupPlayedToEnd(final int adGroupIndex) {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
@ -378,13 +302,13 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
ImaAdsMediaSource.this.onAdUriLoaded(adBreakIndex, adIndexInAdBreak, uri);
|
||||
ImaAdsMediaSource.this.onAdGroupPlayedToEnd(adGroupIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdLoaded(final int adBreakIndex, final int adIndexInAdBreak, final Ad ad) {
|
||||
public void onAdUriLoaded(final int adGroupIndex, final int adIndexInAdGroup, final Uri uri) {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
@ -394,13 +318,13 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
ImaAdsMediaSource.this.onAdLoaded(adBreakIndex, adIndexInAdBreak, ad);
|
||||
ImaAdsMediaSource.this.onAdUriLoaded(adGroupIndex, adIndexInAdGroup, uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdBreakPlayedToEnd(final int adBreakIndex) {
|
||||
public void onAdGroupLoaded(final int adGroupIndex, final int adCountInAdGroup) {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
@ -410,7 +334,7 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
ImaAdsMediaSource.this.onAdBreakPlayedToEnd(adBreakIndex);
|
||||
ImaAdsMediaSource.this.onAdGroupLoaded(adGroupIndex, adCountInAdGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -433,99 +357,4 @@ public final class ImaAdsMediaSource implements MediaSource {
|
||||
|
||||
}
|
||||
|
||||
private static final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
|
||||
|
||||
private final int index;
|
||||
private final Allocator allocator;
|
||||
|
||||
public MediaPeriod mediaPeriod;
|
||||
private MediaPeriod.Callback callback;
|
||||
private long positionUs;
|
||||
|
||||
public DeferredMediaPeriod(int index, Allocator allocator) {
|
||||
this.index = index;
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
public void setMediaSource(MediaSource mediaSource) {
|
||||
mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(index), allocator);
|
||||
if (callback != null) {
|
||||
mediaPeriod.prepare(this, positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback, long positionUs) {
|
||||
this.callback = callback;
|
||||
this.positionUs = positionUs;
|
||||
if (mediaPeriod != null) {
|
||||
mediaPeriod.prepare(this, positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
if (mediaPeriod != null) {
|
||||
mediaPeriod.maybeThrowPrepareError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getTrackGroups() {
|
||||
return mediaPeriod.getTrackGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
|
||||
return mediaPeriod.selectTracks(selections, mayRetainStreamFlags, streams, streamResetFlags,
|
||||
positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discardBuffer(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readDiscontinuity() {
|
||||
return mediaPeriod.readDiscontinuity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
return mediaPeriod.getBufferedPositionUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long seekToUs(long positionUs) {
|
||||
return mediaPeriod.seekToUs(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextLoadPositionUs() {
|
||||
return mediaPeriod.getNextLoadPositionUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return mediaPeriod != null && mediaPeriod.continueLoading(positionUs);
|
||||
}
|
||||
|
||||
// MediaPeriod.Callback implementation.
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
Assertions.checkArgument(this.mediaPeriod == mediaPeriod);
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod mediaPeriod) {
|
||||
Assertions.checkArgument(this.mediaPeriod == mediaPeriod);
|
||||
callback.onContinueLoadingRequested(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
* A {@link Timeline} for sources that have ads.
|
||||
*/
|
||||
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 long[][] adDurationsUs;
|
||||
|
||||
/**
|
||||
* Creates a new timeline with a single period containing the specified ads.
|
||||
*
|
||||
* @param contentTimeline The timeline of the content alongside which ads will be played. It must
|
||||
* have one window and one period.
|
||||
* @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 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) {
|
||||
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.adDurationsUs = adDurationsUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getWindow(int windowIndex, Window window, boolean setIds,
|
||||
long defaultPositionProjectionUs) {
|
||||
return contentTimeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
return period;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexOfPeriod(Object uid) {
|
||||
return contentTimeline.getIndexOfPeriod(uid);
|
||||
}
|
||||
|
||||
}
|
@ -483,7 +483,7 @@ public final class ExoPlayerTest extends TestCase {
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
TimelineWindowDefinition windowDefinition = windowDefinitions[periodIndex];
|
||||
Object id = setIds ? periodIndex : null;
|
||||
return period.set(id, id, periodIndex, windowDefinition.durationUs, 0, false);
|
||||
return period.set(id, id, periodIndex, windowDefinition.durationUs, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,7 +63,7 @@ public class TimelineTest extends TestCase {
|
||||
|
||||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
return period.set(new int[] { id, periodIndex }, null, 0, WINDOW_DURATION_US, 0, false);
|
||||
return period.set(new int[] { id, periodIndex }, null, 0, WINDOW_DURATION_US, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -758,24 +758,24 @@ public final class C {
|
||||
|
||||
/**
|
||||
* Converts a time in microseconds to the corresponding time in milliseconds, preserving
|
||||
* {@link #TIME_UNSET} values.
|
||||
* {@link #TIME_UNSET} and {@link #TIME_END_OF_SOURCE} values.
|
||||
*
|
||||
* @param timeUs The time in microseconds.
|
||||
* @return The corresponding time in milliseconds.
|
||||
*/
|
||||
public static long usToMs(long timeUs) {
|
||||
return timeUs == TIME_UNSET ? TIME_UNSET : (timeUs / 1000);
|
||||
return (timeUs == TIME_UNSET || timeUs == TIME_END_OF_SOURCE) ? timeUs : (timeUs / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time in milliseconds to the corresponding time in microseconds, preserving
|
||||
* {@link #TIME_UNSET} values.
|
||||
* {@link #TIME_UNSET} values and {@link #TIME_END_OF_SOURCE} values.
|
||||
*
|
||||
* @param timeMs The time in milliseconds.
|
||||
* @return The corresponding time in microseconds.
|
||||
*/
|
||||
public static long msToUs(long timeMs) {
|
||||
return timeMs == TIME_UNSET ? TIME_UNSET : (timeMs * 1000);
|
||||
return (timeMs == TIME_UNSET || timeMs == TIME_END_OF_SOURCE) ? timeMs : (timeMs * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,13 +264,6 @@ public abstract class Timeline {
|
||||
*/
|
||||
public long durationUs;
|
||||
|
||||
// TODO: Remove this flag now that in-period ads are supported.
|
||||
|
||||
/**
|
||||
* Whether this period contains an ad.
|
||||
*/
|
||||
public boolean isAd;
|
||||
|
||||
private long positionInWindowUs;
|
||||
private long[] adGroupTimesUs;
|
||||
private boolean[] hasPlayedAdGroup;
|
||||
@ -289,12 +282,11 @@ public abstract class Timeline {
|
||||
* @param positionInWindowUs The position of the start of this period relative to the start of
|
||||
* the window to which it belongs, in milliseconds. May be negative if the start of the
|
||||
* period is not within the window.
|
||||
* @param isAd Whether this period is an ad.
|
||||
* @return This period, for convenience.
|
||||
*/
|
||||
public Period set(Object id, Object uid, int windowIndex, long durationUs,
|
||||
long positionInWindowUs, boolean isAd) {
|
||||
return set(id, uid, windowIndex, durationUs, positionInWindowUs, isAd, null, null, null, null,
|
||||
long positionInWindowUs) {
|
||||
return set(id, uid, windowIndex, durationUs, positionInWindowUs, null, null, null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
@ -309,7 +301,6 @@ public abstract class Timeline {
|
||||
* @param positionInWindowUs The position of the start of this period relative to the start of
|
||||
* the window to which it belongs, in milliseconds. May be negative if the start of the
|
||||
* period is not within the window.
|
||||
* @param isAd Whether this period is an ad.
|
||||
* @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.
|
||||
@ -322,14 +313,13 @@ public abstract class Timeline {
|
||||
* @return This period, for convenience.
|
||||
*/
|
||||
public Period set(Object id, Object uid, int windowIndex, long durationUs,
|
||||
long positionInWindowUs, boolean isAd, long[] adGroupTimesUs, boolean[] hasPlayedAdGroup,
|
||||
int[] adCounts, boolean[][] isAdAvailable, long[][] adDurationsUs) {
|
||||
long positionInWindowUs, long[] adGroupTimesUs, boolean[] hasPlayedAdGroup, int[] adCounts,
|
||||
boolean[][] isAdAvailable, long[][] adDurationsUs) {
|
||||
this.id = id;
|
||||
this.uid = uid;
|
||||
this.windowIndex = windowIndex;
|
||||
this.durationUs = durationUs;
|
||||
this.positionInWindowUs = positionInWindowUs;
|
||||
this.isAd = isAd;
|
||||
this.adGroupTimesUs = adGroupTimesUs;
|
||||
this.hasPlayedAdGroup = hasPlayedAdGroup;
|
||||
this.adCounts = adCounts;
|
||||
|
@ -99,7 +99,7 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
Assertions.checkIndex(periodIndex, 0, 1);
|
||||
Object id = setIds ? ID : null;
|
||||
return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs, false);
|
||||
return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -653,7 +653,7 @@ public final class DashMediaSource implements MediaSource {
|
||||
+ Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null;
|
||||
return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex),
|
||||
C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs)
|
||||
- offsetInFirstPeriodUs, false);
|
||||
- offsetInFirstPeriodUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,13 +78,13 @@ public interface TimeBar {
|
||||
void setDuration(long duration);
|
||||
|
||||
/**
|
||||
* Sets the times of ad breaks.
|
||||
* Sets the times of ad groups.
|
||||
*
|
||||
* @param adBreakTimesMs An array where the first {@code adBreakCount} elements are the times of
|
||||
* ad breaks in milliseconds. May be {@code null} if there are no ad breaks.
|
||||
* @param adBreakCount The number of ad breaks.
|
||||
* @param adGroupTimesMs An array where the first {@code adGroupCount} elements are the times of
|
||||
* ad groups in milliseconds. May be {@code null} if there are no ad groups.
|
||||
* @param adGroupCount The number of ad groups.
|
||||
*/
|
||||
void setAdGroupTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount);
|
||||
void setAdGroupTimesMs(@Nullable long[] adGroupTimesMs, int adGroupCount);
|
||||
|
||||
/**
|
||||
* Listener for scrubbing events.
|
||||
|
Loading…
x
Reference in New Issue
Block a user