Commit 5fd1601f by andrewlewis Committed by Andrew Lewis

Signal an ads identifier to the AdsLoader

In a later change, the AdPlaybackState will include the playing adsId (set by
the AdsLoader) and the ads loader will use this to determine what ad
information is associated with the playing/next periods, to allow loading ads
in playlists.

Apps can continue to pass just a URI for an ad tag with their MediaItem, in
which case the associated playlist will request that ad tag just and the same
state will be used for all occurrences of the ad tag.

This change has breaking changes to the AdsLoader interface and removes
deprecated ways of passing the ad tag, as it's very likely to go into a major
release anyway and not needing to handle the deprecated cases simplifies
ImaAdsLoader.

Issue: #3750
PiperOrigin-RevId: 340438580
parent f937e40e
...@@ -173,7 +173,9 @@ public class IntentUtil { ...@@ -173,7 +173,9 @@ public class IntentUtil {
.putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, playbackProperties.mimeType) .putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, playbackProperties.mimeType)
.putExtra( .putExtra(
AD_TAG_URI_EXTRA + extrasKeySuffix, AD_TAG_URI_EXTRA + extrasKeySuffix,
playbackProperties.adTagUri != null ? playbackProperties.adTagUri.toString() : null); playbackProperties.adsConfiguration != null
? playbackProperties.adsConfiguration.adTagUri.toString()
: null);
if (playbackProperties.drmConfiguration != null) { if (playbackProperties.drmConfiguration != null) {
addDrmConfigurationToIntent(playbackProperties.drmConfiguration, intent, extrasKeySuffix); addDrmConfigurationToIntent(playbackProperties.drmConfiguration, intent, extrasKeySuffix);
} }
......
...@@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; ...@@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
...@@ -102,12 +101,11 @@ public class PlayerActivity extends AppCompatActivity ...@@ -102,12 +101,11 @@ public class PlayerActivity extends AppCompatActivity
private int startWindow; private int startWindow;
private long startPosition; private long startPosition;
// Fields used only for ad playback. // For ad playback only.
private AdsLoader adsLoader; private AdsLoader adsLoader;
private Uri loadedAdTagUri;
// Activity lifecycle // Activity lifecycle.
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
...@@ -355,7 +353,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -355,7 +353,7 @@ public class PlayerActivity extends AppCompatActivity
return Collections.emptyList(); return Collections.emptyList();
} }
} }
hasAds |= mediaItem.playbackProperties.adTagUri != null; hasAds |= mediaItem.playbackProperties.adsConfiguration != null;
} }
if (!hasAds) { if (!hasAds) {
releaseAdsLoader(); releaseAdsLoader();
...@@ -363,16 +361,12 @@ public class PlayerActivity extends AppCompatActivity ...@@ -363,16 +361,12 @@ public class PlayerActivity extends AppCompatActivity
return mediaItems; return mediaItems;
} }
private AdsLoader getAdsLoader(Uri adTagUri) { private AdsLoader getAdsLoader(MediaItem.AdsConfiguration adsConfiguration) {
if (mediaItems.size() > 1) { if (mediaItems.size() > 1) {
showToast(R.string.unsupported_ads_in_playlist); showToast(R.string.unsupported_ads_in_playlist);
releaseAdsLoader(); releaseAdsLoader();
return null; return null;
} }
if (!adTagUri.equals(loadedAdTagUri)) {
releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
// The ads loader is reused for multiple playbacks, so that ad playback can resume. // The ads loader is reused for multiple playbacks, so that ad playback can resume.
if (adsLoader == null) { if (adsLoader == null) {
adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build(); adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
...@@ -401,7 +395,6 @@ public class PlayerActivity extends AppCompatActivity ...@@ -401,7 +395,6 @@ public class PlayerActivity extends AppCompatActivity
if (adsLoader != null) { if (adsLoader != null) {
adsLoader.release(); adsLoader.release();
adsLoader = null; adsLoader = null;
loadedAdTagUri = null;
playerView.getOverlayFrameLayout().removeAllViews(); playerView.getOverlayFrameLayout().removeAllViews();
} }
} }
......
...@@ -252,7 +252,7 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -252,7 +252,7 @@ public class SampleChooserActivity extends AppCompatActivity
} }
MediaItem.PlaybackProperties playbackProperties = MediaItem.PlaybackProperties playbackProperties =
checkNotNull(playlistHolder.mediaItems.get(0).playbackProperties); checkNotNull(playlistHolder.mediaItems.get(0).playbackProperties);
if (playbackProperties.adTagUri != null) { if (playbackProperties.adsConfiguration != null) {
return R.string.download_ads_unsupported; return R.string.download_ads_unsupported;
} }
String scheme = playbackProperties.uri.getScheme(); String scheme = playbackProperties.uri.getScheme();
......
...@@ -33,17 +33,19 @@ of the developer guide. The `AdsLoaderProvider` passed to the player's ...@@ -33,17 +33,19 @@ of the developer guide. The `AdsLoaderProvider` passed to the player's
extension only supports players which are accessed on the application's main extension only supports players which are accessed on the application's main
thread. thread.
Resuming the player after entering the background requires some special handling Resuming the player after entering the background requires some special
when playing ads. The player and its media source are released on entering the handling when playing ads. The player and its media source are released on
background, and are recreated when returning to the foreground. When playing ads entering the background, and are recreated when returning to the foreground.
it is necessary to persist ad playback state while in the background by keeping When playing ads it is necessary to persist ad playback state while in the
a reference to the `ImaAdsLoader`. When re-entering the foreground, pass the background by keeping a reference to the `ImaAdsLoader`. When re-entering the
same instance back when `AdsLoaderProvider.getAdsLoader(Uri adTagUri)` is called foreground, pass the same instance back when
to restore the state. It is also important to persist the player position when `AdsLoaderProvider.getAdsLoader(MediaItem.AdsConfiguration adsConfiguration)`
entering the background by storing the value of `player.getContentPosition()`. is called to restore the state. It is also important to persist the player
On returning to the foreground, seek to that position before preparing the new position when entering the background by storing the value of
player instance. Finally, it is important to call `ImaAdsLoader.release()` when `player.getContentPosition()`. On returning to the foreground, seek to that
playback has finished and will not be resumed. position before preparing the new player instance. Finally, it is important to
call `ImaAdsLoader.release()` when playback has finished and will not be
resumed.
You can try the IMA extension in the ExoPlayer demo app, which has test content You can try the IMA extension in the ExoPlayer demo app, which has test content
in the "IMA sample ad tags" section of the sample chooser. The demo app's in the "IMA sample ad tags" section of the sample chooser. The demo app's
......
...@@ -248,6 +248,7 @@ public final class ImaPlaybackTest { ...@@ -248,6 +248,7 @@ public final class ImaPlaybackTest {
return new AdsMediaSource( return new AdsMediaSource(
contentMediaSource, contentMediaSource,
adTagDataSpec, adTagDataSpec,
/* adsId= */ adTagDataSpec.uri,
new DefaultMediaSourceFactory(dataSourceFactory), new DefaultMediaSourceFactory(dataSourceFactory),
Assertions.checkNotNull(imaAdsLoader), Assertions.checkNotNull(imaAdsLoader),
new AdViewProvider() { new AdViewProvider() {
......
...@@ -23,7 +23,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; ...@@ -23,7 +23,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
...@@ -343,125 +342,44 @@ public final class ImaAdsLoader ...@@ -343,125 +342,44 @@ public final class ImaAdsLoader
return this; return this;
} }
/**
* Returns a new {@link ImaAdsLoader} for the specified ad tag.
*
* @param adTagUri The URI of a compatible ad tag to load. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* information on compatible ad tags.
* @return The new {@link ImaAdsLoader}.
* @deprecated Pass the ad tag URI when setting media item playback properties (if using the
* media item API) or as a {@link DataSpec} when constructing the {@link AdsMediaSource} (if
* using media sources directly).
*/
@Deprecated
public ImaAdsLoader buildForAdTag(Uri adTagUri) {
return new ImaAdsLoader(
context,
getConfiguration(),
imaFactory,
/* adTagUri= */ adTagUri,
/* adsResponse= */ null);
}
/**
* Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response.
*
* @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of
* making a request via an ad tag URL.
* @return The new {@link ImaAdsLoader}.
* @deprecated Pass the ads response as a data URI when setting media item playback properties
* (if using the media item API) or as a {@link DataSpec} when constructing the {@link
* AdsMediaSource} (if using media sources directly). {@link
* Util#getDataUriForString(String, String)} can be used to construct a data URI from
* literal string ads response (with MIME type text/xml).
*/
@Deprecated
public ImaAdsLoader buildForAdsResponse(String adsResponse) {
return new ImaAdsLoader(
context, getConfiguration(), imaFactory, /* adTagUri= */ null, adsResponse);
}
/** Returns a new {@link ImaAdsLoader}. */ /** Returns a new {@link ImaAdsLoader}. */
public ImaAdsLoader build() { public ImaAdsLoader build() {
return new ImaAdsLoader( return new ImaAdsLoader(
context, getConfiguration(), imaFactory, /* adTagUri= */ null, /* adsResponse= */ null); context,
} new ImaUtil.Configuration(
adPreloadTimeoutMs,
// TODO(internal: b/169646419): Remove/hide once the deprecated constructor has been removed. vastLoadTimeoutMs,
/* package */ ImaUtil.Configuration getConfiguration() { mediaLoadTimeoutMs,
return new ImaUtil.Configuration( focusSkipButtonWhenAvailable,
adPreloadTimeoutMs, playAdBeforeStartPosition,
vastLoadTimeoutMs, mediaBitrate,
mediaLoadTimeoutMs, adMediaMimeTypes,
focusSkipButtonWhenAvailable, adUiElements,
playAdBeforeStartPosition, companionAdSlots,
mediaBitrate, adErrorListener,
adMediaMimeTypes, adEventListener,
adUiElements, videoAdPlayerCallback,
companionAdSlots, imaSdkSettings,
adErrorListener, debugModeEnabled),
adEventListener, imaFactory);
videoAdPlayerCallback,
imaSdkSettings,
debugModeEnabled);
} }
} }
private static final DataSpec EMPTY_AD_TAG_DATA_SPEC = new DataSpec(Uri.EMPTY);
private final ImaUtil.Configuration configuration; private final ImaUtil.Configuration configuration;
private final Context context; private final Context context;
private final ImaUtil.ImaFactory imaFactory; private final ImaUtil.ImaFactory imaFactory;
@Nullable private final DataSpec deprecatedAdTagDataSpec;
private boolean wasSetPlayerCalled; private boolean wasSetPlayerCalled;
@Nullable private Player nextPlayer; @Nullable private Player nextPlayer;
@Nullable private AdTagLoader adTagLoader; @Nullable private AdTagLoader adTagLoader;
private List<String> supportedMimeTypes; private List<String> supportedMimeTypes;
private DataSpec adTagDataSpec;
@Nullable private Player player; @Nullable private Player player;
/**
* Creates a new IMA ads loader.
*
* <p>If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead.
*
* @param context The context.
* @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* more information.
* @deprecated Use {@link Builder} to create an instance. Pass the ad tag URI when setting media
* item playback properties (if using the media item API) or as a {@link DataSpec} when
* constructing the {@link AdsMediaSource} (if using media sources directly).
*/
@Deprecated
public ImaAdsLoader(Context context, Uri adTagUri) {
this(
context,
new Builder(context).getConfiguration(),
new DefaultImaFactory(),
adTagUri,
/* adsResponse= */ null);
}
private ImaAdsLoader( private ImaAdsLoader(
Context context, Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory) {
ImaUtil.Configuration configuration,
ImaUtil.ImaFactory imaFactory,
@Nullable Uri adTagUri,
@Nullable String adsResponse) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.configuration = configuration; this.configuration = configuration;
this.imaFactory = imaFactory; this.imaFactory = imaFactory;
deprecatedAdTagDataSpec =
adTagUri != null
? new DataSpec(adTagUri)
: adsResponse != null
? new DataSpec(
Util.getDataUriForString(/* mimeType= */ "text/xml", /* data= */ adsResponse))
: null;
adTagDataSpec = EMPTY_AD_TAG_DATA_SPEC;
supportedMimeTypes = ImmutableList.of(); supportedMimeTypes = ImmutableList.of();
} }
...@@ -497,40 +415,17 @@ public final class ImaAdsLoader ...@@ -497,40 +415,17 @@ public final class ImaAdsLoader
* called, so it is only necessary to call this method if you want to request ads before preparing * called, so it is only necessary to call this method if you want to request ads before preparing
* the player. * the player.
* *
* @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code
* null} if playing audio-only ads.
* @deprecated Use {@link #requestAds(DataSpec, ViewGroup)}, specifying the ad tag data spec to
* request, and migrate off deprecated builder methods/constructor that require an ad tag or
* ads response.
*/
@Deprecated
public void requestAds(@Nullable ViewGroup adViewGroup) {
requestAds(adTagDataSpec, adViewGroup);
}
/**
* Requests ads, if they have not already been requested. Must be called on the main thread.
*
* <p>Ads will be requested automatically when the player is prepared if this method has not been
* called, so it is only necessary to call this method if you want to request ads before preparing
* the player.
*
* @param adTagDataSpec The data specification of the ad tag to load. See class javadoc for * @param adTagDataSpec The data specification of the ad tag to load. See class javadoc for
* information about compatible ad tag formats. * information about compatible ad tag formats.
* @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code
* null} if playing audio-only ads. * null} if playing audio-only ads.
*/ */
public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) { public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) {
if (adTagLoader != null) { if (adTagLoader == null) {
return; adTagLoader =
} new AdTagLoader(
context, configuration, imaFactory, supportedMimeTypes, adTagDataSpec, adViewGroup);
if (EMPTY_AD_TAG_DATA_SPEC.equals(adTagDataSpec)) {
adTagDataSpec = checkNotNull(deprecatedAdTagDataSpec);
} }
adTagLoader =
new AdTagLoader(
context, configuration, imaFactory, supportedMimeTypes, adTagDataSpec, adViewGroup);
} }
/** /**
...@@ -579,12 +474,12 @@ public final class ImaAdsLoader ...@@ -579,12 +474,12 @@ public final class ImaAdsLoader
} }
@Override @Override
public void setAdTagDataSpec(DataSpec adTagDataSpec) { public void start(
this.adTagDataSpec = adTagDataSpec; AdsMediaSource adsMediaSource,
} DataSpec adTagDataSpec,
Object adsId,
@Override AdViewProvider adViewProvider,
public void start(EventListener eventListener, AdViewProvider adViewProvider) { EventListener eventListener) {
checkState( checkState(
wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player."); wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player.");
player = nextPlayer; player = nextPlayer;
......
...@@ -281,7 +281,20 @@ public class MediaItemTest { ...@@ -281,7 +281,20 @@ public class MediaItemTest {
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).setAdTagUri(adTagUri).build(); MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).setAdTagUri(adTagUri).build();
assertThat(mediaItem.playbackProperties.adTagUri).isEqualTo(adTagUri); assertThat(mediaItem.playbackProperties.adsConfiguration.adTagUri).isEqualTo(adTagUri);
assertThat(mediaItem.playbackProperties.adsConfiguration.adsId).isEqualTo(adTagUri);
}
@Test
public void builderSetAdTagUriAndAdsId_setsAdsConfiguration() {
Uri adTagUri = Uri.parse(URI_STRING + "/ad");
Object adsId = new Object();
MediaItem mediaItem =
new MediaItem.Builder().setUri(URI_STRING).setAdTagUri(adTagUri, adsId).build();
assertThat(mediaItem.playbackProperties.adsConfiguration.adTagUri).isEqualTo(adTagUri);
assertThat(mediaItem.playbackProperties.adsConfiguration.adsId).isEqualTo(adsId);
} }
@Test @Test
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -74,27 +73,28 @@ import java.util.List; ...@@ -74,27 +73,28 @@ import java.util.List;
* *
* <h3>Ad support for media items with ad tag URIs</h3> * <h3>Ad support for media items with ad tag URIs</h3>
* *
* <p>To support media items with {@link MediaItem.PlaybackProperties#adTagUri ad tag URIs}, {@link * <p>To support media items with {@link MediaItem.PlaybackProperties#adsConfiguration ads
* #setAdsLoaderProvider} and {@link #setAdViewProvider} need to be called to configure the factory * configuration}, {@link #setAdsLoaderProvider} and {@link #setAdViewProvider} need to be called to
* with the required providers. * configure the factory with the required providers.
*/ */
public final class DefaultMediaSourceFactory implements MediaSourceFactory { public final class DefaultMediaSourceFactory implements MediaSourceFactory {
/** /**
* Provides {@link AdsLoader} instances for media items that have {@link * Provides {@link AdsLoader} instances for media items that have {@link
* MediaItem.PlaybackProperties#adTagUri ad tag URIs}. * MediaItem.PlaybackProperties#adsConfiguration ad tag URIs}.
*/ */
public interface AdsLoaderProvider { public interface AdsLoaderProvider {
/** /**
* Returns an {@link AdsLoader} for the given {@link MediaItem.PlaybackProperties#adTagUri ad * Returns an {@link AdsLoader} for the given {@link
* tag URI}, or null if no ads loader is available for the given ad tag URI. * MediaItem.PlaybackProperties#adsConfiguration ads configuration}, or {@code null} if no ads
* loader is available for the given ads configuration.
* *
* <p>This method is called each time a {@link MediaSource} is created from a {@link MediaItem} * <p>This method is called each time a {@link MediaSource} is created from a {@link MediaItem}
* that defines an {@link MediaItem.PlaybackProperties#adTagUri ad tag URI}. * that defines an {@link MediaItem.PlaybackProperties#adsConfiguration ads configuration}.
*/ */
@Nullable @Nullable
AdsLoader getAdsLoader(Uri adTagUri); AdsLoader getAdsLoader(MediaItem.AdsConfiguration adsConfiguration);
} }
private static final String TAG = "DefaultMediaSourceFactory"; private static final String TAG = "DefaultMediaSourceFactory";
...@@ -171,7 +171,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { ...@@ -171,7 +171,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
/** /**
* Sets the {@link AdsLoaderProvider} that provides {@link AdsLoader} instances for media items * Sets the {@link AdsLoaderProvider} that provides {@link AdsLoader} instances for media items
* that have {@link MediaItem.PlaybackProperties#adTagUri ad tag URIs}. * that have {@link MediaItem.PlaybackProperties#adsConfiguration ads configurations}.
* *
* @param adsLoaderProvider A provider for {@link AdsLoader} instances. * @param adsLoaderProvider A provider for {@link AdsLoader} instances.
* @return This factory, for convenience. * @return This factory, for convenience.
...@@ -389,8 +389,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { ...@@ -389,8 +389,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private MediaSource maybeWrapWithAdsMediaSource(MediaItem mediaItem, MediaSource mediaSource) { private MediaSource maybeWrapWithAdsMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
Assertions.checkNotNull(mediaItem.playbackProperties); Assertions.checkNotNull(mediaItem.playbackProperties);
@Nullable Uri adTagUri = mediaItem.playbackProperties.adTagUri; @Nullable
if (adTagUri == null) { MediaItem.AdsConfiguration adsConfiguration = mediaItem.playbackProperties.adsConfiguration;
if (adsConfiguration == null) {
return mediaSource; return mediaSource;
} }
AdsLoaderProvider adsLoaderProvider = this.adsLoaderProvider; AdsLoaderProvider adsLoaderProvider = this.adsLoaderProvider;
...@@ -402,14 +403,15 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { ...@@ -402,14 +403,15 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
+ " setAdViewProvider."); + " setAdViewProvider.");
return mediaSource; return mediaSource;
} }
@Nullable AdsLoader adsLoader = adsLoaderProvider.getAdsLoader(adTagUri); @Nullable AdsLoader adsLoader = adsLoaderProvider.getAdsLoader(adsConfiguration);
if (adsLoader == null) { if (adsLoader == null) {
Log.w(TAG, "Playing media without ads. No AdsLoader for provided adTagUri"); Log.w(TAG, "Playing media without ads, as no AdsLoader was provided.");
return mediaSource; return mediaSource;
} }
return new AdsMediaSource( return new AdsMediaSource(
mediaSource, mediaSource,
new DataSpec(adTagUri), new DataSpec(adsConfiguration.adTagUri),
adsConfiguration.adsId,
/* adMediaSourceFactory= */ this, /* adMediaSourceFactory= */ this,
adsLoader, adsLoader,
adViewProvider); adViewProvider);
......
...@@ -38,16 +38,17 @@ import java.util.List; ...@@ -38,16 +38,17 @@ import java.util.List;
* with a new copy of the current {@link AdPlaybackState} whenever further information about ads * with a new copy of the current {@link AdPlaybackState} whenever further information about ads
* becomes known (for example, when an ad media URI is available, or an ad has played to the end). * becomes known (for example, when an ad media URI is available, or an ad has played to the end).
* *
* <p>{@link #start(EventListener, AdViewProvider)} will be called when the ads media source first * <p>{@link #start(AdsMediaSource, DataSpec, Object, AdViewProvider, EventListener)} will be called
* initializes, at which point the loader can request ads. If the player enters the background, * when an ads media source first initializes, at which point the loader can request ads. If the
* {@link #stop()} will be called. Loaders should maintain any ad playback state in preparation for * player enters the background, {@link #stop()} will be called. Loaders should maintain any ad
* a later call to {@link #start(EventListener, AdViewProvider)}. If an ad is playing when the * playback state in preparation for a later call to {@link #start(AdsMediaSource, DataSpec, Object,
* player is detached, update the ad playback state with the current playback position using {@link * AdViewProvider, EventListener)}. If an ad is playing when the player is detached, update the ad
* playback state with the current playback position using {@link
* AdPlaybackState#withAdResumePositionUs(long)}. * AdPlaybackState#withAdResumePositionUs(long)}.
* *
* <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the * <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the
* implementation of {@link #start(EventListener, AdViewProvider)} should invoke the same listener * implementation of {@link #start(AdsMediaSource, DataSpec, Object, AdViewProvider, EventListener)}
* to provide the existing playback state to the new player. * should invoke the same listener to provide the existing playback state to the new player.
*/ */
public interface AdsLoader { public interface AdsLoader {
...@@ -190,8 +191,8 @@ public interface AdsLoader { ...@@ -190,8 +191,8 @@ public interface AdsLoader {
/** /**
* Sets the supported content types for ad media. Must be called before the first call to {@link * Sets the supported content types for ad media. Must be called before the first call to {@link
* #start(EventListener, AdViewProvider)}. Subsequent calls may be ignored. Called on the main * #start(AdsMediaSource, DataSpec, Object, AdViewProvider, EventListener)}. Subsequent calls may
* thread by {@link AdsMediaSource}. * be ignored. Called on the main thread by {@link AdsMediaSource}.
* *
* @param contentTypes The supported content types for ad media. Each element must be one of * @param contentTypes The supported content types for ad media. Each element must be one of
* {@link C#TYPE_DASH}, {@link C#TYPE_HLS}, {@link C#TYPE_SS} and {@link C#TYPE_OTHER}. * {@link C#TYPE_DASH}, {@link C#TYPE_HLS}, {@link C#TYPE_SS} and {@link C#TYPE_OTHER}.
...@@ -199,20 +200,20 @@ public interface AdsLoader { ...@@ -199,20 +200,20 @@ public interface AdsLoader {
void setSupportedContentTypes(@C.ContentType int... contentTypes); void setSupportedContentTypes(@C.ContentType int... contentTypes);
/** /**
* Sets the data spec of the ad tag to load.
*
* @param adTagDataSpec The data spec of the ad tag to load. See the implementation's
* documentation for information about compatible ad tag formats.
*/
void setAdTagDataSpec(DataSpec adTagDataSpec);
/**
* Starts using the ads loader for playback. Called on the main thread by {@link AdsMediaSource}. * Starts using the ads loader for playback. Called on the main thread by {@link AdsMediaSource}.
* *
* @param eventListener Listener for ads loader events. * @param adsMediaSource The ads media source requesting to start loading ads.
* @param adTagDataSpec A data spec for the ad tag to load.
* @param adsId An opaque identifier for the ad playback state across start/stop calls.
* @param adViewProvider Provider of views for the ad UI. * @param adViewProvider Provider of views for the ad UI.
* @param eventListener Listener for ads loader events.
*/ */
void start(EventListener eventListener, AdViewProvider adViewProvider); void start(
AdsMediaSource adsMediaSource,
DataSpec adTagDataSpec,
Object adsId,
AdViewProvider adViewProvider,
EventListener eventListener);
/** /**
* Stops using the ads loader for playback and deregisters the event listener. Called on the main * Stops using the ads loader for playback and deregisters the event listener. Called on the main
......
...@@ -33,9 +33,7 @@ import com.google.android.exoplayer2.source.MediaSource; ...@@ -33,9 +33,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
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;
...@@ -128,7 +126,8 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -128,7 +126,8 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
private final MediaSourceFactory adMediaSourceFactory; private final MediaSourceFactory adMediaSourceFactory;
private final AdsLoader adsLoader; private final AdsLoader adsLoader;
private final AdsLoader.AdViewProvider adViewProvider; private final AdsLoader.AdViewProvider adViewProvider;
@Nullable private final DataSpec adTagDataSpec; private final DataSpec adTagDataSpec;
private final Object adsId;
private final Handler mainHandler; private final Handler mainHandler;
private final Timeline.Period period; private final Timeline.Period period;
...@@ -140,60 +139,14 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -140,60 +139,14 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
/** /**
* Constructs a new source that inserts ads linearly with the content specified by {@code * Constructs a new source that inserts ads linearly with the content specified by {@code
* contentMediaSource}. Ad media is loaded using {@link ProgressiveMediaSource}.
*
* @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param dataSourceFactory Factory for data sources used to load ad media.
* @param adsLoader The loader for ads.
* @param adViewProvider Provider of views for the ad UI.
* @deprecated Use {@link AdsMediaSource#AdsMediaSource(MediaSource, DataSpec, MediaSourceFactory,
* AdsLoader, AdsLoader.AdViewProvider)} instead.
*/
@Deprecated
public AdsMediaSource(
MediaSource contentMediaSource,
DataSource.Factory dataSourceFactory,
AdsLoader adsLoader,
AdsLoader.AdViewProvider adViewProvider) {
this(
contentMediaSource,
new ProgressiveMediaSource.Factory(dataSourceFactory),
adsLoader,
adViewProvider,
/* adTagDataSpec= */ null);
}
/**
* Constructs a new source that inserts ads linearly with the content specified by {@code
* contentMediaSource}.
*
* @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param adMediaSourceFactory Factory for media sources used to load ad media.
* @param adsLoader The loader for ads.
* @param adViewProvider Provider of views for the ad UI.
* @deprecated Use {@link AdsMediaSource#AdsMediaSource(MediaSource, DataSpec, MediaSourceFactory,
* AdsLoader, AdsLoader.AdViewProvider)} instead.
*/
@Deprecated
public AdsMediaSource(
MediaSource contentMediaSource,
MediaSourceFactory adMediaSourceFactory,
AdsLoader adsLoader,
AdsLoader.AdViewProvider adViewProvider) {
this(
contentMediaSource,
adMediaSourceFactory,
adsLoader,
adViewProvider,
/* adTagDataSpec= */ null);
}
/**
* Constructs a new source that inserts ads linearly with the content specified by {@code
* contentMediaSource}. * contentMediaSource}.
* *
* @param contentMediaSource The {@link MediaSource} providing the content to play. * @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param adTagDataSpec The data specification of the ad tag to load. * @param adTagDataSpec The data specification of the ad tag to load.
* @param adsId An opaque identifier for ad playback state associated with this instance. Ad
* loading and playback state is shared among all playlist items that have the same ads id (by
* {@link Object#equals(Object) equality}), so it is important to pass the same identifiers
* when constructing playlist items each time the player returns to the foreground.
* @param adMediaSourceFactory Factory for media sources used to load ad media. * @param adMediaSourceFactory Factory for media sources used to load ad media.
* @param adsLoader The loader for ads. * @param adsLoader The loader for ads.
* @param adViewProvider Provider of views for the ad UI. * @param adViewProvider Provider of views for the ad UI.
...@@ -201,23 +154,16 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -201,23 +154,16 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
public AdsMediaSource( public AdsMediaSource(
MediaSource contentMediaSource, MediaSource contentMediaSource,
DataSpec adTagDataSpec, DataSpec adTagDataSpec,
Object adsId,
MediaSourceFactory adMediaSourceFactory, MediaSourceFactory adMediaSourceFactory,
AdsLoader adsLoader, AdsLoader adsLoader,
AdsLoader.AdViewProvider adViewProvider) { AdsLoader.AdViewProvider adViewProvider) {
this(contentMediaSource, adMediaSourceFactory, adsLoader, adViewProvider, adTagDataSpec);
}
private AdsMediaSource(
MediaSource contentMediaSource,
MediaSourceFactory adMediaSourceFactory,
AdsLoader adsLoader,
AdsLoader.AdViewProvider adViewProvider,
@Nullable DataSpec adTagDataSpec) {
this.contentMediaSource = contentMediaSource; this.contentMediaSource = contentMediaSource;
this.adMediaSourceFactory = adMediaSourceFactory; this.adMediaSourceFactory = adMediaSourceFactory;
this.adsLoader = adsLoader; this.adsLoader = adsLoader;
this.adViewProvider = adViewProvider; this.adViewProvider = adViewProvider;
this.adTagDataSpec = adTagDataSpec; this.adTagDataSpec = adTagDataSpec;
this.adsId = adsId;
mainHandler = new Handler(Looper.getMainLooper()); mainHandler = new Handler(Looper.getMainLooper());
period = new Timeline.Period(); period = new Timeline.Period();
adMediaSourceHolders = new AdMediaSourceHolder[0][]; adMediaSourceHolders = new AdMediaSourceHolder[0][];
...@@ -247,12 +193,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -247,12 +193,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
this.componentListener = componentListener; this.componentListener = componentListener;
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource); prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource);
mainHandler.post( mainHandler.post(
() -> { () ->
if (adTagDataSpec != null) { adsLoader.start(
adsLoader.setAdTagDataSpec(adTagDataSpec); /* adsMediaSource= */ this,
} adTagDataSpec,
adsLoader.start(componentListener, adViewProvider); adsId,
}); adViewProvider,
componentListener));
} }
@Override @Override
......
...@@ -5570,6 +5570,7 @@ public final class ExoPlayerTest { ...@@ -5570,6 +5570,7 @@ public final class ExoPlayerTest {
new AdsMediaSource( new AdsMediaSource(
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)), new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
/* adTagDataSpec= */ new DataSpec(Uri.EMPTY), /* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
/* adsId= */ new Object(),
new DefaultMediaSourceFactory(context), new DefaultMediaSourceFactory(context),
new FakeAdsLoader(), new FakeAdsLoader(),
new FakeAdViewProvider()); new FakeAdViewProvider());
...@@ -5608,6 +5609,7 @@ public final class ExoPlayerTest { ...@@ -5608,6 +5609,7 @@ public final class ExoPlayerTest {
new AdsMediaSource( new AdsMediaSource(
mediaSource, mediaSource,
/* adTagDataSpec= */ new DataSpec(Uri.EMPTY), /* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
/* adsId= */ new Object(),
new DefaultMediaSourceFactory(context), new DefaultMediaSourceFactory(context),
new FakeAdsLoader(), new FakeAdsLoader(),
new FakeAdViewProvider()); new FakeAdViewProvider());
...@@ -5648,6 +5650,7 @@ public final class ExoPlayerTest { ...@@ -5648,6 +5650,7 @@ public final class ExoPlayerTest {
new AdsMediaSource( new AdsMediaSource(
mediaSource, mediaSource,
/* adTagDataSpec= */ new DataSpec(Uri.EMPTY), /* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
/* adsId= */ new Object(),
new DefaultMediaSourceFactory(context), new DefaultMediaSourceFactory(context),
new FakeAdsLoader(), new FakeAdsLoader(),
new FakeAdViewProvider()); new FakeAdViewProvider());
...@@ -9018,10 +9021,12 @@ public final class ExoPlayerTest { ...@@ -9018,10 +9021,12 @@ public final class ExoPlayerTest {
public void setSupportedContentTypes(int... contentTypes) {} public void setSupportedContentTypes(int... contentTypes) {}
@Override @Override
public void setAdTagDataSpec(DataSpec adTagDataSpec) {} public void start(
AdsMediaSource adsMediaSource,
@Override DataSpec adTagDataSpec,
public void start(AdsLoader.EventListener eventListener, AdViewProvider adViewProvider) {} Object adsId,
AdViewProvider adViewProvider,
AdsLoader.EventListener eventListener) {}
@Override @Override
public void stop() {} public void stop() {}
...@@ -9050,11 +9055,6 @@ public final class ExoPlayerTest { ...@@ -9050,11 +9055,6 @@ public final class ExoPlayerTest {
* Returns an argument matcher for {@link Timeline} instances that ignores period and window uids. * Returns an argument matcher for {@link Timeline} instances that ignores period and window uids.
*/ */
private static ArgumentMatcher<Timeline> noUid(Timeline timeline) { private static ArgumentMatcher<Timeline> noUid(Timeline timeline) {
return new ArgumentMatcher<Timeline>() { return argument -> new NoUidTimeline(timeline).equals(new NoUidTimeline(argument));
@Override
public boolean matches(Timeline argument) {
return new NoUidTimeline(timeline).equals(new NoUidTimeline(argument));
}
};
} }
} }
...@@ -204,7 +204,7 @@ public final class DefaultMediaSourceFactoryTest { ...@@ -204,7 +204,7 @@ public final class DefaultMediaSourceFactoryTest {
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_MEDIA).setAdTagUri(adTagUri).build(); MediaItem mediaItem = new MediaItem.Builder().setUri(URI_MEDIA).setAdTagUri(adTagUri).build();
DefaultMediaSourceFactory defaultMediaSourceFactory = DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()) new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext())
.setAdsLoaderProvider(ignoredAdTagUri -> mock(AdsLoader.class)) .setAdsLoaderProvider(ignoredAdsConfiguration -> mock(AdsLoader.class))
.setAdViewProvider(mock(AdsLoader.AdViewProvider.class)); .setAdViewProvider(mock(AdsLoader.AdViewProvider.class));
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem); MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
......
...@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider; ...@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider;
import com.google.android.exoplayer2.source.ads.AdsLoader.EventListener; import com.google.android.exoplayer2.source.ads.AdsLoader.EventListener;
import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSpec;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
...@@ -83,6 +84,9 @@ public final class AdsMediaSourceTest { ...@@ -83,6 +84,9 @@ public final class AdsMediaSourceTest {
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) .withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withAdResumePositionUs(/* adResumePositionUs= */ 0); .withAdResumePositionUs(/* adResumePositionUs= */ 0);
private static final DataSpec TEST_ADS_DATA_SPEC = new DataSpec(Uri.EMPTY);
private static final Object TEST_ADS_ID = new Object();
@Rule public final MockitoRule mockito = MockitoJUnit.rule(); @Rule public final MockitoRule mockito = MockitoJUnit.rule();
private FakeMediaSource contentMediaSource; private FakeMediaSource contentMediaSource;
...@@ -107,10 +111,21 @@ public final class AdsMediaSourceTest { ...@@ -107,10 +111,21 @@ public final class AdsMediaSourceTest {
ArgumentCaptor.forClass(AdsLoader.EventListener.class); ArgumentCaptor.forClass(AdsLoader.EventListener.class);
adsMediaSource = adsMediaSource =
new AdsMediaSource( new AdsMediaSource(
contentMediaSource, adMediaSourceFactory, mockAdsLoader, mockAdViewProvider); contentMediaSource,
TEST_ADS_DATA_SPEC,
TEST_ADS_ID,
adMediaSourceFactory,
mockAdsLoader,
mockAdViewProvider);
adsMediaSource.prepareSource(mockMediaSourceCaller, /* mediaTransferListener= */ null); adsMediaSource.prepareSource(mockMediaSourceCaller, /* mediaTransferListener= */ null);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mockAdsLoader).start(eventListenerArgumentCaptor.capture(), eq(mockAdViewProvider)); verify(mockAdsLoader)
.start(
eq(adsMediaSource),
eq(TEST_ADS_DATA_SPEC),
eq(TEST_ADS_ID),
eq(mockAdViewProvider),
eventListenerArgumentCaptor.capture());
// Simulate loading a preroll ad. // Simulate loading a preroll ad.
AdsLoader.EventListener adsLoaderEventListener = eventListenerArgumentCaptor.getValue(); AdsLoader.EventListener adsLoaderEventListener = eventListenerArgumentCaptor.getValue();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment