Support ad tag with default media source

This is the missing attribute to support all features of the Sample with MediaItem. Hence PlayerActivity can use the setMediaItems() method directly without creating actual media sources in the app code.

PiperOrigin-RevId: 307102036
This commit is contained in:
bachinger 2020-04-17 21:14:57 +01:00 committed by Oliver Woodman
parent 9937744f0e
commit 8ea33e2315
8 changed files with 494 additions and 124 deletions

View File

@ -45,10 +45,8 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep
import com.google.android.exoplayer2.offline.DownloadRequest; import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
@ -60,7 +58,6 @@ import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.spherical.SphericalGLSurfaceView; import com.google.android.exoplayer2.ui.spherical.SphericalGLSurfaceView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.util.EventLogger;
@ -138,12 +135,11 @@ public class PlayerActivity extends AppCompatActivity
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private List<MediaSource> mediaSources; private List<MediaItem> mediaItems;
private DefaultTrackSelector trackSelector; private DefaultTrackSelector trackSelector;
private DefaultTrackSelector.Parameters trackSelectorParameters; private DefaultTrackSelector.Parameters trackSelectorParameters;
private DebugTextViewHelper debugViewHelper; private DebugTextViewHelper debugViewHelper;
private TrackGroupArray lastSeenTrackGroupArray; private TrackGroupArray lastSeenTrackGroupArray;
private DefaultMediaSourceFactory mediaSourceFactory;
private boolean startAutoPlay; private boolean startAutoPlay;
private int startWindow; private int startWindow;
private long startPosition; private long startPosition;
@ -164,8 +160,6 @@ public class PlayerActivity extends AppCompatActivity
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
dataSourceFactory = buildDataSourceFactory(); dataSourceFactory = buildDataSourceFactory();
mediaSourceFactory =
DefaultMediaSourceFactory.newInstance(/* context= */ this, dataSourceFactory);
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
} }
@ -328,7 +322,7 @@ public class PlayerActivity extends AppCompatActivity
@Override @Override
public void preparePlayback() { public void preparePlayback() {
player.retry(); player.prepare();
} }
// PlaybackControlView.VisibilityListener implementation // PlaybackControlView.VisibilityListener implementation
@ -343,8 +337,8 @@ public class PlayerActivity extends AppCompatActivity
private void initializePlayer() { private void initializePlayer() {
if (player == null) { if (player == null) {
Intent intent = getIntent(); Intent intent = getIntent();
mediaSources = createTopLevelMediaSources(intent); mediaItems = createMediaItems(intent);
if (mediaSources.isEmpty()) { if (mediaItems.isEmpty()) {
return; return;
} }
TrackSelection.Factory trackSelectionFactory; TrackSelection.Factory trackSelectionFactory;
@ -370,6 +364,9 @@ public class PlayerActivity extends AppCompatActivity
player = player =
new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(
/* context= */ this, dataSourceFactory, new AdSupportProvider()))
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.build(); .build();
player.addListener(new PlayerEventListener()); player.addListener(new PlayerEventListener());
@ -380,20 +377,17 @@ public class PlayerActivity extends AppCompatActivity
playerView.setPlaybackPreparer(this); playerView.setPlaybackPreparer(this);
debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper = new DebugTextViewHelper(player, debugTextView);
debugViewHelper.start(); debugViewHelper.start();
if (adsLoader != null) {
adsLoader.setPlayer(player);
}
} }
boolean haveStartPosition = startWindow != C.INDEX_UNSET; boolean haveStartPosition = startWindow != C.INDEX_UNSET;
if (haveStartPosition) { if (haveStartPosition) {
player.seekTo(startWindow, startPosition); player.seekTo(startWindow, startPosition);
} }
player.setMediaSources(mediaSources, /* resetPosition= */ !haveStartPosition); player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition);
player.prepare(); player.prepare();
updateButtonVisibility(); updateButtonVisibility();
} }
private List<MediaSource> createTopLevelMediaSources(Intent intent) { private List<MediaItem> createMediaItems(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
boolean actionIsListView = ACTION_VIEW_LIST.equals(action); boolean actionIsListView = ACTION_VIEW_LIST.equals(action);
if (!actionIsListView && !ACTION_VIEW.equals(action)) { if (!actionIsListView && !ACTION_VIEW.equals(action)) {
@ -408,69 +402,14 @@ public class PlayerActivity extends AppCompatActivity
? ((Sample.PlaylistSample) intentAsSample).children ? ((Sample.PlaylistSample) intentAsSample).children
: new UriSample[] {(UriSample) intentAsSample}; : new UriSample[] {(UriSample) intentAsSample};
List<MediaSource> mediaSources = new ArrayList<>(); List<MediaItem> mediaItems = new ArrayList<>();
Uri adTagUri = null; boolean hasAds = false;
for (UriSample sample : samples) { for (UriSample sample : samples) {
MediaItem mediaItem = sample.toMediaItem(); MediaItem mediaItem = sample.toMediaItem();
Assertions.checkNotNull(mediaItem.playbackProperties);
if (!Util.checkCleartextTrafficPermitted(mediaItem)) {
showToast(R.string.error_cleartext_not_permitted);
return Collections.emptyList();
}
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, mediaItem)) {
// The player will be reinitialized if the permission is granted.
return Collections.emptyList();
}
MediaSource mediaSource = createLeafMediaSource(mediaItem);
if (mediaSource != null) {
adTagUri = sample.adTagUri;
mediaSources.add(mediaSource);
}
}
if (adTagUri == null) {
releaseAdsLoader();
} else if (mediaSources.size() == 1) {
if (!adTagUri.equals(loadedAdTagUri)) {
releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
MediaSource adsMediaSource = createAdsMediaSource(mediaSources.get(0), adTagUri);
if (adsMediaSource != null) {
mediaSources.set(0, adsMediaSource);
} else {
showToast(R.string.ima_not_loaded);
}
} else if (mediaSources.size() > 1) {
showToast(R.string.unsupported_ads_in_concatenation);
releaseAdsLoader();
}
return mediaSources;
}
@Nullable
private MediaSource createLeafMediaSource(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties);
HttpDataSource.Factory drmDataSourceFactory = null;
if (mediaItem.playbackProperties.drmConfiguration != null) {
if (Util.SDK_INT < 18) {
showToast(R.string.error_drm_unsupported_before_api_18);
finish();
return null;
} else if (!MediaDrm.isCryptoSchemeSupported(
mediaItem.playbackProperties.drmConfiguration.uuid)) {
showToast(R.string.error_drm_unsupported_scheme);
finish();
return null;
}
drmDataSourceFactory = ((DemoApplication) getApplication()).buildHttpDataSourceFactory();
}
DownloadRequest downloadRequest = DownloadRequest downloadRequest =
((DemoApplication) getApplication()) ((DemoApplication) getApplication())
.getDownloadTracker() .getDownloadTracker()
.getDownloadRequest(mediaItem.playbackProperties.sourceUri); .getDownloadRequest(Assertions.checkNotNull(mediaItem.playbackProperties).sourceUri);
if (downloadRequest != null) { if (downloadRequest != null) {
mediaItem = mediaItem =
mediaItem mediaItem
@ -479,9 +418,36 @@ public class PlayerActivity extends AppCompatActivity
.setCustomCacheKey(downloadRequest.customCacheKey) .setCustomCacheKey(downloadRequest.customCacheKey)
.build(); .build();
} }
return mediaSourceFactory
.setDrmHttpDataSourceFactory(drmDataSourceFactory) if (!Util.checkCleartextTrafficPermitted(mediaItem)) {
.createMediaSource(mediaItem); showToast(R.string.error_cleartext_not_permitted);
return Collections.emptyList();
}
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, mediaItem)) {
// The player will be reinitialized if the permission is granted.
return Collections.emptyList();
}
MediaItem.DrmConfiguration drmConfiguration =
Assertions.checkNotNull(mediaItem.playbackProperties).drmConfiguration;
if (drmConfiguration != null) {
if (Util.SDK_INT < 18) {
showToast(R.string.error_drm_unsupported_before_api_18);
finish();
return Collections.emptyList();
} else if (!MediaDrm.isCryptoSchemeSupported(drmConfiguration.uuid)) {
showToast(R.string.error_drm_unsupported_scheme);
finish();
return Collections.emptyList();
}
}
hasAds |= mediaItem.playbackProperties.adTagUri != null;
mediaItems.add(mediaItem);
}
if (!hasAds) {
releaseAdsLoader();
}
return mediaItems;
} }
private void releasePlayer() { private void releasePlayer() {
@ -492,7 +458,7 @@ public class PlayerActivity extends AppCompatActivity
debugViewHelper = null; debugViewHelper = null;
player.release(); player.release();
player = null; player = null;
mediaSources = Collections.emptyList(); mediaItems = Collections.emptyList();
trackSelector = null; trackSelector = null;
} }
if (adsLoader != null) { if (adsLoader != null) {
@ -534,14 +500,15 @@ public class PlayerActivity extends AppCompatActivity
return ((DemoApplication) getApplication()).buildDataSourceFactory(); return ((DemoApplication) getApplication()).buildDataSourceFactory();
} }
/** Returns an ads media source, reusing the ads loader if one exists. */ /**
* Returns an ads loader for the Interactive Media Ads SDK if found in the classpath, or null
* otherwise.
*/
@Nullable @Nullable
private MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { private AdsLoader createAdsLoader(Uri adTagUri) {
// Load the extension source using reflection so the demo app doesn't have to depend on it. // Load the extension source using reflection so the demo app doesn't have to depend on it.
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
try { try {
Class<?> loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); Class<?> loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader");
if (adsLoader == null) {
// Full class names used so the lint rule triggers should any of the classes move. // Full class names used so the lint rule triggers should any of the classes move.
// LINT.IfChange // LINT.IfChange
Constructor<? extends AdsLoader> loaderConstructor = Constructor<? extends AdsLoader> loaderConstructor =
@ -549,9 +516,7 @@ public class PlayerActivity extends AppCompatActivity
.asSubclass(AdsLoader.class) .asSubclass(AdsLoader.class)
.getConstructor(android.content.Context.class, android.net.Uri.class); .getConstructor(android.content.Context.class, android.net.Uri.class);
// LINT.ThenChange(../../../../../../../../proguard-rules.txt) // LINT.ThenChange(../../../../../../../../proguard-rules.txt)
adsLoader = loaderConstructor.newInstance(this, adTagUri); return loaderConstructor.newInstance(this, adTagUri);
}
return new AdsMediaSource(mediaSource, mediaSourceFactory, adsLoader, playerView);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// IMA extension not loaded. // IMA extension not loaded.
return null; return null;
@ -670,4 +635,36 @@ public class PlayerActivity extends AppCompatActivity
return Pair.create(0, errorString); return Pair.create(0, errorString);
} }
} }
private class AdSupportProvider implements DefaultMediaSourceFactory.AdSupportProvider {
@Nullable
@Override
public AdsLoader getAdsLoader(Uri adTagUri) {
if (mediaItems.size() > 1) {
showToast(R.string.unsupported_ads_in_concatenation);
releaseAdsLoader();
return null;
}
if (!adTagUri.equals(loadedAdTagUri)) {
releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
if (adsLoader == null) {
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
adsLoader = createAdsLoader(adTagUri);
if (adsLoader != null) {
adsLoader.setPlayer(player);
} else {
showToast(R.string.ima_not_loaded);
}
}
return adsLoader;
}
@Override
public AdsLoader.AdViewProvider getAdViewProvider() {
return Assertions.checkNotNull(playerView);
}
}
} }

