Commit 39277ebe by andrewlewis Committed by kim-vde

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
parent 6ed371aa
...@@ -49,6 +49,11 @@ ...@@ -49,6 +49,11 @@
([#7956](https://github.com/google/ExoPlayer/issues/7956)). ([#7956](https://github.com/google/ExoPlayer/issues/7956)).
* Allow apps to specify a `VideoAdPlayerCallback` * Allow apps to specify a `VideoAdPlayerCallback`
([#7944](https://github.com/google/ExoPlayer/issues/7944)). ([#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) ### ### 2.12.0 (2020-09-11) ###
......
...@@ -375,7 +375,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -375,7 +375,7 @@ public class PlayerActivity extends AppCompatActivity
} }
// 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(/* context= */ PlayerActivity.this, adTagUri); adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
} }
adsLoader.setPlayer(player); adsLoader.setPlayer(player);
return adsLoader; return adsLoader;
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ext.ima; ...@@ -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.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; 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 static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import android.content.Context; import android.content.Context;
...@@ -92,6 +91,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -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 * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling
* {@link #release()}. * {@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 * <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 * 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 * be registered via the {@link AdViewProvider} passed to the {@link
...@@ -331,7 +339,12 @@ public final class ImaAdsLoader ...@@ -331,7 +339,12 @@ public final class ImaAdsLoader
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* information on compatible ad tags. * information on compatible ad tags.
* @return The new {@link ImaAdsLoader}. * @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) { public ImaAdsLoader buildForAdTag(Uri adTagUri) {
return new ImaAdsLoader( return new ImaAdsLoader(
/* builder= */ this, /* adTagUri= */ adTagUri, /* adsResponse= */ null); /* builder= */ this, /* adTagUri= */ adTagUri, /* adsResponse= */ null);
...@@ -343,10 +356,21 @@ public final class ImaAdsLoader ...@@ -343,10 +356,21 @@ public final class ImaAdsLoader
* @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of * @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of
* making a request via an ad tag URL. * making a request via an ad tag URL.
* @return The new {@link ImaAdsLoader}. * @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) { public ImaAdsLoader buildForAdsResponse(String adsResponse) {
return new ImaAdsLoader(/* builder= */ this, /* adTagUri= */ null, 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; private static final boolean DEBUG = false;
...@@ -400,6 +424,8 @@ public final class ImaAdsLoader ...@@ -400,6 +424,8 @@ public final class ImaAdsLoader
*/ */
private static final int IMA_AD_STATE_PAUSED = 2; 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; private final Context context;
@Nullable private final Uri adTagUri; @Nullable private final Uri adTagUri;
@Nullable private final String adsResponse; @Nullable private final String adsResponse;
...@@ -430,6 +456,7 @@ public final class ImaAdsLoader ...@@ -430,6 +456,7 @@ public final class ImaAdsLoader
private List<String> supportedMimeTypes; private List<String> supportedMimeTypes;
@Nullable private EventListener eventListener; @Nullable private EventListener eventListener;
@Nullable private Player player; @Nullable private Player player;
private DataSpec adTagDataSpec;
private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastContentProgress;
private VideoProgressUpdate lastAdProgress; private VideoProgressUpdate lastAdProgress;
private int lastVolumePercent; private int lastVolumePercent;
...@@ -505,14 +532,18 @@ public final class ImaAdsLoader ...@@ -505,14 +532,18 @@ public final class ImaAdsLoader
* @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See * @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 * https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* more information. * 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) { public ImaAdsLoader(Context context, Uri adTagUri) {
this(new Builder(context), adTagUri, /* adsResponse= */ null); this(new Builder(context), adTagUri, /* adsResponse= */ null);
} }
@SuppressWarnings({"nullness:argument.type.incompatible", "methodref.receiver.bound.invalid"}) @SuppressWarnings({"nullness:argument.type.incompatible", "methodref.receiver.bound.invalid"})
private ImaAdsLoader(Builder builder, @Nullable Uri adTagUri, @Nullable String adsResponse) { private ImaAdsLoader(Builder builder, @Nullable Uri adTagUri, @Nullable String adsResponse) {
checkArgument(adTagUri != null || adsResponse != null);
this.context = builder.context.getApplicationContext(); this.context = builder.context.getApplicationContext();
this.adTagUri = adTagUri; this.adTagUri = adTagUri;
this.adsResponse = adsResponse; this.adsResponse = adsResponse;
...@@ -547,6 +578,7 @@ public final class ImaAdsLoader ...@@ -547,6 +578,7 @@ public final class ImaAdsLoader
updateAdProgressRunnable = this::updateAdProgress; updateAdProgressRunnable = this::updateAdProgress;
adInfoByAdMediaInfo = HashBiMap.create(); adInfoByAdMediaInfo = HashBiMap.create();
supportedMimeTypes = Collections.emptyList(); supportedMimeTypes = Collections.emptyList();
adTagDataSpec = EMPTY_AD_TAG_DATA_SPEC;
lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
...@@ -592,12 +624,62 @@ public final class ImaAdsLoader ...@@ -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 * @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.
* @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) { 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) { if (hasAdPlaybackState || adsManager != null || pendingAdRequestContext != null) {
// Ads have already been requested. // Ads have already been requested.
return; 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) { if (adViewGroup != null) {
adDisplayContainer = adDisplayContainer =
imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener); imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener);
...@@ -608,24 +690,13 @@ public final class ImaAdsLoader ...@@ -608,24 +690,13 @@ public final class ImaAdsLoader
if (companionAdSlots != null) { if (companionAdSlots != null) {
adDisplayContainer.setCompanionSlots(companionAdSlots); adDisplayContainer.setCompanionSlots(companionAdSlots);
} }
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer); adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
adsLoader.addAdErrorListener(componentListener); adsLoader.addAdErrorListener(componentListener);
if (adErrorListener != null) { if (adErrorListener != null) {
adsLoader.addAdErrorListener(adErrorListener); adsLoader.addAdErrorListener(adErrorListener);
} }
adsLoader.addAdsLoadedListener(componentListener); 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); adsLoader.requestAds(request);
} }
...@@ -675,6 +746,11 @@ public final class ImaAdsLoader ...@@ -675,6 +746,11 @@ public final class ImaAdsLoader
} }
@Override @Override
public void setAdTagDataSpec(DataSpec adTagDataSpec) {
this.adTagDataSpec = adTagDataSpec;
}
@Override
public void start(EventListener eventListener, AdViewProvider adViewProvider) { public void start(EventListener eventListener, AdViewProvider adViewProvider) {
checkState( checkState(
wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player."); wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player.");
...@@ -700,7 +776,7 @@ public final class ImaAdsLoader ...@@ -700,7 +776,7 @@ public final class ImaAdsLoader
updateAdPlaybackState(); updateAdPlaybackState();
} else { } else {
// Ads haven't loaded yet, so request them. // Ads haven't loaded yet, so request them.
requestAds(adViewProvider.getAdViewGroup()); requestAds(adTagDataSpec, adViewProvider.getAdViewGroup());
} }
if (adDisplayContainer != null) { if (adDisplayContainer != null) {
for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
...@@ -1431,7 +1507,7 @@ public final class ImaAdsLoader ...@@ -1431,7 +1507,7 @@ public final class ImaAdsLoader
private void maybeNotifyPendingAdLoadError() { private void maybeNotifyPendingAdLoadError() {
if (pendingAdLoadError != null && eventListener != null) { if (pendingAdLoadError != null && eventListener != null) {
eventListener.onAdLoadError(pendingAdLoadError, getAdsDataSpec(adTagUri)); eventListener.onAdLoadError(pendingAdLoadError, adTagDataSpec);
pendingAdLoadError = null; pendingAdLoadError = null;
} }
} }
...@@ -1446,8 +1522,7 @@ public final class ImaAdsLoader ...@@ -1446,8 +1522,7 @@ public final class ImaAdsLoader
updateAdPlaybackState(); updateAdPlaybackState();
if (eventListener != null) { if (eventListener != null) {
eventListener.onAdLoadError( eventListener.onAdLoadError(
AdLoadException.createForUnexpected(new RuntimeException(message, cause)), AdLoadException.createForUnexpected(new RuntimeException(message, cause)), adTagDataSpec);
getAdsDataSpec(adTagUri));
} }
} }
...@@ -1500,10 +1575,6 @@ public final class ImaAdsLoader ...@@ -1500,10 +1575,6 @@ public final class ImaAdsLoader
return "AdMediaInfo[" + adMediaInfo.getUrl() + (adInfo != null ? ", " + adInfo : "") + "]"; 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( private static long getContentPeriodPositionMs(
Player player, Timeline timeline, Timeline.Period period) { Player player, Timeline timeline, Timeline.Period period) {
long contentWindowPositionMs = player.getContentPosition(); long contentWindowPositionMs = player.getContentPosition();
......
...@@ -32,6 +32,10 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; ...@@ -32,6 +32,10 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo; 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.Arrays;
import java.util.List; import java.util.List;
...@@ -116,6 +120,24 @@ import java.util.List; ...@@ -116,6 +120,24 @@ import java.util.List;
return new AdPlaybackState(adGroupTimesUs); 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. */ /** Returns whether the ad error indicates that an entire ad group failed to load. */
public static boolean isAdGroupLoadError(AdError adError) { public static boolean isAdGroupLoadError(AdError adError) {
// TODO: Find out what other errors need to be handled (if any), and whether each one relates to // 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; ...@@ -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;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.upstream.DataSpec; 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.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.IOException; import java.io.IOException;
...@@ -97,8 +98,9 @@ public final class ImaAdsLoaderTest { ...@@ -97,8 +98,9 @@ public final class ImaAdsLoaderTest {
/* isSeekable= */ true, /* isDynamic= */ false, CONTENT_DURATION_US)); /* isSeekable= */ true, /* isDynamic= */ false, CONTENT_DURATION_US));
private static final long CONTENT_PERIOD_DURATION_US = private static final long CONTENT_PERIOD_DURATION_US =
CONTENT_TIMELINE.getPeriod(/* periodIndex= */ 0, new Period()).durationUs; CONTENT_TIMELINE.getPeriod(/* periodIndex= */ 0, new Period()).durationUs;
private static final Uri TEST_URI = Uri.EMPTY; private static final Uri TEST_URI = Uri.parse("https://www.google.com");
private static final AdMediaInfo TEST_AD_MEDIA_INFO = new AdMediaInfo(TEST_URI.toString()); 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 long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND;
private static final ImmutableList<Float> PREROLL_CUE_POINTS_SECONDS = ImmutableList.of(0f); private static final ImmutableList<Float> PREROLL_CUE_POINTS_SECONDS = ImmutableList.of(0f);
...@@ -285,7 +287,7 @@ public final class ImaAdsLoaderTest { ...@@ -285,7 +287,7 @@ public final class ImaAdsLoaderTest {
new AdPlaybackState(/* adGroupTimesUs...= */ 0) new AdPlaybackState(/* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US) .withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .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}}) .withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0) .withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withAdResumePositionUs(/* adResumePositionUs= */ 0)); .withAdResumePositionUs(/* adResumePositionUs= */ 0));
...@@ -550,7 +552,8 @@ public final class ImaAdsLoaderTest { ...@@ -550,7 +552,8 @@ public final class ImaAdsLoaderTest {
.setPlayAdBeforeStartPosition(false) .setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory) .setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings) .setImaSdkSettings(mockImaSdkSettings)
.buildForAdTag(TEST_URI)); .build(),
TEST_DATA_SPEC);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000); fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000);
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
...@@ -582,7 +585,8 @@ public final class ImaAdsLoaderTest { ...@@ -582,7 +585,8 @@ public final class ImaAdsLoaderTest {
.setPlayAdBeforeStartPosition(false) .setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory) .setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings) .setImaSdkSettings(mockImaSdkSettings)
.buildForAdTag(TEST_URI)); .build(),
TEST_DATA_SPEC);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs)); fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs));
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
...@@ -614,7 +618,8 @@ public final class ImaAdsLoaderTest { ...@@ -614,7 +618,8 @@ public final class ImaAdsLoaderTest {
.setPlayAdBeforeStartPosition(false) .setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory) .setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings) .setImaSdkSettings(mockImaSdkSettings)
.buildForAdTag(TEST_URI)); .build(),
TEST_DATA_SPEC);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000); fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000);
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
...@@ -650,7 +655,8 @@ public final class ImaAdsLoaderTest { ...@@ -650,7 +655,8 @@ public final class ImaAdsLoaderTest {
.setPlayAdBeforeStartPosition(false) .setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory) .setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings) .setImaSdkSettings(mockImaSdkSettings)
.buildForAdTag(TEST_URI)); .build(),
TEST_DATA_SPEC);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000); fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000);
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
...@@ -689,7 +695,8 @@ public final class ImaAdsLoaderTest { ...@@ -689,7 +695,8 @@ public final class ImaAdsLoaderTest {
.setPlayAdBeforeStartPosition(false) .setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory) .setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings) .setImaSdkSettings(mockImaSdkSettings)
.buildForAdTag(TEST_URI)); .build(),
TEST_DATA_SPEC);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs)); fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs));
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
...@@ -708,10 +715,50 @@ public final class ImaAdsLoaderTest { ...@@ -708,10 +715,50 @@ public final class ImaAdsLoaderTest {
} }
@Test @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() { public void stop_unregistersAllVideoControlOverlays() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS); setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
imaAdsLoader.requestAds(adViewGroup); imaAdsLoader.requestAds(TEST_DATA_SPEC, adViewGroup);
imaAdsLoader.stop(); imaAdsLoader.stop();
InOrder inOrder = inOrder(mockAdDisplayContainer); InOrder inOrder = inOrder(mockAdDisplayContainer);
...@@ -775,16 +822,21 @@ public final class ImaAdsLoaderTest { ...@@ -775,16 +822,21 @@ public final class ImaAdsLoaderTest {
new ImaAdsLoader.Builder(getApplicationContext()) new ImaAdsLoader.Builder(getApplicationContext())
.setImaFactory(mockImaFactory) .setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings) .setImaSdkSettings(mockImaSdkSettings)
.buildForAdTag(TEST_URI)); .build(),
TEST_DATA_SPEC);
} }
private void setupPlayback( private void setupPlayback(
Timeline contentTimeline, List<Float> cuePoints, ImaAdsLoader imaAdsLoader) { Timeline contentTimeline,
List<Float> cuePoints,
ImaAdsLoader imaAdsLoader,
DataSpec adTagDataSpec) {
fakeExoPlayer = new FakePlayer(); fakeExoPlayer = new FakePlayer();
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline); adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints); when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
this.imaAdsLoader = imaAdsLoader; this.imaAdsLoader = imaAdsLoader;
imaAdsLoader.setPlayer(fakeExoPlayer); imaAdsLoader.setPlayer(fakeExoPlayer);
imaAdsLoader.setAdTagDataSpec(adTagDataSpec);
} }
private void setupMocks() { private void setupMocks() {
......
...@@ -48,6 +48,7 @@ import android.os.SystemClock; ...@@ -48,6 +48,7 @@ import android.os.SystemClock;
import android.security.NetworkSecurityPolicy; import android.security.NetworkSecurityPolicy;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.view.Display; import android.view.Display;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.WindowManager; import android.view.WindowManager;
...@@ -2007,6 +2008,14 @@ public final class Util { ...@@ -2007,6 +2008,14 @@ public final class Util {
return builder.toString(); 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, * A hacky method that always throws {@code t} even if {@code t} is a checked exception,
* and is not declared to be thrown. * and is not declared to be thrown.
......
...@@ -877,6 +877,14 @@ public class UtilTest { ...@@ -877,6 +877,14 @@ public class UtilTest {
} }
@Test @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() { public void crc32_returnsUpdatedCrc32() {
byte[] bytes = {0x5F, 0x78, 0x04, 0x7B, 0x5F}; byte[] bytes = {0x5F, 0x78, 0x04, 0x7B, 0x5F};
int start = 1; int start = 1;
......
...@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.source.ads.AdsLoader; ...@@ -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.AdsLoader.AdViewProvider;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; 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.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
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;
...@@ -280,7 +281,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { ...@@ -280,7 +281,8 @@ 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);
if (mediaItem.playbackProperties.adTagUri == null) { @Nullable Uri adTagUri = mediaItem.playbackProperties.adTagUri;
if (adTagUri == null) {
return mediaSource; return mediaSource;
} }
AdsLoaderProvider adsLoaderProvider = this.adsLoaderProvider; AdsLoaderProvider adsLoaderProvider = this.adsLoaderProvider;
...@@ -292,14 +294,17 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { ...@@ -292,14 +294,17 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
+ " setAdViewProvider."); + " setAdViewProvider.");
return mediaSource; return mediaSource;
} }
@Nullable @Nullable AdsLoader adsLoader = adsLoaderProvider.getAdsLoader(adTagUri);
AdsLoader adsLoader = adsLoaderProvider.getAdsLoader(mediaItem.playbackProperties.adTagUri);
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. No AdsLoader for provided adTagUri");
return mediaSource; return mediaSource;
} }
return new AdsMediaSource( return new AdsMediaSource(
mediaSource, /* adMediaSourceFactory= */ this, adsLoader, adViewProvider); mediaSource,
new DataSpec(adTagUri),
/* adMediaSourceFactory= */ this,
adsLoader,
adViewProvider);
} }
private static SparseArray<MediaSourceFactory> loadDelegates( private static SparseArray<MediaSourceFactory> loadDelegates(
......
...@@ -199,6 +199,14 @@ public interface AdsLoader { ...@@ -199,6 +199,14 @@ 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 eventListener Listener for ads loader events.
......
...@@ -128,6 +128,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -128,6 +128,7 @@ 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 Handler mainHandler; private final Handler mainHandler;
private final Timeline.Period period; private final Timeline.Period period;
...@@ -145,7 +146,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -145,7 +146,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* @param dataSourceFactory Factory for data sources used to load ad media. * @param dataSourceFactory Factory for data 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.
* @deprecated Use {@link AdsMediaSource#AdsMediaSource(MediaSource, DataSpec, MediaSourceFactory,
* AdsLoader, AdsLoader.AdViewProvider)} instead.
*/ */
@Deprecated
public AdsMediaSource( public AdsMediaSource(
MediaSource contentMediaSource, MediaSource contentMediaSource,
DataSource.Factory dataSourceFactory, DataSource.Factory dataSourceFactory,
...@@ -155,7 +159,33 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -155,7 +159,33 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
contentMediaSource, contentMediaSource,
new ProgressiveMediaSource.Factory(dataSourceFactory), new ProgressiveMediaSource.Factory(dataSourceFactory),
adsLoader, adsLoader,
adViewProvider); 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);
} }
/** /**
...@@ -163,19 +193,31 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -163,19 +193,31 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* 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 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.
*/ */
public AdsMediaSource( public AdsMediaSource(
MediaSource contentMediaSource, MediaSource contentMediaSource,
DataSpec adTagDataSpec,
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;
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][];
...@@ -204,7 +246,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -204,7 +246,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
ComponentListener componentListener = new ComponentListener(); ComponentListener componentListener = new ComponentListener();
this.componentListener = componentListener; this.componentListener = componentListener;
prepareChildSource(CHILD_SOURCE_MEDIA_PERIOD_ID, contentMediaSource); 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 @Override
......
...@@ -59,7 +59,8 @@ public final class DataSchemeDataSource extends BaseDataSource { ...@@ -59,7 +59,8 @@ public final class DataSchemeDataSource extends BaseDataSource {
String dataString = uriParts[1]; String dataString = uriParts[1];
if (uriParts[0].contains(";base64")) { if (uriParts[0].contains(";base64")) {
try { 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) { } catch (IllegalArgumentException e) {
throw new ParserException("Error while parsing Base64 encoded string: " + dataString, e); throw new ParserException("Error while parsing Base64 encoded string: " + dataString, e);
} }
......
...@@ -61,6 +61,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; ...@@ -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.ClippingMediaSource;
import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.CompositeMediaSource;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; 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.LoopingMediaSource;
import com.google.android.exoplayer2.source.MaskingMediaSource; import com.google.android.exoplayer2.source.MaskingMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
...@@ -101,7 +102,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; ...@@ -101,7 +102,7 @@ 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.DataSpec;
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;
...@@ -5556,7 +5557,8 @@ public final class ExoPlayerTest { ...@@ -5556,7 +5557,8 @@ public final class ExoPlayerTest {
AdsMediaSource adsMediaSource = AdsMediaSource adsMediaSource =
new AdsMediaSource( new AdsMediaSource(
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)), new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new DefaultDataSourceFactory(context), /* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
new DefaultMediaSourceFactory(context),
new FakeAdsLoader(), new FakeAdsLoader(),
new FakeAdViewProvider()); new FakeAdViewProvider());
Exception[] exception = {null}; Exception[] exception = {null};
...@@ -5593,7 +5595,8 @@ public final class ExoPlayerTest { ...@@ -5593,7 +5595,8 @@ public final class ExoPlayerTest {
AdsMediaSource adsMediaSource = AdsMediaSource adsMediaSource =
new AdsMediaSource( new AdsMediaSource(
mediaSource, mediaSource,
new DefaultDataSourceFactory(context), /* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
new DefaultMediaSourceFactory(context),
new FakeAdsLoader(), new FakeAdsLoader(),
new FakeAdViewProvider()); new FakeAdViewProvider());
final Exception[] exception = {null}; final Exception[] exception = {null};
...@@ -5632,7 +5635,8 @@ public final class ExoPlayerTest { ...@@ -5632,7 +5635,8 @@ public final class ExoPlayerTest {
AdsMediaSource adsMediaSource = AdsMediaSource adsMediaSource =
new AdsMediaSource( new AdsMediaSource(
mediaSource, mediaSource,
new DefaultDataSourceFactory(context), /* adTagDataSpec= */ new DataSpec(Uri.EMPTY),
new DefaultMediaSourceFactory(context),
new FakeAdsLoader(), new FakeAdsLoader(),
new FakeAdViewProvider()); new FakeAdViewProvider());
final Exception[] exception = {null}; final Exception[] exception = {null};
...@@ -8540,6 +8544,9 @@ public final class ExoPlayerTest { ...@@ -8540,6 +8544,9 @@ public final class ExoPlayerTest {
public void setSupportedContentTypes(int... contentTypes) {} public void setSupportedContentTypes(int... contentTypes) {}
@Override @Override
public void setAdTagDataSpec(DataSpec adTagDataSpec) {}
@Override
public void start(AdsLoader.EventListener eventListener, AdViewProvider adViewProvider) {} public void start(AdsLoader.EventListener eventListener, AdViewProvider adViewProvider) {}
@Override @Override
......
...@@ -145,6 +145,14 @@ public final class DataSchemeDataSourceTest { ...@@ -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) { private static DataSpec buildDataSpec(String uriString) {
return buildDataSpec(uriString, /* position= */ 0, /* length= */ C.LENGTH_UNSET); return buildDataSpec(uriString, /* position= */ 0, /* length= */ C.LENGTH_UNSET);
} }
......
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