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
This commit is contained in:
andrewlewis 2018-05-04 09:26:06 -07:00 committed by Oliver Woodman
parent 0edc832d67
commit 2a06e00201
6 changed files with 215 additions and 63 deletions

View File

@ -3,6 +3,9 @@
### dev-v2 (not yet released) ### ### dev-v2 (not yet released) ###
* Added dependency on checkerframework annotations for static code analysis. * 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 ### ### 2.8.0 ###

View File

@ -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;
import com.google.android.exoplayer2.source.ads.AdPlaybackState.AdState; import com.google.android.exoplayer2.source.ads.AdPlaybackState.AdState;
import com.google.android.exoplayer2.source.ads.AdsLoader; 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.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -80,6 +82,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private final Context context; private final Context context;
private @Nullable ImaSdkSettings imaSdkSettings; private @Nullable ImaSdkSettings imaSdkSettings;
private @Nullable AdEventListener adEventListener;
private int vastLoadTimeoutMs; private int vastLoadTimeoutMs;
private int mediaLoadTimeoutMs; private int mediaLoadTimeoutMs;
@ -108,6 +111,18 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
return this; 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. * 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) { public ImaAdsLoader buildForAdTag(Uri adTagUri) {
return new ImaAdsLoader( 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) { public ImaAdsLoader buildForAdsResponse(String adsResponse) {
return new ImaAdsLoader( 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 @Nullable String adsResponse;
private final int vastLoadTimeoutMs; private final int vastLoadTimeoutMs;
private final int mediaLoadTimeoutMs; private final int mediaLoadTimeoutMs;
private final @Nullable AdEventListener adEventListener;
private final Timeline.Period period; private final Timeline.Period period;
private final List<VideoAdPlayerCallback> adCallbacks; private final List<VideoAdPlayerCallback> adCallbacks;
private final ImaSdkFactory imaSdkFactory; private final ImaSdkFactory imaSdkFactory;
@ -229,7 +257,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private VideoProgressUpdate lastAdProgress; private VideoProgressUpdate lastAdProgress;
private AdsManager adsManager; private AdsManager adsManager;
private AdErrorEvent pendingAdErrorEvent; private AdLoadException pendingAdLoadError;
private Timeline timeline; private Timeline timeline;
private long contentDurationMs; private long contentDurationMs;
private int podIndexOffset; private int podIndexOffset;
@ -308,7 +336,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
/* imaSdkSettings= */ null, /* imaSdkSettings= */ null,
/* adsResponse= */ null, /* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* 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, imaSdkSettings,
/* adsResponse= */ null, /* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET); /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
/* adEventListener= */ null);
} }
private ImaAdsLoader( private ImaAdsLoader(
@ -339,12 +369,14 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
@Nullable ImaSdkSettings imaSdkSettings, @Nullable ImaSdkSettings imaSdkSettings,
@Nullable String adsResponse, @Nullable String adsResponse,
int vastLoadTimeoutMs, int vastLoadTimeoutMs,
int mediaLoadTimeoutMs) { int mediaLoadTimeoutMs,
@Nullable AdEventListener adEventListener) {
Assertions.checkArgument(adTagUri != null || adsResponse != null); Assertions.checkArgument(adTagUri != null || adsResponse != null);
this.adTagUri = adTagUri; this.adTagUri = adTagUri;
this.adsResponse = adsResponse; this.adsResponse = adsResponse;
this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.vastLoadTimeoutMs = vastLoadTimeoutMs;
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
this.adEventListener = adEventListener;
period = new Timeline.Period(); period = new Timeline.Period();
adCallbacks = new ArrayList<>(1); adCallbacks = new ArrayList<>(1);
imaSdkFactory = ImaSdkFactory.getInstance(); imaSdkFactory = ImaSdkFactory.getInstance();
@ -500,6 +532,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
this.adsManager = adsManager; this.adsManager = adsManager;
adsManager.addAdErrorListener(this); adsManager.addAdErrorListener(this);
adsManager.addAdEventListener(this); adsManager.addAdEventListener(this);
if (adEventListener != null) {
adsManager.addAdEventListener(adEventListener);
}
if (player != null) { if (player != null) {
// If a player is attached already, start playback immediately. // If a player is attached already, start playback immediately.
try { try {
@ -544,13 +579,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
updateAdPlaybackState(); updateAdPlaybackState();
} else if (isAdGroupLoadError(error)) { } else if (isAdGroupLoadError(error)) {
try { try {
handleAdGroupLoadError(); handleAdGroupLoadError(error);
} catch (Exception e) { } catch (Exception e) {
maybeNotifyInternalError("onAdError", e); maybeNotifyInternalError("onAdError", e);
} }
} }
if (pendingAdErrorEvent == null) { if (pendingAdLoadError == null) {
pendingAdErrorEvent = adErrorEvent; pendingAdLoadError = AdLoadException.createForAllAds(error);
} }
maybeNotifyPendingAdLoadError(); maybeNotifyPendingAdLoadError();
} }
@ -937,9 +972,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
break; break;
case LOG: case LOG:
Map<String, String> adData = adEvent.getAdData(); Map<String, String> adData = adEvent.getAdData();
Log.i(TAG, "Log AdEvent: " + adData); String message = "AdEvent: " + adData;
Log.i(TAG, message);
if ("adLoadError".equals(adData.get("type"))) { if ("adLoadError".equals(adData.get("type"))) {
handleAdGroupLoadError(); handleAdGroupLoadError(new IOException(message));
} }
break; break;
case ALL_ADS_COMPLETED: 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 = int adGroupIndex =
this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex;
if (adGroupIndex == C.INDEX_UNSET) { if (adGroupIndex == C.INDEX_UNSET) {
@ -1033,6 +1069,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
} }
updateAdPlaybackState(); updateAdPlaybackState();
if (pendingAdLoadError == null) {
pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex);
}
} }
private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { 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() { private void maybeNotifyPendingAdLoadError() {
if (pendingAdErrorEvent != null) { if (pendingAdLoadError != null && eventListener != null) {
if (eventListener != null) { eventListener.onAdLoadError(pendingAdLoadError, new DataSpec(adTagUri));
eventListener.onAdLoadError( pendingAdLoadError = null;
new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError()));
}
pendingAdErrorEvent = null;
} }
} }
private void maybeNotifyInternalError(String name, Exception cause) { private void maybeNotifyInternalError(String name, Exception cause) {
String message = "Internal error in " + name; String message = "Internal error in " + name;
Log.e(TAG, message, cause); 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. // We can't recover from an unexpected error in general, so skip all remaining ads.
if (adPlaybackState == null) { if (adPlaybackState == null) {
adPlaybackState = new AdPlaybackState(); adPlaybackState = new AdPlaybackState();
@ -1135,6 +1168,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
} }
updateAdPlaybackState(); updateAdPlaybackState();
if (eventListener != null) {
eventListener.onAdLoadError(
AdLoadException.createForUnexpected(new RuntimeException(message, cause)),
new DataSpec(adTagUri));
}
} }
private static long[] getAdGroupTimesUs(List<Float> cuePoints) { private static long[] getAdGroupTimesUs(List<Float> cuePoints) {

View File

@ -493,6 +493,8 @@ public final class C {
* A data type constant for time synchronization data. * A data type constant for time synchronization data.
*/ */
public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; 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 * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or
* equal to this value. * equal to this value.

View File

@ -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. * 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; public final MediaSource mediaSource;
@ -140,7 +140,7 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
} }
if (!notifiedPrepareError) { if (!notifiedPrepareError) {
notifiedPrepareError = true; notifiedPrepareError = true;
listener.onPrepareError(e); listener.onPrepareError(id, e);
} }
} }
} }