View File

@ -146,8 +146,11 @@ import java.util.UUID;
} }
public MediaItem toMediaItem() { public MediaItem toMediaItem() {
MediaItem.Builder builder = new MediaItem.Builder().setSourceUri(uri); MediaItem.Builder builder =
builder.setMimeType(inferAdaptiveStreamMimeType(uri, extension)); new MediaItem.Builder()
.setSourceUri(uri)
.setMimeType(inferAdaptiveStreamMimeType(uri, extension))
.setAdTagUri(adTagUri);
if (drmInfo != null) { if (drmInfo != null) {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
if (drmInfo.drmKeyRequestProperties != null) { if (drmInfo.drmKeyRequestProperties != null) {

View File

@ -71,6 +71,7 @@ public final class MediaItem {
private List<StreamKey> streamKeys; private List<StreamKey> streamKeys;
@Nullable private String customCacheKey; @Nullable private String customCacheKey;
private List<Subtitle> subtitles; private List<Subtitle> subtitles;
@Nullable private Uri adTagUri;
@Nullable private Object tag; @Nullable private Object tag;
@Nullable private MediaMetadata mediaMetadata; @Nullable private MediaMetadata mediaMetadata;
@ -88,12 +89,13 @@ public final class MediaItem {
clipEndPositionMs = mediaItem.clippingProperties.endPositionMs; clipEndPositionMs = mediaItem.clippingProperties.endPositionMs;
clipRelativeToLiveWindow = mediaItem.clippingProperties.relativeToLiveWindow; clipRelativeToLiveWindow = mediaItem.clippingProperties.relativeToLiveWindow;
clipRelativeToDefaultPosition = mediaItem.clippingProperties.relativeToDefaultPosition; clipRelativeToDefaultPosition = mediaItem.clippingProperties.relativeToDefaultPosition;
clipStartsAtKeyFrame = mediaItem.clippingProperties.startsAtKeyFrame;
clipStartPositionMs = mediaItem.clippingProperties.startPositionMs; clipStartPositionMs = mediaItem.clippingProperties.startPositionMs;
clipStartsAtKeyFrame = mediaItem.clippingProperties.startsAtKeyFrame;
mediaId = mediaItem.mediaId; mediaId = mediaItem.mediaId;
mediaMetadata = mediaItem.mediaMetadata; mediaMetadata = mediaItem.mediaMetadata;
@Nullable PlaybackProperties playbackProperties = mediaItem.playbackProperties; @Nullable PlaybackProperties playbackProperties = mediaItem.playbackProperties;
if (playbackProperties != null) { if (playbackProperties != null) {
adTagUri = playbackProperties.adTagUri;
customCacheKey = playbackProperties.customCacheKey; customCacheKey = playbackProperties.customCacheKey;
mimeType = playbackProperties.mimeType; mimeType = playbackProperties.mimeType;
sourceUri = playbackProperties.sourceUri; sourceUri = playbackProperties.sourceUri;
@ -353,6 +355,28 @@ public final class MediaItem {
return this; return this;
} }
/**
* Sets the optional ad tag URI.
*
* <p>If a {@link PlaybackProperties#sourceUri} is set, the ad tag URI is used to create a
* {@link PlaybackProperties} object. Otherwise it will be ignored.
*/
public Builder setAdTagUri(@Nullable String adTagUri) {
this.adTagUri = adTagUri != null ? Uri.parse(adTagUri) : null;
return this;
}
/**
* Sets the optional ad tag {@link Uri}.
*
* <p>If a {@link PlaybackProperties#sourceUri} is set, the ad tag URI is used to create a
* {@link PlaybackProperties} object. Otherwise it will be ignored.
*/
public Builder setAdTagUri(@Nullable Uri adTagUri) {
this.adTagUri = adTagUri;
return this;
}
/** /**
* Sets the optional tag for custom attributes. The tag for the media source which will be * Sets the optional tag for custom attributes. The tag for the media source which will be
* published in the {@code com.google.android.exoplayer2.Timeline} of the source as {@code * published in the {@code com.google.android.exoplayer2.Timeline} of the source as {@code
@ -395,6 +419,7 @@ public final class MediaItem {
streamKeys, streamKeys,
customCacheKey, customCacheKey,
subtitles, subtitles,
adTagUri,
tag); tag);
mediaId = mediaId != null ? mediaId : sourceUri.toString(); mediaId = mediaId != null ? mediaId : sourceUri.toString();
} }
@ -509,6 +534,9 @@ public final class MediaItem {
/** Optional subtitles to be sideloaded. */ /** Optional subtitles to be sideloaded. */
public final List<Subtitle> subtitles; public final List<Subtitle> subtitles;
/** Optional ad tag {@link Uri}. */
@Nullable public final Uri adTagUri;
/** /**
* Optional tag for custom attributes. The tag for the media source which will be published in * Optional tag for custom attributes. The tag for the media source which will be published in
* the {@code com.google.android.exoplayer2.Timeline} of the source as {@code * the {@code com.google.android.exoplayer2.Timeline} of the source as {@code
@ -523,6 +551,7 @@ public final class MediaItem {
List<StreamKey> streamKeys, List<StreamKey> streamKeys,
@Nullable String customCacheKey, @Nullable String customCacheKey,
List<Subtitle> subtitles, List<Subtitle> subtitles,
@Nullable Uri adTagUri,
@Nullable Object tag) { @Nullable Object tag) {
this.sourceUri = sourceUri; this.sourceUri = sourceUri;
this.mimeType = mimeType; this.mimeType = mimeType;
@ -530,6 +559,7 @@ public final class MediaItem {
this.streamKeys = streamKeys; this.streamKeys = streamKeys;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.subtitles = subtitles; this.subtitles = subtitles;
this.adTagUri = adTagUri;
this.tag = tag; this.tag = tag;
} }
@ -549,6 +579,7 @@ public final class MediaItem {
&& streamKeys.equals(other.streamKeys) && streamKeys.equals(other.streamKeys)
&& Util.areEqual(customCacheKey, other.customCacheKey) && Util.areEqual(customCacheKey, other.customCacheKey)
&& subtitles.equals(other.subtitles) && subtitles.equals(other.subtitles)
&& Util.areEqual(adTagUri, other.adTagUri)
&& Util.areEqual(tag, other.tag); && Util.areEqual(tag, other.tag);
} }
@ -560,6 +591,7 @@ public final class MediaItem {
result = 31 * result + streamKeys.hashCode(); result = 31 * result + streamKeys.hashCode();
result = 31 * result + (customCacheKey == null ? 0 : customCacheKey.hashCode()); result = 31 * result + (customCacheKey == null ? 0 : customCacheKey.hashCode());
result = 31 * result + subtitles.hashCode(); result = 31 * result + subtitles.hashCode();
result = 31 * result + (adTagUri == null ? 0 : adTagUri.hashCode());
result = 31 * result + (tag == null ? 0 : tag.hashCode()); result = 31 * result + (tag == null ? 0 : tag.hashCode());
return result; return result;
} }

View File

@ -266,6 +266,16 @@ public class MediaItemTest {
assertThat(mediaItem.clippingProperties.startsAtKeyFrame).isTrue(); assertThat(mediaItem.clippingProperties.startsAtKeyFrame).isTrue();
} }
@Test
public void builderSetAdTagUri_setsAdTagUri() {
Uri adTagUri = Uri.parse(URI_STRING + "/ad");
MediaItem mediaItem =
new MediaItem.Builder().setSourceUri(URI_STRING).setAdTagUri(adTagUri).build();
assertThat(mediaItem.playbackProperties.adTagUri).isEqualTo(adTagUri);
}
@Test @Test
public void builderSetMediaMetadata_setsMetadata() { public void builderSetMediaMetadata_setsMetadata() {
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build(); MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
@ -280,6 +290,7 @@ public class MediaItemTest {
public void buildUpon_equalsToOriginal() { public void buildUpon_equalsToOriginal() {
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder() new MediaItem.Builder()
.setAdTagUri(URI_STRING)
.setClipEndPositionMs(1000) .setClipEndPositionMs(1000)
.setClipRelativeToDefaultPosition(true) .setClipRelativeToDefaultPosition(true)
.setClipRelativeToLiveWindow(true) .setClipRelativeToLiveWindow(true)

View File

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@ -28,6 +30,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
@ -84,6 +87,7 @@ import java.util.concurrent.TimeoutException;
private SeekParameters seekParameters; private SeekParameters seekParameters;
private ShuffleOrder shuffleOrder; private ShuffleOrder shuffleOrder;
private boolean pauseAtEndOfMediaItems; private boolean pauseAtEndOfMediaItems;
private boolean hasAdsMediaSource;
// Playback information when there is no pending seek/set source operation. // Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
@ -123,8 +127,8 @@ import java.util.concurrent.TimeoutException;
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " [" Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]"); + ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0); Assertions.checkState(renderers.length > 0);
this.renderers = Assertions.checkNotNull(renderers); this.renderers = checkNotNull(renderers);
this.trackSelector = Assertions.checkNotNull(trackSelector); this.trackSelector = checkNotNull(trackSelector);
this.mediaSourceFactory = mediaSourceFactory; this.mediaSourceFactory = mediaSourceFactory;
this.useLazyPreparation = useLazyPreparation; this.useLazyPreparation = useLazyPreparation;
repeatMode = Player.REPEAT_MODE_OFF; repeatMode = Player.REPEAT_MODE_OFF;
@ -397,9 +401,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void addMediaSources(int index, List<MediaSource> mediaSources) { public void addMediaSources(int index, List<MediaSource> mediaSources) {
Assertions.checkArgument(index >= 0); Assertions.checkArgument(index >= 0);
for (int i = 0; i < mediaSources.size(); i++) { validateMediaSources(mediaSources, /* mediaSourceReplacement= */ false);
Assertions.checkArgument(mediaSources.get(i) != null);
}
int currentWindowIndex = getCurrentWindowIndex(); int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition(); long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
@ -973,9 +975,7 @@ import java.util.concurrent.TimeoutException;
int startWindowIndex, int startWindowIndex,
long startPositionMs, long startPositionMs,
boolean resetToDefaultPosition) { boolean resetToDefaultPosition) {
for (int i = 0; i < mediaSources.size(); i++) { validateMediaSources(mediaSources, /* mediaSourceReplacement= */ true);
Assertions.checkArgument(mediaSources.get(i) != null);
}
int currentWindowIndex = getCurrentWindowIndexInternal(); int currentWindowIndex = getCurrentWindowIndexInternal();
long currentPositionMs = getCurrentPosition(); long currentPositionMs = getCurrentPosition();
pendingOperationAcks++; pendingOperationAcks++;
@ -1076,9 +1076,42 @@ import java.util.concurrent.TimeoutException;
removed.add(mediaSourceHolders.remove(i)); removed.add(mediaSourceHolders.remove(i));
} }
shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndexExclusive); shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndexExclusive);
if (mediaSourceHolders.isEmpty()) {
hasAdsMediaSource = false;
}
return removed; return removed;
} }
/**
* Validates media sources before any modification of the existing list of media sources is made.
* This way we can throw an exception before changing the state of the player in case of a
* validation failure.
*
* @param mediaSources The media sources to set or add.
* @param mediaSourceReplacement Whether the given media sources will replace existing ones.
*/
private void validateMediaSources(
List<MediaSource> mediaSources, boolean mediaSourceReplacement) {
if (hasAdsMediaSource && !mediaSourceReplacement && !mediaSources.isEmpty()) {
// Adding media sources to an ads media source is not allowed
// (see https://github.com/google/ExoPlayer/issues/3750).
throw new IllegalStateException();
}
int sizeAfterModification =
mediaSources.size() + (mediaSourceReplacement ? 0 : mediaSourceHolders.size());
for (int i = 0; i < mediaSources.size(); i++) {
MediaSource mediaSource = checkNotNull(mediaSources.get(i));
if (mediaSource instanceof AdsMediaSource) {
if (sizeAfterModification > 1) {
// Ads media sources only allowed with a single source
// (see https://github.com/google/ExoPlayer/issues/3750).
throw new IllegalArgumentException();
}
hasAdsMediaSource = true;
}
}
}
private PlaybackInfo maskTimeline() { private PlaybackInfo maskTimeline() {
return playbackInfo.copyWithTimeline( return playbackInfo.copyWithTimeline(
mediaSourceHolders.isEmpty() mediaSourceHolders.isEmpty()

View File

@ -29,6 +29,8 @@ import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.MediaDrmCallback; import com.google.android.exoplayer2.drm.MediaDrmCallback;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
@ -36,6 +38,7 @@ import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.Arrays; import java.util.Arrays;
@ -89,9 +92,36 @@ import java.util.Map;
* alternative dummy, apps can pass a drm session manager to {@link * alternative dummy, apps can pass a drm session manager to {@link
* #setDrmSessionManager(DrmSessionManager)} which will be used for all items without a drm * #setDrmSessionManager(DrmSessionManager)} which will be used for all items without a drm
* configuration. * configuration.
*
* <h3>Ad support for media items with ad tag uri</h3>
*
* <p>For a media item with an ad tag uri an {@link AdSupportProvider} needs to be passed to the
* constructor {@link #DefaultMediaSourceFactory(Context, DataSource.Factory, AdSupportProvider)}.
*/ */
public final class DefaultMediaSourceFactory implements MediaSourceFactory { public final class DefaultMediaSourceFactory implements MediaSourceFactory {
/**
* Provides {@link AdsLoader ads loaders} and an {@link AdsLoader.AdViewProvider} to created
* {@link AdsMediaSource AdsMediaSources}.
*/
public interface AdSupportProvider {
/**
* Returns an {@link AdsLoader} for the given {@link Uri ad tag uri} or null if no ads loader is
* available for the given ad tag uri.
*
* <p>This method is called for each media item for which a media source is created.
*/
@Nullable
AdsLoader getAdsLoader(Uri adTagUri);
/**
* Returns an {@link AdsLoader.AdViewProvider} which is used to create {@link AdsMediaSource
* AdsMediaSources}.
*/
AdsLoader.AdViewProvider getAdViewProvider();
}
/** /**
* Creates a new instance with the given {@link Context}. * Creates a new instance with the given {@link Context}.
* *
@ -115,10 +145,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
*/ */
public static DefaultMediaSourceFactory newInstance( public static DefaultMediaSourceFactory newInstance(
Context context, DataSource.Factory dataSourceFactory) { Context context, DataSource.Factory dataSourceFactory) {
return new DefaultMediaSourceFactory(context, dataSourceFactory); return new DefaultMediaSourceFactory(context, dataSourceFactory, /* adSupportProvider= */ null);
} }
private static final String TAG = "DefaultMediaSourceFactory";
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
@Nullable private final AdSupportProvider adSupportProvider;
private final SparseArray<MediaSourceFactory> mediaSourceFactories; private final SparseArray<MediaSourceFactory> mediaSourceFactories;
@C.ContentType private final int[] supportedTypes; @C.ContentType private final int[] supportedTypes;
private final String userAgent; private final String userAgent;
@ -127,8 +160,20 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private HttpDataSource.Factory drmHttpDataSourceFactory; private HttpDataSource.Factory drmHttpDataSourceFactory;
@Nullable private List<StreamKey> streamKeys; @Nullable private List<StreamKey> streamKeys;
private DefaultMediaSourceFactory(Context context, DataSource.Factory dataSourceFactory) { /**
* Creates a new instance with the given {@link Context} and {@link DataSource.Factory}.
*
* @param context The {@link Context}.
* @param dataSourceFactory A {@link DataSource.Factory} to be used to create media sources.
* @param adSupportProvider An {@link AdSupportProvider} to get ads loaders and ad view providers
* to be used to create {@link AdsMediaSource AdsMediaSources}.
*/
public DefaultMediaSourceFactory(
Context context,
DataSource.Factory dataSourceFactory,
@Nullable AdSupportProvider adSupportProvider) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.adSupportProvider = adSupportProvider;
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
userAgent = Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY); userAgent = Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY);
drmHttpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent); drmHttpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
@ -214,15 +259,12 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
? mediaItem.playbackProperties.streamKeys ? mediaItem.playbackProperties.streamKeys
: streamKeys); : streamKeys);
MediaSource leafMediaSource = mediaSourceFactory.createMediaSource(mediaItem); MediaSource mediaSource = mediaSourceFactory.createMediaSource(mediaItem);
List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles; List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
if (subtitles.isEmpty()) { if (!subtitles.isEmpty()) {
return maybeClipMediaSource(mediaItem, leafMediaSource);
}
MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1]; MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
mediaSources[0] = leafMediaSource; mediaSources[0] = mediaSource;
SingleSampleMediaSource.Factory singleSampleSourceFactory = SingleSampleMediaSource.Factory singleSampleSourceFactory =
new SingleSampleMediaSource.Factory(dataSourceFactory); new SingleSampleMediaSource.Factory(dataSourceFactory);
for (int i = 0; i < subtitles.size(); i++) { for (int i = 0; i < subtitles.size(); i++) {
@ -237,7 +279,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
singleSampleSourceFactory.createMediaSource( singleSampleSourceFactory.createMediaSource(
subtitle.uri, subtitleFormat, /* durationUs= */ C.TIME_UNSET); subtitle.uri, subtitleFormat, /* durationUs= */ C.TIME_UNSET);
} }
return maybeClipMediaSource(mediaItem, new MergingMediaSource(mediaSources)); mediaSource = new MergingMediaSource(mediaSources);
}
return maybeWrapWithAdsMediaSource(mediaItem, maybeClipMediaSource(mediaItem, mediaSource));
} }
// internal methods // internal methods
@ -285,6 +329,34 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
mediaItem.clippingProperties.relativeToDefaultPosition); mediaItem.clippingProperties.relativeToDefaultPosition);
} }
private MediaSource maybeWrapWithAdsMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
Assertions.checkNotNull(mediaItem.playbackProperties);
if (mediaItem.playbackProperties.adTagUri == null) {
return mediaSource;
}
if (adSupportProvider == null) {
Log.w(
TAG,
"Playing media without ads. Pass an AdsSupportProvider to the constructor for supporting"
+ " media items with an ad tag uri.");
return mediaSource;
}
AdsLoader adsLoader = adSupportProvider.getAdsLoader(mediaItem.playbackProperties.adTagUri);
if (adsLoader == null) {
Log.w(
TAG,
String.format(
"Playing media without ads. No AdsLoader for media item with mediaId '%s'.",
mediaItem.mediaId));
return mediaSource;
}
return new AdsMediaSource(
mediaSource,
/* adMediaSourceFactory= */ this,
adsLoader,
adSupportProvider.getAdViewProvider());
}
private static SparseArray<MediaSourceFactory> loadDelegates( private static SparseArray<MediaSourceFactory> loadDelegates(
DataSource.Factory dataSourceFactory) { DataSource.Factory dataSourceFactory) {
SparseArray<MediaSourceFactory> factories = new SparseArray<>(); SparseArray<MediaSourceFactory> factories = new SparseArray<>();

View File

@ -27,6 +27,8 @@ import android.graphics.SurfaceTexture;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Looper; import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
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;
@ -48,6 +50,8 @@ import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule;
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget;
@ -72,10 +76,12 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.Allocation; import com.google.android.exoplayer2.upstream.Allocation;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -4378,6 +4384,121 @@ public final class ExoPlayerTest {
assertThat(positionAfterSetShuffleOrder.get()).isAtLeast(5000); assertThat(positionAfterSetShuffleOrder.get()).isAtLeast(5000);
} }
@Test
public void setMediaSources_secondAdMediaSource_throws() throws Exception {
AdsMediaSource adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new DefaultDataSourceFactory(
context, Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY)),
new DummyAdsLoader(),
new DummyAdViewProvider());
Exception[] exception = {null};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
try {
player.setMediaSource(adsMediaSource);
player.addMediaSource(adsMediaSource);
} catch (Exception e) {
exception[0] = e;
}
player.prepare();
}
})
.build();
new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(exception[0]).isInstanceOf(IllegalStateException.class);
}
@Test
public void setMediaSources_multipleMediaSourcesWithAd_throws() throws Exception {
MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1));
AdsMediaSource adsMediaSource =
new AdsMediaSource(
mediaSource,
new DefaultDataSourceFactory(
context, Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY)),
new DummyAdsLoader(),
new DummyAdViewProvider());
final Exception[] exception = {null};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
try {
List<MediaSource> sources = new ArrayList<>();
sources.add(mediaSource);
sources.add(adsMediaSource);
player.setMediaSources(sources);
} catch (Exception e) {
exception[0] = e;
}
player.prepare();
}
})
.build();
new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(exception[0]).isInstanceOf(IllegalArgumentException.class);
}
@Test
public void setMediaSources_addingMediaSourcesWithAdToNonEmptyPlaylist_throws() throws Exception {
MediaSource mediaSource = new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1));
AdsMediaSource adsMediaSource =
new AdsMediaSource(
mediaSource,
new DefaultDataSourceFactory(
context, Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY)),
new DummyAdsLoader(),
new DummyAdViewProvider());
final Exception[] exception = {null};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
try {
player.addMediaSource(adsMediaSource);
} catch (Exception e) {
exception[0] = e;
}
}
})
.build();
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(exception[0]).isInstanceOf(IllegalArgumentException.class);
}
@Test @Test
public void setMediaSources_empty_whenEmpty_correctMaskingWindowIndex() throws Exception { public void setMediaSources_empty_whenEmpty_correctMaskingWindowIndex() throws Exception {
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
@ -6493,4 +6614,38 @@ public final class ExoPlayerTest {
return Loader.RETRY; return Loader.RETRY;
} }
} }
private static class DummyAdsLoader implements AdsLoader {
@Override
public void setPlayer(@Nullable Player player) {}
@Override
public void release() {}
@Override
public void setSupportedContentTypes(int... contentTypes) {}
@Override
public void start(AdsLoader.EventListener eventListener, AdViewProvider adViewProvider) {}
@Override
public void stop() {}
@Override
public void handlePrepareError(int adGroupIndex, int adIndexInAdGroup, IOException exception) {}
}
private static class DummyAdViewProvider implements AdsLoader.AdViewProvider {
@Override
public ViewGroup getAdViewGroup() {
return null;
}
@Override
public View[] getAdOverlayViews() {
return new View[0];
}
}
} }

