Commit 0b58c336 by andrewlewis Committed by Oliver Woodman

Handle detaching and reattaching the ads loader

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161526026
parent 94683d1e
......@@ -73,6 +73,7 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
......@@ -124,6 +125,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
private int resumeWindow;
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
@Override
......@@ -191,6 +198,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
@Override
public void onDestroy() {
super.onDestroy();
releaseAdsLoader();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
......@@ -317,20 +330,19 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA);
if (adTagUriString != null) {
Uri adTagUri = Uri.parse(adTagUriString);
ViewGroup adOverlayViewGroup = new FrameLayout(this);
// Load the extension source using reflection so that demo app doesn't have to depend on it.
if (!adTagUri.equals(loadedAdTagUri)) {
releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
try {
Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsMediaSource");
Constructor<?> constructor = clazz.getConstructor(MediaSource.class,
DataSource.Factory.class, Context.class, Uri.class, ViewGroup.class);
mediaSource = (MediaSource) constructor.newInstance(mediaSource,
mediaDataSourceFactory, this, adTagUri, adOverlayViewGroup);
mediaSource = createAdsMediaSource(mediaSource, Uri.parse(adTagUriString));
// The demo app has a non-null overlay frame layout.
simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup);
} 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);
}
} else {
releaseAdsLoader();
}
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
......@@ -429,6 +441,47 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
.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
@Override
......
......@@ -15,12 +15,9 @@
*/
package com.google.android.exoplayer2.ext.ima;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.ViewGroup;
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
......@@ -44,10 +41,8 @@ public final class ImaAdsMediaSource implements MediaSource {
private final MediaSource contentMediaSource;
private final DataSource.Factory dataSourceFactory;
private final Context context;
private final Uri adTagUri;
private final ImaAdsLoader imaAdsLoader;
private final ViewGroup adUiViewGroup;
private final ImaSdkSettings imaSdkSettings;
private final Handler mainHandler;
private final AdsLoaderListener adsLoaderListener;
private final Map<MediaPeriod, MediaSource> adMediaSourceByMediaPeriod;
......@@ -66,49 +61,20 @@ public final class ImaAdsMediaSource implements MediaSource {
private MediaSource.Listener listener;
private IOException adLoadError;
// Accessed on the main thread.
private ImaAdsLoader imaAdsLoader;
/**
* 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 user
* interface.
*/
public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory,
Context context, Uri adTagUri, 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.
* @param imaAdsLoader The loader for ads.
*/
public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory,
Context context, Uri adTagUri, ViewGroup adUiViewGroup, ImaSdkSettings imaSdkSettings) {
ImaAdsLoader imaAdsLoader, ViewGroup adUiViewGroup) {
this.contentMediaSource = contentMediaSource;
this.dataSourceFactory = dataSourceFactory;
this.context = context;
this.adTagUri = adTagUri;
this.imaAdsLoader = imaAdsLoader;
this.adUiViewGroup = adUiViewGroup;
this.imaSdkSettings = imaSdkSettings;
mainHandler = new Handler(Looper.getMainLooper());
adsLoaderListener = new AdsLoaderListener();
adMediaSourceByMediaPeriod = new HashMap<>();
......@@ -118,24 +84,23 @@ public final class ImaAdsMediaSource implements MediaSource {
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
public void prepareSource(final ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Assertions.checkArgument(isTopLevelSource);
this.listener = listener;
this.player = player;
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() {
@Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
ImaAdsMediaSource.this.onContentSourceInfoRefreshed(timeline, manifest);
}
});
mainHandler.post(new Runnable() {
@Override
public void run() {
imaAdsLoader.attachPlayer(player, adsLoaderListener, adUiViewGroup);
}
});
}
@Override
......@@ -146,10 +111,12 @@ public final class ImaAdsMediaSource implements MediaSource {
contentMediaSource.maybeThrowSourceInfoRefreshError();
for (MediaSource[] mediaSources : adGroupMediaSources) {
for (MediaSource mediaSource : mediaSources) {
if (mediaSource != null) {
mediaSource.maybeThrowSourceInfoRefreshError();
}
}
}
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
......@@ -201,17 +168,15 @@ public final class ImaAdsMediaSource implements MediaSource {
contentMediaSource.releaseSource();
for (MediaSource[] mediaSources : adGroupMediaSources) {
for (MediaSource mediaSource : mediaSources) {
if (mediaSource != null) {
mediaSource.releaseSource();
}
}
}
mainHandler.post(new Runnable() {
@Override
public void run() {
// TODO: The source will be released when the application is paused/stopped, which can occur
// 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;
imaAdsLoader.detachPlayer();
}
});
}
......
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