Handle detaching and reattaching the ads loader

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161526026
This commit is contained in:
andrewlewis 2017-07-11 07:29:39 -07:00 committed by Oliver Woodman
parent 94683d1e8c
commit 0b58c33632
3 changed files with 188 additions and 113 deletions

View File

@ -73,6 +73,7 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
@ -124,6 +125,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
private int resumeWindow; private int resumeWindow;
private long resumePosition; private long resumePosition;
// Fields used only for ad playback. The ads loader is loaded via reflection.
private Object imaAdsLoader; // com.google.android.exoplayer2.ext.ima.ImaAdsLoader
private Uri loadedAdTagUri;
private ViewGroup adOverlayViewGroup;
// Activity lifecycle // Activity lifecycle
@Override @Override
@ -190,6 +197,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
} }
@Override
public void onDestroy() {
super.onDestroy();
releaseAdsLoader();
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
@ -317,20 +330,19 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA); String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA);
if (adTagUriString != null) { if (adTagUriString != null) {
Uri adTagUri = Uri.parse(adTagUriString); Uri adTagUri = Uri.parse(adTagUriString);
ViewGroup adOverlayViewGroup = new FrameLayout(this); if (!adTagUri.equals(loadedAdTagUri)) {
// Load the extension source using reflection so that demo app doesn't have to depend on it. releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
try { try {
Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsMediaSource"); mediaSource = createAdsMediaSource(mediaSource, Uri.parse(adTagUriString));
Constructor<?> constructor = clazz.getConstructor(MediaSource.class,
DataSource.Factory.class, Context.class, Uri.class, ViewGroup.class);
mediaSource = (MediaSource) constructor.newInstance(mediaSource,
mediaDataSourceFactory, this, adTagUri, adOverlayViewGroup);
// The demo app has a non-null overlay frame layout. // The demo app has a non-null overlay frame layout.
simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup); simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup);
} catch (Exception e) { } catch (Exception e) {
// Throw if the media source class was not found, or there was an error instantiating it.
showToast(R.string.ima_not_loaded); showToast(R.string.ima_not_loaded);
} }
} else {
releaseAdsLoader();
} }
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) { if (haveResumePosition) {
@ -429,6 +441,47 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null); .buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
} }
/**
* Returns an ads media source, reusing the ads loader if one exists.
*
* @throws Exception Thrown if it was not possible to create an ads media source, for example, due
* to a missing dependency.
*/
private MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) throws Exception {
// Load the extension source using reflection so the demo app doesn't have to depend on it.
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
Class<?> loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader");
if (imaAdsLoader == null) {
imaAdsLoader = loaderClass.getConstructor(Context.class, Uri.class)
.newInstance(this, adTagUri);
adOverlayViewGroup = new FrameLayout(this);
// The demo app has a non-null overlay frame layout.
simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup);
}
Class<?> sourceClass =
Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsMediaSource");
Constructor<?> constructor = sourceClass.getConstructor(MediaSource.class,
DataSource.Factory.class, loaderClass, ViewGroup.class);
return (MediaSource) constructor.newInstance(mediaSource, mediaDataSourceFactory, imaAdsLoader,
adOverlayViewGroup);
}
private void releaseAdsLoader() {
if (imaAdsLoader != null) {
try {
Class<?> loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader");
Method releaseMethod = loaderClass.getMethod("release");
releaseMethod.invoke(imaAdsLoader);
} catch (Exception e) {
// Should never happen.
throw new IllegalStateException(e);
}
imaAdsLoader = null;
loadedAdTagUri = null;
simpleExoPlayerView.getOverlayFrameLayout().removeAllViews();
}
}
// ExoPlayer.EventListener implementation // ExoPlayer.EventListener implementation
@Override @Override

View File

