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; ...@@ -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 ...@@ -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
...@@ -191,6 +198,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -191,6 +198,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
@Override @Override
public void onDestroy() {
super.onDestroy();
releaseAdsLoader();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
...@@ -317,20 +330,19 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -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 ...@@ -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
......
...@@ -15,12 +15,9 @@ ...@@ -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 { ...@@ -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 { ...@@ -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
* {@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 * 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 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, public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory,
Context context, Uri adTagUri, ViewGroup adUiViewGroup, ImaSdkSettings imaSdkSettings) { ImaAdsLoader imaAdsLoader, ViewGroup adUiViewGroup) {
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 { ...@@ -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,10 +111,12 @@ public final class ImaAdsMediaSource implements MediaSource { ...@@ -146,10 +111,12 @@ 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) {
if (mediaSource != null) {
mediaSource.maybeThrowSourceInfoRefreshError(); mediaSource.maybeThrowSourceInfoRefreshError();
} }
} }
} }
}
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
...@@ -201,17 +168,15 @@ public final class ImaAdsMediaSource implements MediaSource { ...@@ -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) {
if (mediaSource != null) {
mediaSource.releaseSource(); 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;
} }
}); });
} }
......
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