mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Pass ad tags via AdsMediaSource
This is in preparation for supporting playlists of ads media sources using ImaAdsLoader. Existing ways of passing ad tags should still function but are deprecated (and won't be supported with playlists). Issue: #3750 PiperOrigin-RevId: 335618364
This commit is contained in:
parent
6ed371aaf3
commit
39277ebe95
13 changed files with 289 additions and 45 deletions
|
|
@ -49,6 +49,11 @@
|
|||
([#7956](https://github.com/google/ExoPlayer/issues/7956)).
|
||||
* Allow apps to specify a `VideoAdPlayerCallback`
|
||||
([#7944](https://github.com/google/ExoPlayer/issues/7944)).
|
||||
* Accept ad tags via the `AdsMediaSource` constructor and deprecate
|
||||
passing them via the `ImaAdsLoader` constructor/builders. Passing the
|
||||
ad tag via media item playback properties continues to be supported.
|
||||
This is in preparation for supporting ads in playlists
|
||||
([#3750](https://github.com/google/ExoPlayer/issues/3750)).
|
||||
|
||||
### 2.12.0 (2020-09-11) ###
|
||||
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
}
|
||||
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
|
||||
if (adsLoader == null) {
|
||||
adsLoader = new ImaAdsLoader(/* context= */ PlayerActivity.this, adTagUri);
|
||||
adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
|
||||
}
|
||||
adsLoader.setPlayer(player);
|
||||
return adsLoader;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ext.ima;
|
|||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||
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.Util.castNonNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.content.Context;
|
||||
|
|
@ -92,6 +91,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
* #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling
|
||||
* {@link #release()}.
|
||||
*
|
||||
* <p>See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
|
||||
* information on compatible ad tag formats. 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
|
||||
* com.google.android.exoplayer2.source.ads.AdsMediaSource} (if using media sources directly). For
|
||||
* the latter case, please note that this implementation delegates loading of the data spec to the
|
||||
* IMA SDK, so range and headers specifications will be ignored in ad tag URIs. Literal ads
|
||||
* responses can be encoded as data scheme data specs, for example, by constructing the data spec
|
||||
* using a URI generated via {@link Util#getDataUriForString(String, String)}.
|
||||
*
|
||||
* <p>The IMA SDK can report obstructions to the ad view for accurate viewability measurement. This
|
||||
* means that any overlay views that obstruct the ad overlay but are essential for playback need to
|
||||
* be registered via the {@link AdViewProvider} passed to the {@link
|
||||
|
|
@ -331,7 +339,12 @@ public final class ImaAdsLoader
|
|||
* 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
|
||||
* com.google.android.exoplayer2.source.ads.AdsMediaSource} (if using media sources
|
||||
* directly).
|
||||
*/
|
||||
@Deprecated
|
||||
public ImaAdsLoader buildForAdTag(Uri adTagUri) {
|
||||
return new ImaAdsLoader(
|
||||
/* builder= */ this, /* adTagUri= */ adTagUri, /* adsResponse= */ null);
|
||||
|
|
@ -343,10 +356,21 @@ public final class ImaAdsLoader
|
|||
* @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
|
||||
* com.google.android.exoplayer2.source.ads.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(/* builder= */ this, /* adTagUri= */ null, adsResponse);
|
||||
}
|
||||
|
||||
/** Returns a new {@link ImaAdsLoader}. */
|
||||
public ImaAdsLoader build() {
|
||||
return new ImaAdsLoader(/* builder= */ this, /* adTagUri= */ null, /* adsResponse= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
|
@ -400,6 +424,8 @@ public final class ImaAdsLoader
|
|||
*/
|
||||
private static final int IMA_AD_STATE_PAUSED = 2;
|
||||
|
||||
private static final DataSpec EMPTY_AD_TAG_DATA_SPEC = new DataSpec(Uri.EMPTY);
|
||||
|
||||
private final Context context;
|
||||
@Nullable private final Uri adTagUri;
|
||||
@Nullable private final String adsResponse;
|
||||
|
|
@ -430,6 +456,7 @@ public final class ImaAdsLoader
|
|||
private List<String> supportedMimeTypes;
|
||||
@Nullable private EventListener eventListener;
|
||||
@Nullable private Player player;
|
||||
private DataSpec adTagDataSpec;
|
||||
private VideoProgressUpdate lastContentProgress;
|
||||
private VideoProgressUpdate lastAdProgress;
|
||||
private int lastVolumePercent;
|
||||
|
|
@ -505,14 +532,18 @@ public final class ImaAdsLoader
|
|||
* @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 com.google.android.exoplayer2.source.ads.AdsMediaSource} (if using
|
||||
* media sources directly).
|
||||
*/
|
||||
@Deprecated
|
||||
public ImaAdsLoader(Context context, Uri adTagUri) {
|
||||
this(new Builder(context), adTagUri, /* adsResponse= */ null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"nullness:argument.type.incompatible", "methodref.receiver.bound.invalid"})
|
||||
private ImaAdsLoader(Builder builder, @Nullable Uri adTagUri, @Nullable String adsResponse) {
|
||||
checkArgument(adTagUri != null || adsResponse != null);
|
||||
this.context = builder.context.getApplicationContext();
|
||||
this.adTagUri = adTagUri;
|
||||
this.adsResponse = adsResponse;
|
||||
|
|
@ -547,6 +578,7 @@ public final class ImaAdsLoader
|
|||
updateAdProgressRunnable = this::updateAdProgress;
|
||||
adInfoByAdMediaInfo = HashBiMap.create();
|
||||
supportedMimeTypes = Collections.emptyList();
|
||||
adTagDataSpec = EMPTY_AD_TAG_DATA_SPEC;
|
||||
lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||
|
|
@ -592,12 +624,62 @@ public final class ImaAdsLoader
|
|||
*
|
||||
* @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
|
||||
* information about compatible ad tag formats.
|
||||
* @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code
|
||||
* null} if playing audio-only ads.
|
||||
*/
|
||||
public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) {
|
||||
if (hasAdPlaybackState || adsManager != null || pendingAdRequestContext != null) {
|
||||
// Ads have already been requested.
|
||||
return;
|
||||
}
|
||||
|
||||
if (EMPTY_AD_TAG_DATA_SPEC.equals(adTagDataSpec)) {
|
||||
// Handle deprecated ways of specifying the ad tag.
|
||||
if (adTagUri != null) {
|
||||
adTagDataSpec = new DataSpec(adTagUri);
|
||||
} else if (adsResponse != null) {
|
||||
adTagDataSpec = new DataSpec(Util.getDataUriForString(adsResponse, "text/xml"));
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
AdsRequest request;
|
||||
try {
|
||||
request = ImaUtil.getAdsRequestForAdTagDataSpec(imaFactory, adTagDataSpec);
|
||||
} catch (IOException e) {
|
||||
hasAdPlaybackState = true;
|
||||
updateAdPlaybackState();
|
||||
pendingAdLoadError = AdLoadException.createForAllAds(e);
|
||||
maybeNotifyPendingAdLoadError();
|
||||
return;
|
||||
}
|
||||
this.adTagDataSpec = adTagDataSpec;
|
||||
pendingAdRequestContext = new Object();
|
||||
request.setUserRequestContext(pendingAdRequestContext);
|
||||
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
|
||||
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
||||
}
|
||||
request.setContentProgressProvider(componentListener);
|
||||
|
||||
if (adViewGroup != null) {
|
||||
adDisplayContainer =
|
||||
imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener);
|
||||
|
|
@ -608,24 +690,13 @@ public final class ImaAdsLoader
|
|||
if (companionAdSlots != null) {
|
||||
adDisplayContainer.setCompanionSlots(companionAdSlots);
|
||||
}
|
||||
|
||||
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||
adsLoader.addAdErrorListener(componentListener);
|
||||
if (adErrorListener != null) {
|
||||
adsLoader.addAdErrorListener(adErrorListener);
|
||||
}
|
||||
adsLoader.addAdsLoadedListener(componentListener);
|
||||
AdsRequest request = imaFactory.createAdsRequest();
|
||||
if (adTagUri != null) {
|
||||
request.setAdTagUrl(adTagUri.toString());
|
||||
} else {
|
||||
request.setAdsResponse(castNonNull(adsResponse));
|
||||
}
|
||||
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
|
||||
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
||||
}
|
||||
request.setContentProgressProvider(componentListener);
|
||||
pendingAdRequestContext = new Object();
|
||||
request.setUserRequestContext(pendingAdRequestContext);
|
||||
adsLoader.requestAds(request);
|
||||
}
|
||||
|
||||
|
|
@ -674,6 +745,11 @@ public final class ImaAdsLoader
|
|||
this.supportedMimeTypes = Collections.unmodifiableList(supportedMimeTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdTagDataSpec(DataSpec adTagDataSpec) {
|
||||
this.adTagDataSpec = adTagDataSpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(EventListener eventListener, AdViewProvider adViewProvider) {
|
||||
checkState(
|
||||
|
|
@ -700,7 +776,7 @@ public final class ImaAdsLoader
|
|||
updateAdPlaybackState();
|
||||
} else {
|
||||
// Ads haven't loaded yet, so request them.
|
||||
requestAds(adViewProvider.getAdViewGroup());
|
||||
requestAds(adTagDataSpec, adViewProvider.getAdViewGroup());
|
||||
}
|
||||
if (adDisplayContainer != null) {
|
||||
for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
|
||||
|
|
@ -1431,7 +1507,7 @@ public final class ImaAdsLoader
|
|||
|
||||
private void maybeNotifyPendingAdLoadError() {
|
||||
if (pendingAdLoadError != null && eventListener != null) {
|
||||
eventListener.onAdLoadError(pendingAdLoadError, getAdsDataSpec(adTagUri));
|
||||
eventListener.onAdLoadError(pendingAdLoadError, adTagDataSpec);
|
||||
pendingAdLoadError = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1446,8 +1522,7 @@ public final class ImaAdsLoader
|
|||
updateAdPlaybackState();
|
||||
if (eventListener != null) {
|
||||
eventListener.onAdLoadError(
|
||||
AdLoadException.createForUnexpected(new RuntimeException(message, cause)),
|
||||
getAdsDataSpec(adTagUri));
|
||||
AdLoadException.createForUnexpected(new RuntimeException(message, cause)), adTagDataSpec);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1500,10 +1575,6 @@ public final class ImaAdsLoader
|
|||
return "AdMediaInfo[" + adMediaInfo.getUrl() + (adInfo != null ? ", " + adInfo : "") + "]";
|
||||
}
|
||||
|
||||
private static DataSpec getAdsDataSpec(@Nullable Uri adTagUri) {
|
||||
return new DataSpec(adTagUri != null ? adTagUri : Uri.EMPTY);
|
||||
}
|
||||
|
||||
private static long getContentPeriodPositionMs(
|
||||
Player player, Timeline timeline, Timeline.Period period) {
|
||||
long contentWindowPositionMs = player.getContentPosition();
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
|
|||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo;
|
||||
import com.google.android.exoplayer2.upstream.DataSchemeDataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -116,6 +120,24 @@ import java.util.List;
|
|||
return new AdPlaybackState(adGroupTimesUs);
|
||||
}
|
||||
|
||||
/** Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. */
|
||||
public static AdsRequest getAdsRequestForAdTagDataSpec(
|
||||
ImaFactory imaFactory, DataSpec adTagDataSpec) throws IOException {
|
||||
AdsRequest request = imaFactory.createAdsRequest();
|
||||
if (DataSchemeDataSource.SCHEME_DATA.equals(adTagDataSpec.uri.getScheme())) {
|
||||
DataSchemeDataSource dataSchemeDataSource = new DataSchemeDataSource();
|
||||
try {
|
||||
dataSchemeDataSource.open(adTagDataSpec);
|
||||
request.setAdsResponse(Util.fromUtf8Bytes(Util.readToEnd(dataSchemeDataSource)));
|
||||
} finally {
|
||||
dataSchemeDataSource.close();
|
||||
}
|
||||
} else {
|
||||
request.setAdTagUrl(adTagDataSpec.uri.toString());
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/** Returns whether the ad error indicates that an entire ad group failed to load. */
|
||||
public static boolean isAdGroupLoadError(AdError adError) {
|
||||
// TODO: Find out what other errors need to be handled (if any), and whether each one relates to
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline;
|
|||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.IOException;
|
||||
|
|
@ -97,8 +98,9 @@ public final class ImaAdsLoaderTest {
|
|||
/* isSeekable= */ true, /* isDynamic= */ false, CONTENT_DURATION_US));
|
||||
private static final long CONTENT_PERIOD_DURATION_US =
|
||||
CONTENT_TIMELINE.getPeriod(/* periodIndex= */ 0, new Period()).durationUs;
|
||||
private static final Uri TEST_URI = Uri.EMPTY;
|
||||
private static final AdMediaInfo TEST_AD_MEDIA_INFO = new AdMediaInfo(TEST_URI.toString());
|
||||
private static final Uri TEST_URI = Uri.parse("https://www.google.com");
|
||||
private static final DataSpec TEST_DATA_SPEC = new DataSpec(TEST_URI);
|
||||
private static final AdMediaInfo TEST_AD_MEDIA_INFO = new AdMediaInfo("https://www.google.com");
|
||||
private static final long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND;
|
||||
private static final ImmutableList<Float> PREROLL_CUE_POINTS_SECONDS = ImmutableList.of(0f);
|
||||
|
||||
|
|
@ -285,7 +287,7 @@ public final class ImaAdsLoaderTest {
|
|||
new AdPlaybackState(/* adGroupTimesUs...= */ 0)
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* uri= */ TEST_URI)
|
||||
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI)
|
||||
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
|
||||
|
|
@ -550,7 +552,8 @@ public final class ImaAdsLoaderTest {
|
|||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
|
||||
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
|
@ -582,7 +585,8 @@ public final class ImaAdsLoaderTest {
|
|||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
|
||||
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs));
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
|
@ -614,7 +618,8 @@ public final class ImaAdsLoaderTest {
|
|||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
|
||||
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
|
@ -650,7 +655,8 @@ public final class ImaAdsLoaderTest {
|
|||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
|
||||
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
|
@ -689,7 +695,8 @@ public final class ImaAdsLoaderTest {
|
|||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
|
||||
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs));
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
|
@ -707,11 +714,51 @@ public final class ImaAdsLoaderTest {
|
|||
.withSkippedAdGroup(/* adGroupIndex= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAdTagWithDataScheme_requestsWithAdsResponse() throws Exception {
|
||||
String adsResponse =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<VAST xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\""
|
||||
+ " xsi:noNamespaceSchemaLocation=\"vast.xsd\" version=\"2.0\">\n"
|
||||
+ " <Ad id=\"17180293\">\n"
|
||||
+ " <InLine></InLine>\n"
|
||||
+ " </Ad>\n"
|
||||
+ "</VAST>";
|
||||
DataSpec adDataSpec = new DataSpec(Util.getDataUriForString("text/xml", adsResponse));
|
||||
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
ImmutableList.of(0f),
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.build(),
|
||||
adDataSpec);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
verify(mockAdsRequest).setAdsResponse(adsResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAdTagWithUri_requestsWithAdTagUrl() throws Exception {
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
ImmutableList.of(0f),
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
verify(mockAdsRequest).setAdTagUrl(TEST_DATA_SPEC.uri.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stop_unregistersAllVideoControlOverlays() {
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.requestAds(TEST_DATA_SPEC, adViewGroup);
|
||||
imaAdsLoader.stop();
|
||||
|
||||
InOrder inOrder = inOrder(mockAdDisplayContainer);
|
||||
|
|
@ -775,16 +822,21 @@ public final class ImaAdsLoaderTest {
|
|||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
.build(),
|
||||
TEST_DATA_SPEC);
|
||||
}
|
||||
|
||||
private void setupPlayback(
|
||||
Timeline contentTimeline, List<Float> cuePoints, ImaAdsLoader imaAdsLoader) {
|
||||
Timeline contentTimeline,
|
||||
List<Float> cuePoints,
|
||||
ImaAdsLoader imaAdsLoader,
|
||||
DataSpec adTagDataSpec) {
|
||||
fakeExoPlayer = new FakePlayer();
|
||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
|
||||
this.imaAdsLoader = imaAdsLoader;
|
||||
imaAdsLoader.setPlayer(fakeExoPlayer);
|
||||
imaAdsLoader.setAdTagDataSpec(adTagDataSpec);
|
||||
}
|
||||
|
||||
private void setupMocks() {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import android.os.SystemClock;
|
|||
import android.security.NetworkSecurityPolicy;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.WindowManager;
|
||||
|
|
@ -2007,6 +2008,14 @@ public final class Util {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
/** Returns a data URI with the specified MIME type and data. */
|
||||
public static Uri getDataUriForString(String mimeType, String data) {
|
||||
// TODO(internal: b/169937045): For now we don't pass the URL_SAFE flag as DataSchemeDataSource
|
||||
// doesn't decode using it.
|
||||
return Uri.parse(
|
||||
"data:" + mimeType + ";base64," + Base64.encodeToString(data.getBytes(), Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
/**
|
||||
* A hacky method that always throws {@code t} even if {@code t} is a checked exception,
|
||||
* and is not declared to be thrown.
|
||||
|
|
|
|||
|
|
@ -876,6 +876,14 @@ public class UtilTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDataUriForString_returnsCorrectDataUri() {
|
||||
assertThat(
|
||||
Util.getDataUriForString(/* mimeType= */ "text/plain", "Some Data!<>:\"/\\|?*%")
|
||||
.toString())
|
||||
.isEqualTo("data:text/plain;base64,U29tZSBEYXRhITw+OiIvXHw/KiU=");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void crc32_returnsUpdatedCrc32() {
|
||||
byte[] bytes = {0x5F, 0x78, 0x04, 0x7B, 0x5F};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.google.android.exoplayer2.source.ads.AdsLoader;
|
|||
import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
|
|
@ -280,7 +281,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||
|
||||
private MediaSource maybeWrapWithAdsMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
|
||||
Assertions.checkNotNull(mediaItem.playbackProperties);
|
||||
if (mediaItem.playbackProperties.adTagUri == null) {
|
||||
@Nullable Uri adTagUri = mediaItem.playbackProperties.adTagUri;
|
||||
if (adTagUri == null) {
|
||||
return mediaSource;
|
||||
}
|
||||
AdsLoaderProvider adsLoaderProvider = this.adsLoaderProvider;
|
||||
|
|
@ -292,14 +294,17 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||
+ " setAdViewProvider.");
|
||||
return mediaSource;
|
||||
}
|
||||
@Nullable
|
||||
AdsLoader adsLoader = adsLoaderProvider.getAdsLoader(mediaItem.playbackProperties.adTagUri);
|
||||
@Nullable AdsLoader adsLoader = adsLoaderProvider.getAdsLoader(adTagUri);
|
||||
if (adsLoader == null) {
|
||||
Log.w(TAG, "Playing media without ads. No AdsLoader for provided adTagUri");
|
||||
return mediaSource;
|
||||
}
|
||||
return new AdsMediaSource(
|
||||
mediaSource, /* adMediaSourceFactory= */ this, adsLoader, adViewProvider);
|
||||
mediaSource,
|
||||
new DataSpec(adTagUri),
|
||||
/* adMediaSourceFactory= */ this,
|
||||
adsLoader,
|
||||
adViewProvider);
|
||||
}
|
||||
|
||||
private static SparseArray<MediaSourceFactory> loadDelegates(
|
||||
|
|
|
|||
|
|
@ -198,6 +198,14 @@ public interface AdsLoader {
|
|||
*/
|
||||
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}.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
private final MediaSourceFactory adMediaSourceFactory;
|
||||
private final AdsLoader adsLoader;
|
||||
private final AdsLoader.AdViewProvider adViewProvider;
|
||||
@Nullable private final DataSpec adTagDataSpec;
|
||||
private final Handler mainHandler;
|
||||
private final Timeline.Period period;
|
||||
|
||||
|
|
@ -145,7 +146,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
* @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,
|
||||
|
|
@ -155,7 +159,8 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
contentMediaSource,
|
||||
new ProgressiveMediaSource.Factory(dataSourceFactory),
|
||||
adsLoader,
|
||||
adViewProvider);
|
||||
adViewProvider,
|
||||
/* adTagDataSpec= */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,16 +171,53 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
* @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}.
|
||||
*
|
||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
||||
* @param adTagDataSpec The data specification of the ad tag to load.
|
||||
* @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.
|
||||
*/
|
||||
public AdsMediaSource(
|
||||
MediaSource contentMediaSource,
|
||||
DataSpec adTagDataSpec,
|
||||
MediaSourceFactory adMediaSourceFactory,
|
||||
AdsLoader adsLoader,
|
||||
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.adMediaSourceFactory = adMediaSourceFactory;
|
||||
this.adsLoader = adsLoader;
|
||||
this.adViewProvider = adViewProvider;
|
||||
this.adTagDataSpec = adTagDataSpec;
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
period = new Timeline.Period();
|
||||
adMediaSourceHolders = new AdMediaSourceHolder[0][];
|
||||
|
|
@ -204,7 +246,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
ComponentListener componentListener = new ComponentListener();
|
||||
this.componentListener = componentListener;
|
||||
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource);
|
||||
mainHandler.post(() -> adsLoader.start(componentListener, adViewProvider));
|
||||
mainHandler.post(
|
||||
() -> {
|
||||
if (adTagDataSpec != null) {
|
||||
adsLoader.setAdTagDataSpec(adTagDataSpec);
|
||||
}
|
||||
adsLoader.start(componentListener, adViewProvider);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ public final class DataSchemeDataSource extends BaseDataSource {
|
|||
String dataString = uriParts[1];
|
||||
if (uriParts[0].contains(";base64")) {
|
||||
try {
|
||||
data = Base64.decode(dataString, 0);
|
||||
// TODO(internal: b/169937045): Consider passing Base64.URL_SAFE flag.
|
||||
data = Base64.decode(dataString, /* flags= */ Base64.DEFAULT);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ParserException("Error while parsing Base64 encoded string: " + dataString, e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
|||
import com.google.android.exoplayer2.source.ClippingMediaSource;
|
||||
import com.google.android.exoplayer2.source.CompositeMediaSource;
|
||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
||||
import com.google.android.exoplayer2.source.LoopingMediaSource;
|
||||
import com.google.android.exoplayer2.source.MaskingMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
|
|
@ -101,7 +102,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.Allocation;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.Loader;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
|
@ -5556,7 +5557,8 @@ public final class ExoPlayerTest {
|
|||
AdsMediaSource adsMediaSource =
|
||||
new AdsMediaSource(
|
||||
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
|
||||
new DefaultDataSourceFactory(context),
|
||||
/* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
|
||||
new DefaultMediaSourceFactory(context),
|
||||
new FakeAdsLoader(),
|
||||
new FakeAdViewProvider());
|
||||
Exception[] exception = {null};
|
||||
|
|
@ -5593,7 +5595,8 @@ public final class ExoPlayerTest {
|
|||
AdsMediaSource adsMediaSource =
|
||||
new AdsMediaSource(
|
||||
mediaSource,
|
||||
new DefaultDataSourceFactory(context),
|
||||
/* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
|
||||
new DefaultMediaSourceFactory(context),
|
||||
new FakeAdsLoader(),
|
||||
new FakeAdViewProvider());
|
||||
final Exception[] exception = {null};
|
||||
|
|
@ -5632,7 +5635,8 @@ public final class ExoPlayerTest {
|
|||
AdsMediaSource adsMediaSource =
|
||||
new AdsMediaSource(
|
||||
mediaSource,
|
||||
new DefaultDataSourceFactory(context),
|
||||
/* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
|
||||
new DefaultMediaSourceFactory(context),
|
||||
new FakeAdsLoader(),
|
||||
new FakeAdViewProvider());
|
||||
final Exception[] exception = {null};
|
||||
|
|
@ -8539,6 +8543,9 @@ public final class ExoPlayerTest {
|
|||
@Override
|
||||
public void setSupportedContentTypes(int... contentTypes) {}
|
||||
|
||||
@Override
|
||||
public void setAdTagDataSpec(DataSpec adTagDataSpec) {}
|
||||
|
||||
@Override
|
||||
public void start(AdsLoader.EventListener eventListener, AdViewProvider adViewProvider) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,14 @@ public final class DataSchemeDataSourceTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readSourceToEnd_readsEncodedString() throws Exception {
|
||||
String data = "Some Data!<>:\"/\\|?*%";
|
||||
schemeDataDataSource.open(new DataSpec(Util.getDataUriForString("text/plain", data)));
|
||||
|
||||
assertThat(Util.fromUtf8Bytes(Util.readToEnd(schemeDataDataSource))).isEqualTo(data);
|
||||
}
|
||||
|
||||
private static DataSpec buildDataSpec(String uriString) {
|
||||
return buildDataSpec(uriString, /* position= */ 0, /* length= */ C.LENGTH_UNSET);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue