From 2a06e0020141660ba777c7edc4f824c75f42f515 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 4 May 2018 09:26:06 -0700 Subject: [PATCH] Expose ad load errors via MediaSourceEventListener The old event listener on AdsMediaSource is deprecated, in favor of reporting in the normal way (via MediaSourceEventListener). Add AdLoadException with information on what ad/ads failed to load. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=195426144 --- RELEASENOTES.md | 3 + .../exoplayer2/ext/ima/ImaAdsLoader.java | 80 +++++--- .../java/com/google/android/exoplayer2/C.java | 2 + .../source/DeferredMediaPeriod.java | 4 +- .../exoplayer2/source/ads/AdsLoader.java | 15 +- .../exoplayer2/source/ads/AdsMediaSource.java | 174 +++++++++++++++--- 6 files changed, 215 insertions(+), 63 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a39e28a57f..60ce036861 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,6 +3,9 @@ ### dev-v2 (not yet released) ### * Added dependency on checkerframework annotations for static code analysis. +* IMA: Expose ad load errors via `MediaSourceEventListener` on `AdsMediaSource`, + and allow setting an ad event listener on `ImaAdsLoader`. Deprecate the + `AdsMediaSource.EventListener`. ### 2.8.0 ### diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 60017a27e4..d3dbaaec96 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -52,6 +52,8 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdPlaybackState.AdState; import com.google.android.exoplayer2.source.ads.AdsLoader; +import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; +import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -80,6 +82,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A private final Context context; private @Nullable ImaSdkSettings imaSdkSettings; + private @Nullable AdEventListener adEventListener; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; @@ -108,6 +111,18 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A return this; } + /** + * Sets a listener for ad events that will be passed to {@link + * AdsManager#addAdEventListener(AdEventListener)}. + * + * @param adEventListener The ad event listener. + * @return This builder, for convenience. + */ + public Builder setAdEventListener(AdEventListener adEventListener) { + this.adEventListener = Assertions.checkNotNull(adEventListener); + return this; + } + /** * Sets the VAST load timeout, in milliseconds. * @@ -144,7 +159,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A */ public ImaAdsLoader buildForAdTag(Uri adTagUri) { return new ImaAdsLoader( - context, adTagUri, imaSdkSettings, null, vastLoadTimeoutMs, mediaLoadTimeoutMs); + context, + adTagUri, + imaSdkSettings, + null, + vastLoadTimeoutMs, + mediaLoadTimeoutMs, + adEventListener); } /** @@ -156,7 +177,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A */ public ImaAdsLoader buildForAdsResponse(String adsResponse) { return new ImaAdsLoader( - context, null, imaSdkSettings, adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs); + context, + null, + imaSdkSettings, + adsResponse, + vastLoadTimeoutMs, + mediaLoadTimeoutMs, + adEventListener); } } @@ -214,6 +241,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A private final @Nullable String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; + private final @Nullable AdEventListener adEventListener; private final Timeline.Period period; private final List adCallbacks; private final ImaSdkFactory imaSdkFactory; @@ -229,7 +257,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A private VideoProgressUpdate lastAdProgress; private AdsManager adsManager; - private AdErrorEvent pendingAdErrorEvent; + private AdLoadException pendingAdLoadError; private Timeline timeline; private long contentDurationMs; private int podIndexOffset; @@ -308,7 +336,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A /* imaSdkSettings= */ null, /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, - /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET); + /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* adEventListener= */ null); } /** @@ -330,7 +359,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A imaSdkSettings, /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, - /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET); + /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* adEventListener= */ null); } private ImaAdsLoader( @@ -339,12 +369,14 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A @Nullable ImaSdkSettings imaSdkSettings, @Nullable String adsResponse, int vastLoadTimeoutMs, - int mediaLoadTimeoutMs) { + int mediaLoadTimeoutMs, + @Nullable AdEventListener adEventListener) { Assertions.checkArgument(adTagUri != null || adsResponse != null); this.adTagUri = adTagUri; this.adsResponse = adsResponse; this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; + this.adEventListener = adEventListener; period = new Timeline.Period(); adCallbacks = new ArrayList<>(1); imaSdkFactory = ImaSdkFactory.getInstance(); @@ -500,6 +532,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A this.adsManager = adsManager; adsManager.addAdErrorListener(this); adsManager.addAdEventListener(this); + if (adEventListener != null) { + adsManager.addAdEventListener(adEventListener); + } if (player != null) { // If a player is attached already, start playback immediately. try { @@ -544,13 +579,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A updateAdPlaybackState(); } else if (isAdGroupLoadError(error)) { try { - handleAdGroupLoadError(); + handleAdGroupLoadError(error); } catch (Exception e) { maybeNotifyInternalError("onAdError", e); } } - if (pendingAdErrorEvent == null) { - pendingAdErrorEvent = adErrorEvent; + if (pendingAdLoadError == null) { + pendingAdLoadError = AdLoadException.createForAllAds(error); } maybeNotifyPendingAdLoadError(); } @@ -937,9 +972,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A break; case LOG: Map adData = adEvent.getAdData(); - Log.i(TAG, "Log AdEvent: " + adData); + String message = "AdEvent: " + adData; + Log.i(TAG, message); if ("adLoadError".equals(adData.get("type"))) { - handleAdGroupLoadError(); + handleAdGroupLoadError(new IOException(message)); } break; case ALL_ADS_COMPLETED: @@ -1011,7 +1047,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } - private void handleAdGroupLoadError() { + private void handleAdGroupLoadError(Exception error) { int adGroupIndex = this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; if (adGroupIndex == C.INDEX_UNSET) { @@ -1033,6 +1069,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } updateAdPlaybackState(); + if (pendingAdLoadError == null) { + pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex); + } } private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { @@ -1111,21 +1150,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } private void maybeNotifyPendingAdLoadError() { - if (pendingAdErrorEvent != null) { - if (eventListener != null) { - eventListener.onAdLoadError( - new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError())); - } - pendingAdErrorEvent = null; + if (pendingAdLoadError != null && eventListener != null) { + eventListener.onAdLoadError(pendingAdLoadError, new DataSpec(adTagUri)); + pendingAdLoadError = null; } } private void maybeNotifyInternalError(String name, Exception cause) { String message = "Internal error in " + name; Log.e(TAG, message, cause); - if (eventListener != null) { - eventListener.onInternalAdLoadError(new RuntimeException(message, cause)); - } // We can't recover from an unexpected error in general, so skip all remaining ads. if (adPlaybackState == null) { adPlaybackState = new AdPlaybackState(); @@ -1135,6 +1168,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } updateAdPlaybackState(); + if (eventListener != null) { + eventListener.onAdLoadError( + AdLoadException.createForUnexpected(new RuntimeException(message, cause)), + new DataSpec(adTagUri)); + } } private static long[] getAdGroupTimesUs(List cuePoints) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 79de146f29..de210f5eff 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -493,6 +493,8 @@ public final class C { * A data type constant for time synchronization data. */ public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; + /** A data type constant for ads loader data. */ + public static final int DATA_TYPE_AD = 6; /** * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or * equal to this value. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java index ab6b3a311a..229043b127 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java @@ -37,7 +37,7 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb /** * Called the first time an error occurs while refreshing source info or preparing the period. */ - void onPrepareError(IOException exception); + void onPrepareError(MediaPeriodId mediaPeriodId, IOException exception); } public final MediaSource mediaSource; @@ -140,7 +140,7 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb } if (!notifiedPrepareError) { notifiedPrepareError = true; - listener.onPrepareError(e); + listener.onPrepareError(id, e); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index 6295ca4229..d05c51a793 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -18,6 +18,8 @@ package com.google.android.exoplayer2.source.ads; import android.view.ViewGroup; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; +import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; /** @@ -54,19 +56,12 @@ public interface AdsLoader { void onAdPlaybackState(AdPlaybackState adPlaybackState); /** - * Called when there was an error loading ads. The loader will skip the problematic ad(s). + * Called when there was an error loading ads. * * @param error The error. + * @param dataSpec The data spec associated with the load error. */ - void onAdLoadError(IOException error); - - /** - * Called when an unexpected internal error is encountered while loading ads. The loader will - * skip all remaining ads, as the error is not recoverable. - * - * @param error The error. - */ - void onInternalAdLoadError(RuntimeException error); + void onAdLoadError(AdLoadException error, DataSpec dataSpec); /** * Called when the user clicks through an ad (for example, following a 'learn more' link). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 9e93f8d92d..7f9dc18eaf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -18,8 +18,8 @@ package com.google.android.exoplayer2.source.ads; import android.net.Uri; import android.os.Handler; import android.os.Looper; +import android.support.annotation.IntDef; import android.support.annotation.Nullable; -import android.util.Log; import android.view.ViewGroup; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; @@ -30,10 +30,16 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; +import com.google.android.exoplayer2.source.MediaSourceEventListener; +import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo; +import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData; 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.util.Assertions; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -64,7 +70,75 @@ public final class AdsMediaSource extends CompositeMediaSource { int[] getSupportedTypes(); } - /** Listener for ads media source events. */ + /** + * Wrapper for exceptions that occur while loading ads, which are notified via {@link + * MediaSourceEventListener#onLoadError(int, MediaPeriodId, LoadEventInfo, MediaLoadData, + * IOException, boolean)}. + */ + public static final class AdLoadException extends IOException { + + /** Types of ad load exceptions. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_AD, TYPE_AD_GROUP, TYPE_ALL_ADS, TYPE_UNEXPECTED}) + public @interface Type {} + /** Type for when an ad failed to load. The ad will be skipped. */ + public static final int TYPE_AD = 0; + /** Type for when an ad group failed to load. The ad group will be skipped. */ + public static final int TYPE_AD_GROUP = 1; + /** Type for when all ad groups failed to load. All ads will be skipped. */ + public static final int TYPE_ALL_ADS = 2; + /** Type for when an unexpected error occurred while loading ads. All ads will be skipped. */ + public static final int TYPE_UNEXPECTED = 3; + + /** Returns a new ad load exception of {@link #TYPE_AD}. */ + public static AdLoadException createForAd(Exception error) { + return new AdLoadException(TYPE_AD, error); + } + + /** Returns a new ad load exception of {@link #TYPE_AD_GROUP}. */ + public static AdLoadException createForAdGroup(Exception error, int adGroupIndex) { + return new AdLoadException( + TYPE_AD_GROUP, new IOException("Failed to load ad group " + adGroupIndex, error)); + } + + /** Returns a new ad load exception of {@link #TYPE_ALL_ADS}. */ + public static AdLoadException createForAllAds(Exception error) { + return new AdLoadException(TYPE_ALL_ADS, error); + } + + /** Returns a new ad load exception of {@link #TYPE_UNEXPECTED}. */ + public static AdLoadException createForUnexpected(RuntimeException error) { + return new AdLoadException(TYPE_UNEXPECTED, error); + } + + /** The {@link Type} of the ad load exception. */ + public final @Type int type; + + private AdLoadException(@Type int type, Exception cause) { + super(cause); + this.type = type; + } + + /** + * Returns the {@link RuntimeException} that caused the exception if its type is {@link + * #TYPE_UNEXPECTED}. + */ + public RuntimeException getRuntimeExceptionForUnexpected() { + Assertions.checkState(type == TYPE_UNEXPECTED); + return (RuntimeException) getCause(); + } + } + + /** + * Listener for ads media source events. + * + * @deprecated To listen for ad load error events, add a listener via {@link + * #addEventListener(Handler, MediaSourceEventListener)} and check for {@link + * AdLoadException}s in {@link MediaSourceEventListener#onLoadError(int, MediaPeriodId, + * LoadEventInfo, MediaLoadData, IOException, boolean)}. Individual ads loader implementations + * should expose ad interaction events, if applicable. + */ + @Deprecated public interface EventListener { /** @@ -131,7 +205,30 @@ public final class AdsMediaSource extends CompositeMediaSource { ViewGroup adUiViewGroup) { this( contentMediaSource, - dataSourceFactory, + new ExtractorMediaSource.Factory(dataSourceFactory), + adsLoader, + adUiViewGroup, + /* eventHandler= */ null, + /* eventListener= */ 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 adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. + */ + public AdsMediaSource( + MediaSource contentMediaSource, + MediaSourceFactory adMediaSourceFactory, + AdsLoader adsLoader, + ViewGroup adUiViewGroup) { + this( + contentMediaSource, + adMediaSourceFactory, adsLoader, adUiViewGroup, /* eventHandler= */ null, @@ -148,7 +245,13 @@ public final class AdsMediaSource extends CompositeMediaSource { * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. * @param eventHandler A handler for events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. + * @deprecated To listen for ad load error events, add a listener via {@link + * #addEventListener(Handler, MediaSourceEventListener)} and check for {@link + * AdLoadException}s in {@link MediaSourceEventListener#onLoadError(int, MediaPeriodId, + * LoadEventInfo, MediaLoadData, IOException, boolean)}. Individual ads loader implementations + * should expose ad interaction events, if applicable. */ + @Deprecated public AdsMediaSource( MediaSource contentMediaSource, DataSource.Factory dataSourceFactory, @@ -175,7 +278,13 @@ public final class AdsMediaSource extends CompositeMediaSource { * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. * @param eventHandler A handler for events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. + * @deprecated To listen for ad load error events, add a listener via {@link + * #addEventListener(Handler, MediaSourceEventListener)} and check for {@link + * AdLoadException}s in {@link MediaSourceEventListener#onLoadError(int, MediaPeriodId, + * LoadEventInfo, MediaLoadData, IOException, boolean)}. Individual ads loader implementations + * should expose ad interaction events, if applicable. */ + @Deprecated public AdsMediaSource( MediaSource contentMediaSource, MediaSourceFactory adMediaSourceFactory, @@ -217,10 +326,10 @@ public final class AdsMediaSource extends CompositeMediaSource { if (adPlaybackState.adGroupCount > 0 && id.isAd()) { int adGroupIndex = id.adGroupIndex; int adIndexInAdGroup = id.adIndexInAdGroup; + Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup]; if (adGroupMediaSources[adGroupIndex].length <= adIndexInAdGroup) { - Uri adUri = adPlaybackState.adGroups[id.adGroupIndex].uris[id.adIndexInAdGroup]; MediaSource adMediaSource = adMediaSourceFactory.createMediaSource(adUri); - int oldAdCount = adGroupMediaSources[id.adGroupIndex].length; + int oldAdCount = adGroupMediaSources[adGroupIndex].length; if (adIndexInAdGroup >= oldAdCount) { int adCount = adIndexInAdGroup + 1; adGroupMediaSources[adGroupIndex] = @@ -239,7 +348,7 @@ public final class AdsMediaSource extends CompositeMediaSource { new MediaPeriodId(/* periodIndex= */ 0, id.windowSequenceNumber), allocator); deferredMediaPeriod.setPrepareErrorListener( - new AdPrepareErrorListener(adGroupIndex, adIndexInAdGroup)); + new AdPrepareErrorListener(adUri, adGroupIndex, adIndexInAdGroup)); List mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource); if (mediaPeriods == null) { deferredMediaPeriod.createPeriod(); @@ -357,6 +466,7 @@ public final class AdsMediaSource extends CompositeMediaSource { private final class ComponentListener implements AdsLoader.EventListener { private final Handler playerHandler; + private volatile boolean released; /** @@ -424,37 +534,30 @@ public final class AdsMediaSource extends CompositeMediaSource { } @Override - public void onAdLoadError(final IOException error) { + public void onAdLoadError(final AdLoadException error, DataSpec dataSpec) { if (released) { return; } - Log.w(TAG, "Ad load error", error); + createEventDispatcher(/* mediaPeriodId= */ null) + .loadError( + dataSpec, + C.DATA_TYPE_AD, + C.TRACK_TYPE_UNKNOWN, + /* loadDurationMs= */ 0, + /* bytesLoaded= */ 0, + error, + /* wasCanceled= */ true); if (eventHandler != null && eventListener != null) { eventHandler.post( new Runnable() { @Override public void run() { if (!released) { - eventListener.onAdLoadError(error); - } - } - }); - } - } - - @Override - public void onInternalAdLoadError(final RuntimeException error) { - if (released) { - return; - } - Log.w(TAG, "Internal ad load error", error); - if (eventHandler != null && eventListener != null) { - eventHandler.post( - new Runnable() { - @Override - public void run() { - if (!released) { - eventListener.onInternalAdLoadError(error); + if (error.type == AdLoadException.TYPE_UNEXPECTED) { + eventListener.onInternalAdLoadError(error.getRuntimeExceptionForUnexpected()); + } else { + eventListener.onAdLoadError(error); + } } } }); @@ -464,16 +567,27 @@ public final class AdsMediaSource extends CompositeMediaSource { private final class AdPrepareErrorListener implements DeferredMediaPeriod.PrepareErrorListener { + private final Uri adUri; private final int adGroupIndex; private final int adIndexInAdGroup; - public AdPrepareErrorListener(int adGroupIndex, int adIndexInAdGroup) { + public AdPrepareErrorListener(Uri adUri, int adGroupIndex, int adIndexInAdGroup) { + this.adUri = adUri; this.adGroupIndex = adGroupIndex; this.adIndexInAdGroup = adIndexInAdGroup; } @Override - public void onPrepareError(final IOException exception) { + public void onPrepareError(MediaPeriodId mediaPeriodId, final IOException exception) { + createEventDispatcher(mediaPeriodId) + .loadError( + new DataSpec(adUri), + C.DATA_TYPE_AD, + C.TRACK_TYPE_UNKNOWN, + /* loadDurationMs= */ 0, + /* bytesLoaded= */ 0, + AdLoadException.createForAd(exception), + /* wasCanceled= */ true); mainHandler.post( new Runnable() { @Override