@ -54,7 +54,7 @@ import java.util.List;
/** /**
* Loads ads using the IMA SDK. All methods are called on the main thread. * Loads ads using the IMA SDK. All methods are called on the main thread.
*/ */
/* package */ final class ImaAdsLoader implements ExoPlayer.EventListener, VideoAdPlayer, public final class ImaAdsLoader implements ExoPlayer.EventListener, VideoAdPlayer,
ContentProgressProvider, AdErrorListener, AdsLoadedListener, AdEventListener { ContentProgressProvider, AdErrorListener, AdsLoadedListener, AdEventListener {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@ -95,19 +95,23 @@ import java.util.List;
*/ */
private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000;
private final EventListener eventListener; private final Uri adTagUri;
private final ExoPlayer player;
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 AdDisplayContainer adDisplayContainer;
private final AdsLoader adsLoader; private final AdsLoader adsLoader;
private EventListener eventListener;
private ExoPlayer player;
private VideoProgressUpdate lastContentProgress;
private VideoProgressUpdate lastAdProgress;
private AdsManager adsManager; private AdsManager adsManager;
private Timeline timeline; private Timeline timeline;
private long contentDurationMs; private long contentDurationMs;
private AdPlaybackState adPlaybackState; private AdPlaybackState adPlaybackState;
private boolean released;
// Fields tracking IMA's state. // Fields tracking IMA's state.
/** /**
@ -163,46 +167,80 @@ import java.util.List;
* @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.
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. */
public ImaAdsLoader(Context context, Uri adTagUri) {
this(context, adTagUri, null);
}
/**
* Creates a new IMA ads loader.
*
* @param context The context.
* @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* more information.
* @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to * @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to
* use the default settings. If set, the player type and version fields may be overwritten. * use the default settings. If set, the player type and version fields may be overwritten.
* @param player The player instance that will play the loaded ad schedule.
* @param eventListener Listener for ad loader events.
*/ */
public ImaAdsLoader(Context context, Uri adTagUri, ViewGroup adUiViewGroup, public ImaAdsLoader(Context context, Uri adTagUri, ImaSdkSettings imaSdkSettings) {
ImaSdkSettings imaSdkSettings, ExoPlayer player, EventListener eventListener) { this.adTagUri = adTagUri;
this.eventListener = eventListener;
this.player = player;
period = new Timeline.Period(); period = new Timeline.Period();
adCallbacks = new ArrayList<>(1); adCallbacks = new ArrayList<>(1);
imaSdkFactory = ImaSdkFactory.getInstance();
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; adDisplayContainer = imaSdkFactory.createAdDisplayContainer();
pendingContentPositionMs = C.TIME_UNSET;
adGroupIndex = C.INDEX_UNSET;
contentDurationMs = C.TIME_UNSET;
player.addListener(this);
ImaSdkFactory imaSdkFactory = ImaSdkFactory.getInstance();
AdDisplayContainer adDisplayContainer = imaSdkFactory.createAdDisplayContainer();
adDisplayContainer.setPlayer(this); adDisplayContainer.setPlayer(this);
adDisplayContainer.setAdContainer(adUiViewGroup);
if (imaSdkSettings == null) { if (imaSdkSettings == null) {
imaSdkSettings = imaSdkFactory.createImaSdkSettings(); imaSdkSettings = imaSdkFactory.createImaSdkSettings();
} }
imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE); imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE);
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION); imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
AdsRequest request = imaSdkFactory.createAdsRequest();
request.setAdTagUrl(adTagUri.toString());
request.setAdDisplayContainer(adDisplayContainer);
request.setContentProgressProvider(this);
adsLoader = imaSdkFactory.createAdsLoader(context, imaSdkSettings); adsLoader = imaSdkFactory.createAdsLoader(context, imaSdkSettings);
adsLoader.addAdErrorListener(this); adsLoader.addAdErrorListener(this);
adsLoader.addAdsLoadedListener(this); adsLoader.addAdsLoadedListener(this);
adsLoader.requestAds(request); fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
pendingContentPositionMs = C.TIME_UNSET;
adGroupIndex = C.INDEX_UNSET;
contentDurationMs = C.TIME_UNSET;
}
/**
* Attaches a player that will play ads loaded using this instance.
*
* @param player The player instance that will play the loaded ads.
* @param eventListener Listener for ads loader events.
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
*/
public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) {
this.player = player;
this.eventListener = eventListener;
lastAdProgress = null;
lastContentProgress = null;
adDisplayContainer.setAdContainer(adUiViewGroup);
player.addListener(this);
if (adPlaybackState != null) {
eventListener.onAdPlaybackState(adPlaybackState);
// TODO: Call adsManager.resume if an ad is playing.
} else if (adTagUri != null) {
requestAds();
}
}
/**
* Detaches any attached player and event listener. To attach a new player, call
* {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. Call {@link #release()} to release
* all resources associated with this instance.
*/
public void detachPlayer() {
if (player != null) {
if (adsManager != null && player.isPlayingAd()) {
adsManager.pause();
}
lastAdProgress = getAdProgress();
lastContentProgress = getContentProgress();
player.removeListener(this);
player = null;
}
eventListener = null;
} }
/** /**
@ -212,9 +250,8 @@ import java.util.List;
if (adsManager != null) { if (adsManager != null) {
adsManager.destroy(); adsManager.destroy();
adsManager = null; adsManager = null;
detachPlayer();
} }
player.removeListener(this);
released = true;
} }
// AdsLoader.AdsLoadedListener implementation. // AdsLoader.AdsLoadedListener implementation.
@ -251,8 +288,8 @@ import java.util.List;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onAdEvent " + adEvent.getType()); Log.d(TAG, "onAdEvent " + adEvent.getType());
} }
if (released) { if (adsManager == null) {
// The ads manager may pass CONTENT_RESUME_REQUESTED after it is destroyed. Log.w(TAG, "Dropping ad event while detached: " + adEvent);
return; return;
} }
switch (adEvent.getType()) { switch (adEvent.getType()) {
@ -274,11 +311,15 @@ import java.util.List;
case CONTENT_PAUSE_REQUESTED: case CONTENT_PAUSE_REQUESTED:
// After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads // After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads
// before sending CONTENT_RESUME_REQUESTED. // before sending CONTENT_RESUME_REQUESTED.
pauseContentInternal(); if (player != null) {
pauseContentInternal();
}
break; break;
case SKIPPED: // Fall through. case SKIPPED: // Fall through.
case CONTENT_RESUME_REQUESTED: case CONTENT_RESUME_REQUESTED:
resumeContentInternal(); if (player != null) {
resumeContentInternal();
}
break; break;
case ALL_ADS_COMPLETED: case ALL_ADS_COMPLETED:
// Do nothing. The ads manager will be released when the source is released. // Do nothing. The ads manager will be released when the source is released.
@ -294,8 +335,10 @@ import java.util.List;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onAdError " + adErrorEvent); Log.d(TAG, "onAdError " + adErrorEvent);
} }
IOException exception = new IOException("Ad error: " + adErrorEvent, adErrorEvent.getError()); if (eventListener != null) {
eventListener.onLoadError(exception); IOException exception = new IOException("Ad error: " + adErrorEvent, adErrorEvent.getError());
eventListener.onLoadError(exception);
}
// TODO: Provide a timeline to the player if it doesn't have one yet, so the content can play. // TODO: Provide a timeline to the player if it doesn't have one yet, so the content can play.
} }
@ -303,32 +346,36 @@ import java.util.List;
@Override @Override
public VideoProgressUpdate getContentProgress() { public VideoProgressUpdate getContentProgress() {
if (pendingContentPositionMs != C.TIME_UNSET) { if (player == null) {
return lastContentProgress;
} else if (pendingContentPositionMs != C.TIME_UNSET) {
sentPendingContentPositionMs = true; sentPendingContentPositionMs = true;
return new VideoProgressUpdate(pendingContentPositionMs, contentDurationMs); return new VideoProgressUpdate(pendingContentPositionMs, contentDurationMs);
} } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
if (adGroupTimeMs == C.TIME_END_OF_SOURCE) { if (adGroupTimeMs == C.TIME_END_OF_SOURCE) {
adGroupTimeMs = contentDurationMs; adGroupTimeMs = contentDurationMs;
} }
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
return new VideoProgressUpdate(adGroupTimeMs + elapsedSinceEndMs, contentDurationMs); return new VideoProgressUpdate(adGroupTimeMs + elapsedSinceEndMs, contentDurationMs);
} } else if (player.isPlayingAd() || contentDurationMs == C.TIME_UNSET) {
if (player.isPlayingAd() || contentDurationMs == C.TIME_UNSET) {
return VideoProgressUpdate.VIDEO_TIME_NOT_READY; return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
} else {
return new VideoProgressUpdate(player.getCurrentPosition(), contentDurationMs);
} }
return new VideoProgressUpdate(player.getCurrentPosition(), contentDurationMs);
} }
// VideoAdPlayer implementation. // VideoAdPlayer implementation.
@Override @Override
public VideoProgressUpdate getAdProgress() { public VideoProgressUpdate getAdProgress() {
if (!player.isPlayingAd()) { if (player == null) {
return lastAdProgress;
} else if (!player.isPlayingAd()) {
return VideoProgressUpdate.VIDEO_TIME_NOT_READY; return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
} else {
return new VideoProgressUpdate(player.getCurrentPosition(), player.getDuration());
} }
return new VideoProgressUpdate(player.getCurrentPosition(), player.getDuration());
} }
@Override @Override
@ -352,6 +399,7 @@ import java.util.List;
@Override @Override
public void playAd() { public void playAd() {
Assertions.checkState(player != null);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "playAd"); Log.d(TAG, "playAd");
} }
@ -379,6 +427,7 @@ import java.util.List;
@Override @Override
public void stopAd() { public void stopAd() {
Assertions.checkState(player != null);
if (!playingAd) { if (!playingAd) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Ignoring unexpected stopAd"); Log.d(TAG, "Ignoring unexpected stopAd");
@ -396,7 +445,7 @@ import java.util.List;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "pauseAd"); Log.d(TAG, "pauseAd");
} }
if (released || !playingAd) { if (player == null || !playingAd) {
// This method is called after content is resumed, and may also be called after release. // This method is called after content is resumed, and may also be called after release.
return; return;
} }
@ -513,9 +562,14 @@ import java.util.List;
// Internal methods. // Internal methods.
/** private void requestAds() {
* Resumes the player, ensuring the current period is a content period by seeking if necessary. AdsRequest request = imaSdkFactory.createAdsRequest();
*/ request.setAdTagUrl(adTagUri.toString());
request.setAdDisplayContainer(adDisplayContainer);
request.setContentProgressProvider(this);
adsLoader.requestAds(request);
}
private void resumeContentInternal() { private void resumeContentInternal() {
if (contentDurationMs != C.TIME_UNSET) { if (contentDurationMs != C.TIME_UNSET) {
if (playingAd) { if (playingAd) {
@ -573,7 +627,10 @@ import java.util.List;
} }
private void updateAdPlaybackState() { private void updateAdPlaybackState() {
eventListener.onAdPlaybackState(adPlaybackState.copy()); // Ignore updates while detached. When a player is attached it will receive the latest state.
if (eventListener != null) {
eventListener.onAdPlaybackState(adPlaybackState.copy());
}
} }
private static long[] getAdGroupTimesUs(List<Float> cuePoints) { private static long[] getAdGroupTimesUs(List<Float> cuePoints) {

View File

@ -15,12 +15,9 @@
*/ */
package com.google.android.exoplayer2.ext.ima; package com.google.android.exoplayer2.ext.ima;
import android.content.Context;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
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.Timeline; import com.google.android.exoplayer2.Timeline;
@ -44,10 +41,8 @@ public final class ImaAdsMediaSource implements MediaSource {
private final MediaSource contentMediaSource; private final MediaSource contentMediaSource;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final Context context; private final ImaAdsLoader imaAdsLoader;
private final Uri adTagUri;
private final ViewGroup adUiViewGroup; private final ViewGroup adUiViewGroup;
private final ImaSdkSettings imaSdkSettings;
private final Handler mainHandler; private final Handler mainHandler;
private final AdsLoaderListener adsLoaderListener; private final AdsLoaderListener adsLoaderListener;
private final Map<MediaPeriod, MediaSource> adMediaSourceByMediaPeriod; private final Map<MediaPeriod, MediaSource> adMediaSourceByMediaPeriod;
@ -66,49 +61,20 @@ public final class ImaAdsMediaSource implements MediaSource {
private MediaSource.Listener listener; private MediaSource.Listener listener;
private IOException adLoadError; private IOException adLoadError;
// Accessed on the main thread.
private ImaAdsLoader imaAdsLoader;
/** /**
* Constructs a new source that inserts ads linearly with the content specified by * Constructs a new source that inserts ads linearly with the content specified by
* {@code contentMediaSource}. * {@code contentMediaSource}.
* *
* @param contentMediaSource The {@link MediaSource} providing the content to play. * @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param dataSourceFactory Factory for data sources used to load ad media. * @param dataSourceFactory Factory for data sources used to load ad media.
* @param context The context. * @param imaAdsLoader The loader for ads.
* @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* more information.
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad user
* interface.
*/ */
public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory, public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory,
Context context, Uri adTagUri, ViewGroup adUiViewGroup) { ImaAdsLoader imaAdsLoader, ViewGroup adUiViewGroup) {
this(contentMediaSource, dataSourceFactory, context, adTagUri, adUiViewGroup, 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 dataSourceFactory Factory for data sources used to load ad media.
* @param context The context.
* @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* more information.
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
* @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to
* use the default settings. If set, the player type and version fields may be overwritten.
*/
public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory,
Context context, Uri adTagUri, ViewGroup adUiViewGroup, ImaSdkSettings imaSdkSettings) {
this.contentMediaSource = contentMediaSource; this.contentMediaSource = contentMediaSource;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.context = context; this.imaAdsLoader = imaAdsLoader;
this.adTagUri = adTagUri;
this.adUiViewGroup = adUiViewGroup; this.adUiViewGroup = adUiViewGroup;
this.imaSdkSettings = imaSdkSettings;
mainHandler = new Handler(Looper.getMainLooper()); mainHandler = new Handler(Looper.getMainLooper());
adsLoaderListener = new AdsLoaderListener(); adsLoaderListener = new AdsLoaderListener();
adMediaSourceByMediaPeriod = new HashMap<>(); adMediaSourceByMediaPeriod = new HashMap<>();
@ -118,24 +84,23 @@ public final class ImaAdsMediaSource implements MediaSource {
} }
@Override @Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { public void prepareSource(final ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Assertions.checkArgument(isTopLevelSource); Assertions.checkArgument(isTopLevelSource);
this.listener = listener; this.listener = listener;
this.player = player; this.player = player;
playerHandler = new Handler(); playerHandler = new Handler();
mainHandler.post(new Runnable() {
@Override
public void run() {
imaAdsLoader = new ImaAdsLoader(context, adTagUri, adUiViewGroup, imaSdkSettings,
ImaAdsMediaSource.this.player, adsLoaderListener);
}
});
contentMediaSource.prepareSource(player, false, new Listener() { contentMediaSource.prepareSource(player, false, new Listener() {
@Override @Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
ImaAdsMediaSource.this.onContentSourceInfoRefreshed(timeline, manifest); ImaAdsMediaSource.this.onContentSourceInfoRefreshed(timeline, manifest);
} }
}); });
mainHandler.post(new Runnable() {
@Override
public void run() {
imaAdsLoader.attachPlayer(player, adsLoaderListener, adUiViewGroup);
}
});
} }
@Override @Override
@ -146,7 +111,9 @@ public final class ImaAdsMediaSource implements MediaSource {
contentMediaSource.maybeThrowSourceInfoRefreshError(); contentMediaSource.maybeThrowSourceInfoRefreshError();
for (MediaSource[] mediaSources : adGroupMediaSources) { for (MediaSource[] mediaSources : adGroupMediaSources) {
for (MediaSource mediaSource : mediaSources) { for (MediaSource mediaSource : mediaSources) {
mediaSource.maybeThrowSourceInfoRefreshError(); if (mediaSource != null) {
mediaSource.maybeThrowSourceInfoRefreshError();
}
} }
} }
} }
@ -201,17 +168,15 @@ public final class ImaAdsMediaSource implements MediaSource {
contentMediaSource.releaseSource(); contentMediaSource.releaseSource();
for (MediaSource[] mediaSources : adGroupMediaSources) { for (MediaSource[] mediaSources : adGroupMediaSources) {
for (MediaSource mediaSource : mediaSources) { for (MediaSource mediaSource : mediaSources) {
mediaSource.releaseSource(); if (mediaSource != null) {
mediaSource.releaseSource();
}
} }
} }
mainHandler.post(new Runnable() { mainHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
// TODO: The source will be released when the application is paused/stopped, which can occur imaAdsLoader.detachPlayer();
// if the user taps on the ad. In this case, we should keep the ads manager alive but pause
// it, instead of destroying it.
imaAdsLoader.release();
imaAdsLoader = null;
} }
}); });
} }