View File

@ -16,12 +16,18 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable;
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.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -184,4 +190,65 @@ public final class DefaultMediaSourceFactoryTest {
assertThat(supportedTypes).asList().containsExactly(C.TYPE_OTHER); assertThat(supportedTypes).asList().containsExactly(C.TYPE_OTHER);
} }
@Test
public void createMediaSource_withAdTagUri_callsAdsLoader() {
Context applicationContext = ApplicationProvider.getApplicationContext();
Uri adTagUri = Uri.parse(URI_MEDIA);
MediaItem mediaItem =
new MediaItem.Builder().setSourceUri(URI_MEDIA).setAdTagUri(adTagUri).build();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory(
applicationContext,
new DefaultDataSourceFactory(applicationContext, "userAgent"),
createAdSupportProvider(mock(AdsLoader.class), mock(AdsLoader.AdViewProvider.class)));
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
assertThat(mediaSource).isInstanceOf(AdsMediaSource.class);
}
@Test
public void createMediaSource_withAdTagUriAdsLoaderNull_playsWithoutAdNoException() {
Context applicationContext = ApplicationProvider.getApplicationContext();
MediaItem mediaItem =
new MediaItem.Builder().setSourceUri(URI_MEDIA).setAdTagUri(Uri.parse(URI_MEDIA)).build();
DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory(
applicationContext,
new DefaultDataSourceFactory(applicationContext, "userAgent"),
createAdSupportProvider(/* adsLoader= */ null, mock(AdsLoader.AdViewProvider.class)));
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
assertThat(mediaSource).isNotInstanceOf(AdsMediaSource.class);
}
@Test
public void createMediaSource_withAdTagUriProvidersNull_playsWithoutAdNoException() {
Context applicationContext = ApplicationProvider.getApplicationContext();
MediaItem mediaItem =
new MediaItem.Builder().setSourceUri(URI_MEDIA).setAdTagUri(Uri.parse(URI_MEDIA)).build();
MediaSource mediaSource =
DefaultMediaSourceFactory.newInstance(applicationContext).createMediaSource(mediaItem);
assertThat(mediaSource).isNotInstanceOf(AdsMediaSource.class);
}
private static DefaultMediaSourceFactory.AdSupportProvider createAdSupportProvider(
@Nullable AdsLoader adsLoader, AdsLoader.AdViewProvider adViewProvider) {
return new DefaultMediaSourceFactory.AdSupportProvider() {
@Nullable
@Override
public AdsLoader getAdsLoader(Uri adTagUri) {
return adsLoader;
}
@Override
public AdsLoader.AdViewProvider getAdViewProvider() {
return adViewProvider;
}
};
}
} }