View File

@ -18,6 +18,8 @@ package com.google.android.exoplayer2.source.ads;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; 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; import java.io.IOException;
/** /**
@ -54,19 +56,12 @@ public interface AdsLoader {
void onAdPlaybackState(AdPlaybackState adPlaybackState); 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 error The error.
* @param dataSpec The data spec associated with the load error.
*/ */
void onAdLoadError(IOException error); void onAdLoadError(AdLoadException error, DataSpec dataSpec);
/**
* 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);
/** /**
* Called when the user clicks through an ad (for example, following a 'learn more' link). * Called when the user clicks through an ad (for example, following a 'learn more' link).

View File

@ -18,8 +18,8 @@ package com.google.android.exoplayer2.source.ads;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; 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.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -64,7 +70,75 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
int[] getSupportedTypes(); 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 { public interface EventListener {
/** /**
@ -131,7 +205,30 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
ViewGroup adUiViewGroup) { ViewGroup adUiViewGroup) {
this( this(
contentMediaSource, 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, adsLoader,
adUiViewGroup, adUiViewGroup,
/* eventHandler= */ null, /* eventHandler= */ null,
@ -148,7 +245,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. * @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 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. * @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( public AdsMediaSource(
MediaSource contentMediaSource, MediaSource contentMediaSource,
DataSource.Factory dataSourceFactory, DataSource.Factory dataSourceFactory,
@ -175,7 +278,13 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. * @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 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. * @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( public AdsMediaSource(
MediaSource contentMediaSource, MediaSource contentMediaSource,
MediaSourceFactory adMediaSourceFactory, MediaSourceFactory adMediaSourceFactory,
@ -217,10 +326,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
if (adPlaybackState.adGroupCount > 0 && id.isAd()) { if (adPlaybackState.adGroupCount > 0 && id.isAd()) {
int adGroupIndex = id.adGroupIndex; int adGroupIndex = id.adGroupIndex;
int adIndexInAdGroup = id.adIndexInAdGroup; int adIndexInAdGroup = id.adIndexInAdGroup;
Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup];
if (adGroupMediaSources[adGroupIndex].length <= adIndexInAdGroup) { if (adGroupMediaSources[adGroupIndex].length <= adIndexInAdGroup) {
Uri adUri = adPlaybackState.adGroups[id.adGroupIndex].uris[id.adIndexInAdGroup];
MediaSource adMediaSource = adMediaSourceFactory.createMediaSource(adUri); MediaSource adMediaSource = adMediaSourceFactory.createMediaSource(adUri);
int oldAdCount = adGroupMediaSources[id.adGroupIndex].length; int oldAdCount = adGroupMediaSources[adGroupIndex].length;
if (adIndexInAdGroup >= oldAdCount) { if (adIndexInAdGroup >= oldAdCount) {
int adCount = adIndexInAdGroup + 1; int adCount = adIndexInAdGroup + 1;
adGroupMediaSources[adGroupIndex] = adGroupMediaSources[adGroupIndex] =
@ -239,7 +348,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
new MediaPeriodId(/* periodIndex= */ 0, id.windowSequenceNumber), new MediaPeriodId(/* periodIndex= */ 0, id.windowSequenceNumber),
allocator); allocator);
deferredMediaPeriod.setPrepareErrorListener( deferredMediaPeriod.setPrepareErrorListener(
new AdPrepareErrorListener(adGroupIndex, adIndexInAdGroup)); new AdPrepareErrorListener(adUri, adGroupIndex, adIndexInAdGroup));
List<DeferredMediaPeriod> mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource); List<DeferredMediaPeriod> mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource);
if (mediaPeriods == null) { if (mediaPeriods == null) {
deferredMediaPeriod.createPeriod(); deferredMediaPeriod.createPeriod();
@ -357,6 +466,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
private final class ComponentListener implements AdsLoader.EventListener { private final class ComponentListener implements AdsLoader.EventListener {
private final Handler playerHandler; private final Handler playerHandler;
private volatile boolean released; private volatile boolean released;
/** /**
@ -424,38 +534,31 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
} }
@Override @Override
public void onAdLoadError(final IOException error) { public void onAdLoadError(final AdLoadException error, DataSpec dataSpec) {
if (released) { if (released) {
return; 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) { if (eventHandler != null && eventListener != null) {
eventHandler.post( eventHandler.post(
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
if (!released) { if (!released) {
if (error.type == AdLoadException.TYPE_UNEXPECTED) {
eventListener.onInternalAdLoadError(error.getRuntimeExceptionForUnexpected());
} else {
eventListener.onAdLoadError(error); 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);
}
} }
}); });
} }
@ -464,16 +567,27 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
private final class AdPrepareErrorListener implements DeferredMediaPeriod.PrepareErrorListener { private final class AdPrepareErrorListener implements DeferredMediaPeriod.PrepareErrorListener {
private final Uri adUri;
private final int adGroupIndex; private final int adGroupIndex;
private final int adIndexInAdGroup; 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.adGroupIndex = adGroupIndex;
this.adIndexInAdGroup = adIndexInAdGroup; this.adIndexInAdGroup = adIndexInAdGroup;
} }
@Override @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( mainHandler.post(
new Runnable() { new Runnable() {
@Override @Override