mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Map live interstitials to ad playback state
After this change, updates of the live HLS playlists are reflected in the ad playback state. Interstitials are inserted into new or existing ad groups according to the current ad playback state. PiperOrigin-RevId: 735733207
This commit is contained in:
parent
ecac78f630
commit
593c6fa1e8
@ -15,11 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.hls;
|
package androidx.media3.exoplayer.hls;
|
||||||
|
|
||||||
|
import static androidx.media3.common.AdPlaybackState.AD_STATE_UNAVAILABLE;
|
||||||
import static androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION;
|
import static androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION;
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Interstitial.CUE_TRIGGER_POST;
|
||||||
|
import static androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Interstitial.CUE_TRIGGER_PRE;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -48,6 +51,7 @@ import androidx.media3.exoplayer.source.MediaSource;
|
|||||||
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
||||||
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
|
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -283,6 +287,7 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
private final PlayerListener playerListener;
|
private final PlayerListener playerListener;
|
||||||
private final Map<Object, EventListener> activeEventListeners;
|
private final Map<Object, EventListener> activeEventListeners;
|
||||||
private final Map<Object, AdPlaybackState> activeAdPlaybackStates;
|
private final Map<Object, AdPlaybackState> activeAdPlaybackStates;
|
||||||
|
private final Map<Object, Set<String>> insertedInterstitialIds;
|
||||||
private final List<Listener> listeners;
|
private final List<Listener> listeners;
|
||||||
private final Set<Object> unsupportedAdsIds;
|
private final Set<Object> unsupportedAdsIds;
|
||||||
|
|
||||||
@ -294,6 +299,7 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
playerListener = new PlayerListener();
|
playerListener = new PlayerListener();
|
||||||
activeEventListeners = new HashMap<>();
|
activeEventListeners = new HashMap<>();
|
||||||
activeAdPlaybackStates = new HashMap<>();
|
activeAdPlaybackStates = new HashMap<>();
|
||||||
|
insertedInterstitialIds = new HashMap<>();
|
||||||
listeners = new ArrayList<>();
|
listeners = new ArrayList<>();
|
||||||
unsupportedAdsIds = new HashSet<>();
|
unsupportedAdsIds = new HashSet<>();
|
||||||
}
|
}
|
||||||
@ -366,18 +372,17 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
}
|
}
|
||||||
activeEventListeners.put(adsId, eventListener);
|
activeEventListeners.put(adsId, eventListener);
|
||||||
MediaItem mediaItem = adsMediaSource.getMediaItem();
|
MediaItem mediaItem = adsMediaSource.getMediaItem();
|
||||||
if (player != null && isSupportedMediaItem(mediaItem, player.getCurrentTimeline())) {
|
if (isHlsMediaItem(mediaItem)) {
|
||||||
// Mark with NONE. Update and notify later when timeline with interstitials arrives.
|
// Mark with NONE. Update and notify later when timeline with interstitials arrives.
|
||||||
activeAdPlaybackStates.put(adsId, AdPlaybackState.NONE);
|
activeAdPlaybackStates.put(adsId, AdPlaybackState.NONE);
|
||||||
|
insertedInterstitialIds.put(adsId, new HashSet<>());
|
||||||
notifyListeners(listener -> listener.onStart(mediaItem, adsId, adViewProvider));
|
notifyListeners(listener -> listener.onStart(mediaItem, adsId, adViewProvider));
|
||||||
} else {
|
} else {
|
||||||
putAndNotifyAdPlaybackStateUpdate(adsId, new AdPlaybackState(adsId));
|
putAndNotifyAdPlaybackStateUpdate(adsId, new AdPlaybackState(adsId));
|
||||||
if (player != null) {
|
|
||||||
Log.w(TAG, "Unsupported media item. Playing without ads for adsId=" + adsId);
|
Log.w(TAG, "Unsupported media item. Playing without ads for adsId=" + adsId);
|
||||||
unsupportedAdsIds.add(adsId);
|
unsupportedAdsIds.add(adsId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleContentTimelineChanged(AdsMediaSource adsMediaSource, Timeline timeline) {
|
public void handleContentTimelineChanged(AdsMediaSource adsMediaSource, Timeline timeline) {
|
||||||
@ -387,6 +392,7 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
if (eventListener != null) {
|
if (eventListener != null) {
|
||||||
unsupportedAdsIds.remove(adsId);
|
unsupportedAdsIds.remove(adsId);
|
||||||
AdPlaybackState adPlaybackState = checkNotNull(activeAdPlaybackStates.remove(adsId));
|
AdPlaybackState adPlaybackState = checkNotNull(activeAdPlaybackStates.remove(adsId));
|
||||||
|
insertedInterstitialIds.remove(adsId);
|
||||||
if (adPlaybackState.equals(AdPlaybackState.NONE)) {
|
if (adPlaybackState.equals(AdPlaybackState.NONE)) {
|
||||||
// Play without ads after release to not interrupt playback.
|
// Play without ads after release to not interrupt playback.
|
||||||
eventListener.onAdPlaybackState(new AdPlaybackState(adsId));
|
eventListener.onAdPlaybackState(new AdPlaybackState(adsId));
|
||||||
@ -394,17 +400,35 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AdPlaybackState adPlaybackState = checkNotNull(activeAdPlaybackStates.get(adsId));
|
AdPlaybackState adPlaybackState = checkNotNull(activeAdPlaybackStates.get(adsId));
|
||||||
if (!adPlaybackState.equals(AdPlaybackState.NONE)) {
|
if (!adPlaybackState.equals(AdPlaybackState.NONE)
|
||||||
// VOD only. Updating the playback state is not supported yet.
|
&& !adPlaybackState.endsWithLivePostrollPlaceHolder()) {
|
||||||
|
// Multiple timeline updates for VOD not supported.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (adPlaybackState.equals(AdPlaybackState.NONE)) {
|
||||||
|
// Setup initial ad playback state for VOD or live.
|
||||||
adPlaybackState = new AdPlaybackState(adsId);
|
adPlaybackState = new AdPlaybackState(adsId);
|
||||||
|
if (isLiveMediaItem(adsMediaSource.getMediaItem(), timeline)) {
|
||||||
|
adPlaybackState =
|
||||||
|
adPlaybackState.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Window window = timeline.getWindow(0, new Window());
|
Window window = timeline.getWindow(0, new Window());
|
||||||
if (window.manifest instanceof HlsManifest) {
|
if (window.manifest instanceof HlsManifest) {
|
||||||
|
HlsMediaPlaylist mediaPlaylist = ((HlsManifest) window.manifest).mediaPlaylist;
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
mapHlsInterstitialsToAdPlaybackState(
|
window.isLive()
|
||||||
((HlsManifest) window.manifest).mediaPlaylist, adPlaybackState);
|
? mapInterstitialsForLive(
|
||||||
|
mediaPlaylist,
|
||||||
|
adPlaybackState,
|
||||||
|
window.positionInFirstPeriodUs,
|
||||||
|
checkNotNull(insertedInterstitialIds.get(adsId)))
|
||||||
|
: mapInterstitialsForVod(
|
||||||
|
mediaPlaylist, adPlaybackState, checkNotNull(insertedInterstitialIds.get(adsId)));
|
||||||
}
|
}
|
||||||
putAndNotifyAdPlaybackStateUpdate(adsId, adPlaybackState);
|
putAndNotifyAdPlaybackStateUpdate(adsId, adPlaybackState);
|
||||||
if (!unsupportedAdsIds.contains(adsId)) {
|
if (!unsupportedAdsIds.contains(adsId)) {
|
||||||
@ -464,6 +488,7 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
adsMediaSource.getAdsId(),
|
adsMediaSource.getAdsId(),
|
||||||
checkNotNull(adPlaybackState)));
|
checkNotNull(adPlaybackState)));
|
||||||
}
|
}
|
||||||
|
insertedInterstitialIds.remove(adsId);
|
||||||
unsupportedAdsIds.remove(adsId);
|
unsupportedAdsIds.remove(adsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,6 +513,7 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
eventListener.onAdPlaybackState(adPlaybackState);
|
eventListener.onAdPlaybackState(adPlaybackState);
|
||||||
} else {
|
} else {
|
||||||
activeAdPlaybackStates.remove(adsId);
|
activeAdPlaybackStates.remove(adsId);
|
||||||
|
insertedInterstitialIds.remove(adsId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -498,10 +524,6 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSupportedMediaItem(MediaItem mediaItem, Timeline timeline) {
|
|
||||||
return isHlsMediaItem(mediaItem) && !isLiveMediaItem(mediaItem, timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isLiveMediaItem(MediaItem mediaItem, Timeline timeline) {
|
private static boolean isLiveMediaItem(MediaItem mediaItem, Timeline timeline) {
|
||||||
int windowIndex = timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false);
|
int windowIndex = timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false);
|
||||||
Window window = new Window();
|
Window window = new Window();
|
||||||
@ -523,68 +545,161 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
|
|||||||
|| Util.inferContentType(localConfiguration.uri) == C.CONTENT_TYPE_HLS;
|
|| Util.inferContentType(localConfiguration.uri) == C.CONTENT_TYPE_HLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AdPlaybackState mapHlsInterstitialsToAdPlaybackState(
|
private static AdPlaybackState mapInterstitialsForLive(
|
||||||
HlsMediaPlaylist hlsMediaPlaylist, AdPlaybackState adPlaybackState) {
|
HlsMediaPlaylist mediaPlaylist,
|
||||||
for (int i = 0; i < hlsMediaPlaylist.interstitials.size(); i++) {
|
AdPlaybackState adPlaybackState,
|
||||||
Interstitial interstitial = hlsMediaPlaylist.interstitials.get(i);
|
long windowPositionInPeriodUs,
|
||||||
|
Set<String> insertedInterstitialIds) {
|
||||||
|
ArrayList<Interstitial> interstitials = new ArrayList<>(mediaPlaylist.interstitials);
|
||||||
|
for (int i = 0; i < interstitials.size(); i++) {
|
||||||
|
Interstitial interstitial = interstitials.get(i);
|
||||||
|
long positionInPlaylistWindowUs =
|
||||||
|
interstitial.cue.contains(CUE_TRIGGER_PRE)
|
||||||
|
? 0L
|
||||||
|
: (interstitial.startDateUnixUs - mediaPlaylist.startTimeUs);
|
||||||
|
if (interstitial.assetUri == null
|
||||||
|
|| insertedInterstitialIds.contains(interstitial.id)
|
||||||
|
|| interstitial.cue.contains(CUE_TRIGGER_POST)
|
||||||
|
|| positionInPlaylistWindowUs < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long timeUs = windowPositionInPeriodUs + positionInPlaylistWindowUs;
|
||||||
|
int insertionIndex = adPlaybackState.adGroupCount - 1;
|
||||||
|
boolean isNewAdGroup = true;
|
||||||
|
for (int adGroupIndex = adPlaybackState.adGroupCount - 2; // skip live placeholder
|
||||||
|
adGroupIndex >= adPlaybackState.removedAdGroupCount;
|
||||||
|
adGroupIndex--) {
|
||||||
|
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
|
||||||
|
if (adGroup.timeUs == timeUs) {
|
||||||
|
// Insert interstitials into or update in existing group.
|
||||||
|
insertionIndex = adGroupIndex;
|
||||||
|
isNewAdGroup = false;
|
||||||
|
break;
|
||||||
|
} else if (adGroup.timeUs < timeUs) {
|
||||||
|
// Insert at index after group before interstitial.
|
||||||
|
insertionIndex = adGroupIndex + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Interstitial is before the ad group. Possible insertion index.
|
||||||
|
insertionIndex = adGroupIndex;
|
||||||
|
}
|
||||||
|
if (isNewAdGroup) {
|
||||||
|
if (insertionIndex < getLowestValidAdGroupInsertionIndex(adPlaybackState)) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Skipping insertion of interstitial attempted to be inserted before an already"
|
||||||
|
+ " initialized ad group.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
adPlaybackState = adPlaybackState.withNewAdGroup(insertionIndex, timeUs);
|
||||||
|
}
|
||||||
|
adPlaybackState =
|
||||||
|
insertOrUpdateInterstitialInAdGroup(
|
||||||
|
interstitial, /* adGroupIndex= */ insertionIndex, adPlaybackState);
|
||||||
|
insertedInterstitialIds.add(interstitial.id);
|
||||||
|
}
|
||||||
|
return adPlaybackState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AdPlaybackState mapInterstitialsForVod(
|
||||||
|
HlsMediaPlaylist mediaPlaylist,
|
||||||
|
AdPlaybackState adPlaybackState,
|
||||||
|
Set<String> insertedInterstitialIds) {
|
||||||
|
checkArgument(adPlaybackState.adGroupCount == 0);
|
||||||
|
ImmutableList<Interstitial> interstitials = mediaPlaylist.interstitials;
|
||||||
|
for (int i = 0; i < interstitials.size(); i++) {
|
||||||
|
Interstitial interstitial = interstitials.get(i);
|
||||||
if (interstitial.assetUri == null) {
|
if (interstitial.assetUri == null) {
|
||||||
Log.w(TAG, "Ignoring interstitials with X-ASSET-LIST. Not yet supported.");
|
Log.w(TAG, "Ignoring interstitials with X-ASSET-LIST. Not yet supported.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
long positionUs;
|
long timeUs;
|
||||||
if (interstitial.cue.contains(Interstitial.CUE_TRIGGER_PRE)) {
|
if (interstitial.cue.contains(CUE_TRIGGER_PRE)) {
|
||||||
positionUs = 0;
|
timeUs = 0L;
|
||||||
} else if (interstitial.cue.contains(Interstitial.CUE_TRIGGER_POST)) {
|
} else if (interstitial.cue.contains(CUE_TRIGGER_POST)) {
|
||||||
positionUs = C.TIME_END_OF_SOURCE;
|
timeUs = C.TIME_END_OF_SOURCE;
|
||||||
} else {
|
} else {
|
||||||
positionUs = interstitial.startDateUnixUs - hlsMediaPlaylist.startTimeUs;
|
timeUs = interstitial.startDateUnixUs - mediaPlaylist.startTimeUs;
|
||||||
}
|
}
|
||||||
// Check whether and at which index to insert an ad group for the interstitial start time.
|
|
||||||
int adGroupIndex =
|
int adGroupIndex =
|
||||||
adPlaybackState.getAdGroupIndexForPositionUs(
|
adPlaybackState.getAdGroupIndexForPositionUs(timeUs, mediaPlaylist.durationUs);
|
||||||
positionUs, /* periodDurationUs= */ hlsMediaPlaylist.durationUs);
|
|
||||||
if (adGroupIndex == C.INDEX_UNSET) {
|
if (adGroupIndex == C.INDEX_UNSET) {
|
||||||
// There is no ad group before or at the interstitials position.
|
// There is no ad group before or at the interstitials position.
|
||||||
adGroupIndex = 0;
|
adGroupIndex = 0;
|
||||||
adPlaybackState = adPlaybackState.withNewAdGroup(0, positionUs);
|
adPlaybackState = adPlaybackState.withNewAdGroup(/* adGroupIndex= */ 0, timeUs);
|
||||||
} else if (adPlaybackState.getAdGroup(adGroupIndex).timeUs != positionUs) {
|
} else if (adPlaybackState.getAdGroup(adGroupIndex).timeUs != timeUs) {
|
||||||
// There is an ad group before the interstitials. Insert after that index.
|
// There is an ad group before the interstitials. Insert after that index.
|
||||||
adGroupIndex++;
|
adGroupIndex++;
|
||||||
adPlaybackState = adPlaybackState.withNewAdGroup(adGroupIndex, positionUs);
|
adPlaybackState = adPlaybackState.withNewAdGroup(adGroupIndex, timeUs);
|
||||||
|
}
|
||||||
|
adPlaybackState =
|
||||||
|
insertOrUpdateInterstitialInAdGroup(interstitial, adGroupIndex, adPlaybackState);
|
||||||
|
insertedInterstitialIds.add(interstitial.id);
|
||||||
|
}
|
||||||
|
return adPlaybackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
int adIndexInAdGroup = max(adPlaybackState.getAdGroup(adGroupIndex).count, 0);
|
private static AdPlaybackState insertOrUpdateInterstitialInAdGroup(
|
||||||
|
Interstitial interstitial, int adGroupIndex, AdPlaybackState adPlaybackState) {
|
||||||
|
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
|
||||||
|
int adIndexInAdGroup = adGroup.getIndexOfAdId(interstitial.id);
|
||||||
|
if (adIndexInAdGroup != C.INDEX_UNSET) {
|
||||||
|
// Interstitial already inserted. Updating not yet supported.
|
||||||
|
return adPlaybackState;
|
||||||
|
}
|
||||||
|
|
||||||
// Insert duration of new interstitial into existing ad durations.
|
// Append to the end of the group.
|
||||||
|
adIndexInAdGroup = max(adGroup.count, 0);
|
||||||
|
// Append duration of new interstitial into existing ad durations.
|
||||||
long interstitialDurationUs =
|
long interstitialDurationUs =
|
||||||
getInterstitialDurationUs(interstitial, /* defaultDurationUs= */ C.TIME_UNSET);
|
getInterstitialDurationUs(interstitial, /* defaultDurationUs= */ C.TIME_UNSET);
|
||||||
long[] adDurations;
|
long[] adDurations;
|
||||||
if (adIndexInAdGroup == 0) {
|
if (adIndexInAdGroup == 0) {
|
||||||
adDurations = new long[1];
|
adDurations = new long[1];
|
||||||
} else {
|
} else {
|
||||||
long[] previousDurations = adPlaybackState.getAdGroup(adGroupIndex).durationsUs;
|
long[] previousDurations = adGroup.durationsUs;
|
||||||
adDurations = new long[previousDurations.length + 1];
|
adDurations = new long[previousDurations.length + 1];
|
||||||
System.arraycopy(previousDurations, 0, adDurations, 0, previousDurations.length);
|
System.arraycopy(previousDurations, 0, adDurations, 0, previousDurations.length);
|
||||||
}
|
}
|
||||||
adDurations[adDurations.length - 1] = interstitialDurationUs;
|
adDurations[adDurations.length - 1] = interstitialDurationUs;
|
||||||
|
|
||||||
long resumeOffsetIncrementUs =
|
long resumeOffsetIncrementUs =
|
||||||
interstitial.resumeOffsetUs != C.TIME_UNSET
|
interstitial.resumeOffsetUs != C.TIME_UNSET
|
||||||
? interstitial.resumeOffsetUs
|
? interstitial.resumeOffsetUs
|
||||||
: (interstitialDurationUs != C.TIME_UNSET ? interstitialDurationUs : 0L);
|
: (interstitialDurationUs != C.TIME_UNSET ? interstitialDurationUs : 0L);
|
||||||
long resumeOffsetUs =
|
long resumeOffsetUs = adGroup.contentResumeOffsetUs + resumeOffsetIncrementUs;
|
||||||
adPlaybackState.getAdGroup(adGroupIndex).contentResumeOffsetUs + resumeOffsetIncrementUs;
|
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
adPlaybackState
|
adPlaybackState
|
||||||
.withAdCount(adGroupIndex, /* adCount= */ adIndexInAdGroup + 1)
|
.withAdCount(adGroupIndex, adIndexInAdGroup + 1)
|
||||||
|
.withAdId(adGroupIndex, adIndexInAdGroup, interstitial.id)
|
||||||
.withAdDurationsUs(adGroupIndex, adDurations)
|
.withAdDurationsUs(adGroupIndex, adDurations)
|
||||||
.withContentResumeOffsetUs(adGroupIndex, resumeOffsetUs)
|
.withContentResumeOffsetUs(adGroupIndex, resumeOffsetUs);
|
||||||
.withAvailableAdMediaItem(
|
if (interstitial.assetUri != null) {
|
||||||
adGroupIndex, adIndexInAdGroup, MediaItem.fromUri(interstitial.assetUri));
|
adPlaybackState =
|
||||||
|
adPlaybackState.withAvailableAdMediaItem(
|
||||||
|
adGroupIndex,
|
||||||
|
adIndexInAdGroup,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri(interstitial.assetUri)
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
return adPlaybackState;
|
return adPlaybackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getLowestValidAdGroupInsertionIndex(AdPlaybackState adPlaybackState) {
|
||||||
|
for (int adGroupIndex = adPlaybackState.adGroupCount - 1;
|
||||||
|
adGroupIndex >= adPlaybackState.removedAdGroupCount;
|
||||||
|
adGroupIndex--) {
|
||||||
|
for (@AdPlaybackState.AdState int state : adPlaybackState.getAdGroup(adGroupIndex).states) {
|
||||||
|
if (state != AD_STATE_UNAVAILABLE) {
|
||||||
|
return adGroupIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All ad groups unavailable.
|
||||||
|
return adPlaybackState.removedAdGroupCount;
|
||||||
|
}
|
||||||
|
|
||||||
private static long getInterstitialDurationUs(Interstitial interstitial, long defaultDurationUs) {
|
private static long getInterstitialDurationUs(Interstitial interstitial, long defaultDurationUs) {
|
||||||
if (interstitial.playoutLimitUs != C.TIME_UNSET) {
|
if (interstitial.playoutLimitUs != C.TIME_UNSET) {
|
||||||
return interstitial.playoutLimitUs;
|
return interstitial.playoutLimitUs;
|
||||||
|
@ -21,6 +21,7 @@ import static org.junit.Assert.assertThrows;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.atMost;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
@ -36,6 +37,7 @@ import androidx.media3.common.AdViewProvider;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSpec;
|
import androidx.media3.datasource.DataSpec;
|
||||||
@ -45,12 +47,14 @@ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
|||||||
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
||||||
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
|
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
|
||||||
import androidx.media3.test.utils.FakeTimeline;
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
|
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -76,8 +80,8 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
private MediaItem contentMediaItem;
|
private MediaItem contentMediaItem;
|
||||||
private DataSpec adTagDataSpec;
|
private DataSpec adTagDataSpec;
|
||||||
private AdsMediaSource adsMediaSource;
|
private AdsMediaSource adsMediaSource;
|
||||||
private FakeTimeline.TimelineWindowDefinition contentWindowDefinition;
|
private TimelineWindowDefinition contentWindowDefinition;
|
||||||
private FakeTimeline.TimelineWindowDefinition adsMediaSourceWindowDefinition;
|
private TimelineWindowDefinition adsMediaSourceWindowDefinition;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@ -101,13 +105,13 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.createMediaSource(contentMediaItem);
|
.createMediaSource(contentMediaItem);
|
||||||
// The content timeline with empty ad playback state.
|
// The content timeline with empty ad playback state.
|
||||||
contentWindowDefinition =
|
contentWindowDefinition =
|
||||||
new FakeTimeline.TimelineWindowDefinition.Builder()
|
new TimelineWindowDefinition.Builder()
|
||||||
.setDurationUs(90_000_000L)
|
.setDurationUs(90_000_000L)
|
||||||
.setMediaItem(contentMediaItem)
|
.setMediaItem(contentMediaItem)
|
||||||
.build();
|
.build();
|
||||||
// The ads timeline with a minimal ad playback state with the ads ID.
|
// The ads timeline with a minimal ad playback state with the ads ID.
|
||||||
adsMediaSourceWindowDefinition =
|
adsMediaSourceWindowDefinition =
|
||||||
new FakeTimeline.TimelineWindowDefinition.Builder()
|
new TimelineWindowDefinition.Builder()
|
||||||
.setDurationUs(90_000_000L)
|
.setDurationUs(90_000_000L)
|
||||||
.setMediaItem(contentMediaItem)
|
.setMediaItem(contentMediaItem)
|
||||||
.setAdPlaybackStates(ImmutableList.of(new AdPlaybackState("adsId")))
|
.setAdPlaybackStates(ImmutableList.of(new AdPlaybackState("adsId")))
|
||||||
@ -152,7 +156,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
when(mockPlayer.getCurrentTimeline())
|
when(mockPlayer.getCurrentTimeline())
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
new FakeTimeline(
|
new FakeTimeline(
|
||||||
new FakeTimeline.TimelineWindowDefinition.Builder()
|
new TimelineWindowDefinition.Builder()
|
||||||
.setDynamic(true)
|
.setDynamic(true)
|
||||||
.setDurationUs(C.TIME_UNSET)
|
.setDurationUs(C.TIME_UNSET)
|
||||||
.setMediaItem(mp4MediaItem)
|
.setMediaItem(mp4MediaItem)
|
||||||
@ -164,24 +168,6 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
verify(mockEventListener).onAdPlaybackState(new AdPlaybackState("adsId"));
|
verify(mockEventListener).onAdPlaybackState(new AdPlaybackState("adsId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void start_liveWindow_emptyAdPlaybackState() {
|
|
||||||
when(mockPlayer.getCurrentTimeline())
|
|
||||||
.thenReturn(
|
|
||||||
new FakeTimeline(
|
|
||||||
new FakeTimeline.TimelineWindowDefinition.Builder()
|
|
||||||
.setDynamic(true)
|
|
||||||
.setLive(true)
|
|
||||||
.setDurationUs(C.TIME_UNSET)
|
|
||||||
.setMediaItem(contentMediaItem)
|
|
||||||
.build()));
|
|
||||||
adsLoader.setPlayer(mockPlayer);
|
|
||||||
|
|
||||||
adsLoader.start(adsMediaSource, adTagDataSpec, "adsId", mockAdViewProvider, mockEventListener);
|
|
||||||
|
|
||||||
verify(mockEventListener).onAdPlaybackState(new AdPlaybackState("adsId"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_twiceWithIdenticalAdsId_throwIllegalStateException() {
|
public void start_twiceWithIdenticalAdsId_throwIllegalStateException() {
|
||||||
when(mockPlayer.getCurrentTimeline()).thenReturn(new FakeTimeline(contentWindowDefinition));
|
when(mockPlayer.getCurrentTimeline()).thenReturn(new FakeTimeline(contentWindowDefinition));
|
||||||
@ -220,7 +206,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
when(mockPlayer.getCurrentTimeline())
|
when(mockPlayer.getCurrentTimeline())
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
new FakeTimeline(
|
new FakeTimeline(
|
||||||
new FakeTimeline.TimelineWindowDefinition.Builder()
|
new TimelineWindowDefinition.Builder()
|
||||||
.setDynamic(true)
|
.setDynamic(true)
|
||||||
.setDurationUs(C.TIME_UNSET)
|
.setDurationUs(C.TIME_UNSET)
|
||||||
.setMediaItem(mp4MediaItem)
|
.setMediaItem(mp4MediaItem)
|
||||||
@ -249,27 +235,28 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
+ "#EXT-X-ENDLIST"
|
+ "#EXT-X-ENDLIST"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad0\","
|
+ "ID=\"ad0-0\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:44.000Z\","
|
+ "START-DATE=\"2020-01-02T21:55:44.000Z\","
|
||||||
+ "CUE=\"PRE\","
|
+ "CUE=\"PRE\","
|
||||||
+ "X-ASSET-URI=\"http://example.com/media-0.m3u8\""
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad1\","
|
+ "ID=\"ad1-0\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:55.000Z\","
|
+ "START-DATE=\"2020-01-02T21:55:55.000Z\","
|
||||||
+ "X-ASSET-URI=\"http://example.com/media-1.m3u8\""
|
+ "X-ASSET-URI=\"http://example.com/media-1-0.m3u8\""
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad2\","
|
+ "ID=\"ad2-0\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:44.000Z\","
|
+ "START-DATE=\"2020-01-02T21:55:44.000Z\","
|
||||||
+ "CUE=\"POST\","
|
+ "CUE=\"POST\","
|
||||||
+ "X-ASSET-URI=\"http://example.com/media-2.m3u8\"\n";
|
+ "X-ASSET-URI=\"http://example.com/media-2-0.m3u8\"\n";
|
||||||
|
|
||||||
assertThat(callHandleContentTimelineChangedAndCaptureAdPlaybackState(playlistString, adsLoader))
|
AdPlaybackState actual =
|
||||||
.isEqualTo(
|
callHandleContentTimelineChangedAndCaptureAdPlaybackState(playlistString, adsLoader);
|
||||||
|
AdPlaybackState expected =
|
||||||
new AdPlaybackState("adsId", 0L, 15_000_000L, C.TIME_END_OF_SOURCE)
|
new AdPlaybackState("adsId", 0L, 15_000_000L, C.TIME_END_OF_SOURCE)
|
||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET)
|
||||||
.withAdDurationsUs(/* adGroupIndex= */ 1, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 1, C.TIME_UNSET)
|
||||||
@ -280,18 +267,31 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, 0L)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, 0L)
|
||||||
|
.withAdId(0, 0, "ad0-0")
|
||||||
|
.withAdId(1, 0, "ad1-0")
|
||||||
|
.withAdId(2, 0, "ad2-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 1,
|
/* adGroupIndex= */ 1,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-1.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-1-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 2,
|
/* adGroupIndex= */ 2,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-2.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-2-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build());
|
||||||
|
assertThat(actual).isEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -331,18 +331,30 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 3)
|
.withAdCount(/* adGroupIndex= */ 0, 3)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, "ad0-1")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, "ad0-2")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 1,
|
/* adIndexInAdGroup= */ 1,
|
||||||
MediaItem.fromUri("http://example.com/media-0-1.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 2,
|
/* adIndexInAdGroup= */ 2,
|
||||||
MediaItem.fromUri("http://example.com/media-0-2.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-2.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -381,18 +393,30 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 3)
|
.withAdCount(/* adGroupIndex= */ 0, 3)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, "ad0-1")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, "ad0-2")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 1,
|
/* adIndexInAdGroup= */ 1,
|
||||||
MediaItem.fromUri("http://example.com/media-0-1.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 2,
|
/* adIndexInAdGroup= */ 2,
|
||||||
MediaItem.fromUri("http://example.com/media-0-2.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-2.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -437,18 +461,30 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000_000L, 1_100_000L, 1_200_000L)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000_000L, 1_100_000L, 1_200_000L)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 3)
|
.withAdCount(/* adGroupIndex= */ 0, 3)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 3_300_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 3_300_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, "ad0-1")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, "ad0-2")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 1,
|
/* adIndexInAdGroup= */ 1,
|
||||||
MediaItem.fromUri("http://example.com/media-0-1.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 2,
|
/* adIndexInAdGroup= */ 2,
|
||||||
MediaItem.fromUri("http://example.com/media-0-2.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-2.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -463,7 +499,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
+ "#EXT-X-ENDLIST"
|
+ "#EXT-X-ENDLIST"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad0-2\","
|
+ "ID=\"ad2-0\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:40.500Z\","
|
+ "START-DATE=\"2020-01-02T21:55:40.500Z\","
|
||||||
+ "CUE=\"POST\","
|
+ "CUE=\"POST\","
|
||||||
@ -471,7 +507,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
+ "X-ASSET-URI=\"http://example.com/media-2-0.m3u8\""
|
+ "X-ASSET-URI=\"http://example.com/media-2-0.m3u8\""
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad0-1\","
|
+ "ID=\"ad1-0\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:42.000Z\","
|
+ "START-DATE=\"2020-01-02T21:55:42.000Z\","
|
||||||
+ "DURATION=2.0,"
|
+ "DURATION=2.0,"
|
||||||
@ -498,18 +534,30 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000_000L)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, 2_000_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, 2_000_000L)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, 3_000_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, 3_000_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, "ad1-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, "ad2-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 1,
|
/* adGroupIndex= */ 1,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-1-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-1-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 2,
|
/* adGroupIndex= */ 2,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-2-0.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-2-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -533,7 +581,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad0-0\","
|
+ "ID=\"ad0-1\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:41.123Z\","
|
+ "START-DATE=\"2020-01-02T21:55:41.123Z\","
|
||||||
+ "DURATION=1.0,"
|
+ "DURATION=1.0,"
|
||||||
@ -547,14 +595,22 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000_000L, 1_000_000L)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000_000L, 1_000_000L)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 2)
|
.withAdCount(/* adGroupIndex= */ 0, 2)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, "ad0-1")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 1,
|
/* adIndexInAdGroup= */ 1,
|
||||||
MediaItem.fromUri("http://example.com/media-0-1.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -578,7 +634,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "#EXT-X-DATERANGE:"
|
+ "#EXT-X-DATERANGE:"
|
||||||
+ "ID=\"ad0-0\","
|
+ "ID=\"ad0-1\","
|
||||||
+ "CLASS=\"com.apple.hls.interstitial\","
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
+ "START-DATE=\"2020-01-02T21:55:41.123Z\","
|
+ "START-DATE=\"2020-01-02T21:55:41.123Z\","
|
||||||
+ "CUE=\"PRE\","
|
+ "CUE=\"PRE\","
|
||||||
@ -591,14 +647,22 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000_000L, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000_000L, C.TIME_UNSET)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 2)
|
.withAdCount(/* adGroupIndex= */ 0, 2)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, "ad0-1")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 1,
|
/* adIndexInAdGroup= */ 1,
|
||||||
MediaItem.fromUri("http://example.com/media-0-1.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -629,10 +693,14 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 4_000_000L)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 4_000_000L)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 4_000_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 4_000_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -662,10 +730,14 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 3_456_000L)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 3_456_000L)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 3_456_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 3_456_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -693,10 +765,14 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_123_000L)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_123_000L)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_123_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_123_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -724,10 +800,14 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 2_234_000L)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 2_234_000L)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 2_234_000L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 2_234_000L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -753,10 +833,391 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 0L)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8")));
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleContentTimelineChanged_livePlaylistWithoutInterstitials_hasLivePlaceholder()
|
||||||
|
throws IOException {
|
||||||
|
assertThat(
|
||||||
|
callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
adsLoader,
|
||||||
|
/* startAdsLoader= */ true,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0L,
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:00.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain0.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "\n",
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:06.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain5.0.ts\n"
|
||||||
|
+ "\n"))
|
||||||
|
.containsExactly(
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
handleContentTimelineChanged_threeLivePlaylistUpdatesUnplayed_correctAdPlaybackStateUpdates()
|
||||||
|
throws IOException {
|
||||||
|
assertThat(
|
||||||
|
callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
adsLoader,
|
||||||
|
/* startAdsLoader= */ true,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0L,
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad0-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:06.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:00.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain0.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n" // ad0-0 cue point: 21:00:06
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "\n",
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad0-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:06.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad1-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:18.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-1-0.m3u8\"\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:06.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n" // ad0-0 cue point: 21:00:06
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n" // ad1-0 cue point: 21:00:18
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain5.0.ts\n"
|
||||||
|
+ "\n",
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:2\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad1-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:18.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-1-0.m3u8\"\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad1-1\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:18.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-1-1.m3u8\"\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:12.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n" // ad1-0 cue point: 21:00:18
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain5.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain6.0.ts\n"
|
||||||
|
+ "\n"))
|
||||||
|
.containsExactly(
|
||||||
|
new AdPlaybackState("adsId", 6_000_000L)
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false),
|
||||||
|
new AdPlaybackState("adsId", 6_000_000L)
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withNewAdGroup(1, 18_000_000L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withAdId(1, 0, "ad1-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 1,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-1-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false),
|
||||||
|
new AdPlaybackState("adsId", 6_000_000L)
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withNewAdGroup(1, 18_000_000L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2)
|
||||||
|
.withAdId(1, 0, "ad1-0")
|
||||||
|
.withAdId(1, 1, "ad1-1")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 1,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-1-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 1,
|
||||||
|
/* adIndexInAdGroup= */ 1,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-1-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false))
|
||||||
|
.inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
handleContentTimelineChanged_livePlaylistUpdateNewAdAfterPlayedAd_correctAdPlaybackStateUpdates()
|
||||||
|
throws IOException {
|
||||||
|
callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
adsLoader,
|
||||||
|
/* startAdsLoader= */ true,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0L,
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad0-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:06.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:00.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain0.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n" // ad0-0 cue point: 21:00:06
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "\n");
|
||||||
|
reset(mockEventListener);
|
||||||
|
// Mark ad as played by a automatic discontinuity from the ad to the content.
|
||||||
|
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
||||||
|
verify(mockPlayer).addListener(listener.capture());
|
||||||
|
Object windowUid = new Object();
|
||||||
|
Object periodUid = new Object();
|
||||||
|
listener
|
||||||
|
.getValue()
|
||||||
|
.onPositionDiscontinuity(
|
||||||
|
new Player.PositionInfo(
|
||||||
|
windowUid,
|
||||||
|
/* mediaItemIndex= */ 0,
|
||||||
|
contentMediaItem,
|
||||||
|
periodUid,
|
||||||
|
/* periodIndex= */ 0,
|
||||||
|
/* positionMs= */ 10_000L,
|
||||||
|
/* contentPositionMs= */ 0L,
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0),
|
||||||
|
new Player.PositionInfo(
|
||||||
|
windowUid,
|
||||||
|
/* mediaItemIndex= */ 0,
|
||||||
|
contentMediaItem,
|
||||||
|
periodUid,
|
||||||
|
/* periodIndex= */ 0,
|
||||||
|
/* positionMs= */ 0L,
|
||||||
|
/* contentPositionMs= */ 0L,
|
||||||
|
/* adGroupIndex= */ C.INDEX_UNSET,
|
||||||
|
/* adIndexInAdGroup= */ C.INDEX_UNSET),
|
||||||
|
DISCONTINUITY_REASON_AUTO_TRANSITION);
|
||||||
|
verify(mockEventListener)
|
||||||
|
.onAdPlaybackState(
|
||||||
|
new AdPlaybackState("adsId", 6_000_000L)
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||||
|
reset(mockEventListener);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
adsLoader,
|
||||||
|
/* startAdsLoader= */ false,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 6_000_000L,
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad0-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:06.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad1-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:18.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-1-0.m3u8\"\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:06.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n" // ad0-0 cue point: 21:00:06
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain5.0.ts\n" // ad1-0 cue point: 21:00:30
|
||||||
|
+ "\n"))
|
||||||
|
.containsExactly(
|
||||||
|
new AdPlaybackState("adsId", 6_000_000L)
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||||
|
.withNewAdGroup(1, 18_000_000L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withAdId(1, 0, "ad1-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 1,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-1-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
handleContentTimelineChanged_attemptInsertionForLiveBeforeAvailableAdGroup_interstitialIgnored()
|
||||||
|
throws IOException {
|
||||||
|
assertThat(
|
||||||
|
callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
adsLoader,
|
||||||
|
/* startAdsLoader= */ true,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0L,
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad0-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:18.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:00.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain0.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n" // ad0-0 cue point: 21:00:18
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "\n",
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad1-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:06.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-1-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:06.000Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n" // ad1-0 cue point: 21:00:06
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain3.0.ts\n" // ad0-0 cue point: 21:00:18
|
||||||
|
+ "#EXTINF:6,\nmain4.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain5.0.ts\n"
|
||||||
|
+ "\n"))
|
||||||
|
.containsExactly(
|
||||||
|
new AdPlaybackState("adsId", 18_000_000L)
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false))
|
||||||
|
.inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleContentTimelineChanged_attemptInsertionBehindLiveWindow_interstitialIgnored()
|
||||||
|
throws IOException {
|
||||||
|
assertThat(
|
||||||
|
callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
adsLoader,
|
||||||
|
/* startAdsLoader= */ true,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0L,
|
||||||
|
"#EXTM3U\n"
|
||||||
|
+ "#EXT-X-TARGETDURATION:6\n"
|
||||||
|
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
|
||||||
|
+ "#EXT-X-DATERANGE:"
|
||||||
|
+ "ID=\"ad0-0\","
|
||||||
|
+ "CLASS=\"com.apple.hls.interstitial\","
|
||||||
|
+ "START-DATE=\"2020-01-02T21:00:00.000Z\","
|
||||||
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
|
+ "\n"
|
||||||
|
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:00.001Z\n"
|
||||||
|
+ "#EXTINF:6,\nmain0.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain1.0.ts\n"
|
||||||
|
+ "#EXTINF:6,\nmain2.0.ts\n"
|
||||||
|
+ "\n"))
|
||||||
|
.containsExactly(
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withAdResumePositionUs(0)
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false))
|
||||||
|
.inOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -832,8 +1293,8 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
/* periodIndex= */ 0,
|
/* periodIndex= */ 0,
|
||||||
/* positionMs= */ 0L,
|
/* positionMs= */ 0L,
|
||||||
/* contentPositionMs= */ 0L,
|
/* contentPositionMs= */ 0L,
|
||||||
/* adGroupIndex= */ -1,
|
/* adGroupIndex= */ C.INDEX_UNSET,
|
||||||
/* adIndexInAdGroup= */ -1),
|
/* adIndexInAdGroup= */ C.INDEX_UNSET),
|
||||||
DISCONTINUITY_REASON_AUTO_TRANSITION);
|
DISCONTINUITY_REASON_AUTO_TRANSITION);
|
||||||
|
|
||||||
verify(mockAdsLoaderListener)
|
verify(mockAdsLoaderListener)
|
||||||
@ -854,14 +1315,22 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET, C.TIME_UNSET)
|
.withAdDurationsUs(/* adGroupIndex= */ 0, C.TIME_UNSET, C.TIME_UNSET)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 0)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 0)
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, "ad0-1")
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 1,
|
/* adIndexInAdGroup= */ 1,
|
||||||
MediaItem.fromUri("http://example.com/media-0-1.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-1.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1));
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1));
|
||||||
}
|
}
|
||||||
@ -884,6 +1353,7 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
+ "X-ASSET-URI=\"http://example.com/media-0-0.m3u8\""
|
||||||
+ "\n";
|
+ "\n";
|
||||||
callHandleContentTimelineChangedAndCaptureAdPlaybackState(playlistString, adsLoader);
|
callHandleContentTimelineChangedAndCaptureAdPlaybackState(playlistString, adsLoader);
|
||||||
|
reset(mockEventListener);
|
||||||
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
||||||
when(mockPlayer.getCurrentTimeline())
|
when(mockPlayer.getCurrentTimeline())
|
||||||
.thenReturn(new FakeTimeline(adsMediaSourceWindowDefinition));
|
.thenReturn(new FakeTimeline(adsMediaSourceWindowDefinition));
|
||||||
@ -911,7 +1381,11 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
.withAvailableAdMediaItem(
|
.withAvailableAdMediaItem(
|
||||||
/* adGroupIndex= */ 0,
|
/* adGroupIndex= */ 0,
|
||||||
/* adIndexInAdGroup= */ 0,
|
/* adIndexInAdGroup= */ 0,
|
||||||
MediaItem.fromUri("http://example.com/media-0-0.m3u8"))
|
new MediaItem.Builder()
|
||||||
|
.setUri("http://example.com/media-0-0.m3u8")
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
||||||
|
.build())
|
||||||
|
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-1")
|
||||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1032,7 +1506,6 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
||||||
InOrder inOrder = inOrder(mockPlayer);
|
InOrder inOrder = inOrder(mockPlayer);
|
||||||
inOrder.verify(mockPlayer).addListener(listener.capture());
|
inOrder.verify(mockPlayer).addListener(listener.capture());
|
||||||
inOrder.verify(mockPlayer).getCurrentTimeline();
|
|
||||||
inOrder.verifyNoMoreInteractions();
|
inOrder.verifyNoMoreInteractions();
|
||||||
reset(mockPlayer);
|
reset(mockPlayer);
|
||||||
|
|
||||||
@ -1059,7 +1532,6 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
ArgumentCaptor<Player.Listener> listener = ArgumentCaptor.forClass(Player.Listener.class);
|
||||||
InOrder inOrder = inOrder(mockPlayer);
|
InOrder inOrder = inOrder(mockPlayer);
|
||||||
inOrder.verify(mockPlayer).addListener(listener.capture());
|
inOrder.verify(mockPlayer).addListener(listener.capture());
|
||||||
inOrder.verify(mockPlayer).getCurrentTimeline();
|
|
||||||
inOrder.verifyNoMoreInteractions();
|
inOrder.verifyNoMoreInteractions();
|
||||||
reset(mockPlayer);
|
reset(mockPlayer);
|
||||||
|
|
||||||
@ -1334,6 +1806,55 @@ public class HlsInterstitialsAdsLoaderTest {
|
|||||||
verifyNoMoreInteractions(mockEventListener);
|
verifyNoMoreInteractions(mockEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<AdPlaybackState> callHandleContentTimelineChangedForLiveAndCaptureAdPlaybackStates(
|
||||||
|
HlsInterstitialsAdsLoader adsLoader,
|
||||||
|
boolean startAdsLoader,
|
||||||
|
long windowOffsetInFirstPeriodUs,
|
||||||
|
String... playlistStrings)
|
||||||
|
throws IOException {
|
||||||
|
if (startAdsLoader) {
|
||||||
|
// Set the player.
|
||||||
|
adsLoader.setPlayer(mockPlayer);
|
||||||
|
// Start the ad.
|
||||||
|
adsLoader.start(
|
||||||
|
adsMediaSource, adTagDataSpec, "adsId", mockAdViewProvider, mockEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
HlsPlaylistParser hlsPlaylistParser = new HlsPlaylistParser();
|
||||||
|
long firstPlaylistStartTimeUs = C.TIME_UNSET;
|
||||||
|
for (String playlistString : playlistStrings) {
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||||
|
HlsMediaPlaylist mediaPlaylist =
|
||||||
|
(HlsMediaPlaylist) hlsPlaylistParser.parse(Uri.EMPTY, inputStream);
|
||||||
|
if (firstPlaylistStartTimeUs == C.TIME_UNSET) {
|
||||||
|
firstPlaylistStartTimeUs = mediaPlaylist.startTimeUs;
|
||||||
|
}
|
||||||
|
HlsManifest hlsManifest = new HlsManifest(/* multivariantPlaylist= */ null, mediaPlaylist);
|
||||||
|
adsLoader.handleContentTimelineChanged(
|
||||||
|
adsMediaSource,
|
||||||
|
new FakeTimeline(
|
||||||
|
new Object[] {hlsManifest},
|
||||||
|
new TimelineWindowDefinition.Builder()
|
||||||
|
.setDynamic(true)
|
||||||
|
.setLive(true)
|
||||||
|
.setDurationUs(mediaPlaylist.durationUs)
|
||||||
|
.setDefaultPositionUs(mediaPlaylist.durationUs / 2)
|
||||||
|
.setWindowStartTimeUs(mediaPlaylist.startTimeUs)
|
||||||
|
.setWindowPositionInFirstPeriodUs(
|
||||||
|
windowOffsetInFirstPeriodUs
|
||||||
|
+ (mediaPlaylist.startTimeUs - firstPlaylistStartTimeUs))
|
||||||
|
.setMediaItem(contentMediaItem)
|
||||||
|
.build()));
|
||||||
|
}
|
||||||
|
ArgumentCaptor<AdPlaybackState> adPlaybackState =
|
||||||
|
ArgumentCaptor.forClass(AdPlaybackState.class);
|
||||||
|
verify(mockEventListener, atMost(playlistStrings.length))
|
||||||
|
.onAdPlaybackState(adPlaybackState.capture());
|
||||||
|
when(mockPlayer.getCurrentTimeline())
|
||||||
|
.thenReturn(new FakeTimeline(adsMediaSourceWindowDefinition));
|
||||||
|
return adPlaybackState.getAllValues();
|
||||||
|
}
|
||||||
|
|
||||||
private AdPlaybackState callHandleContentTimelineChangedAndCaptureAdPlaybackState(
|
private AdPlaybackState callHandleContentTimelineChangedAndCaptureAdPlaybackState(
|
||||||
String playlistString, HlsInterstitialsAdsLoader adsLoader) throws IOException {
|
String playlistString, HlsInterstitialsAdsLoader adsLoader) throws IOException {
|
||||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user