Support non-extractor ads in AdsMediaSource and demo apps

Issue: #3302

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=178615074
This commit is contained in:
andrewlewis 2017-12-11 07:19:53 -08:00 committed by Oliver Woodman
parent 2e3667eeff
commit a4ae206ebe
5 changed files with 136 additions and 32 deletions

View File

@ -44,6 +44,8 @@
* Use surfaceless context for secure DummySurface, if available * Use surfaceless context for secure DummySurface, if available
([#3558](https://github.com/google/ExoPlayer/issues/3558)). ([#3558](https://github.com/google/ExoPlayer/issues/3558)).
* IMA extension: * IMA extension:
* Support non-ExtractorMediaSource ads
([#3302](https://github.com/google/ExoPlayer/issues/3302)).
* Skip ads before the ad preceding the player's initial seek position * Skip ads before the ad preceding the player's initial seek position
([#3527](https://github.com/google/ExoPlayer/issues/3527)). ([#3527](https://github.com/google/ExoPlayer/issues/3527)).
* Fix ad loading when there is no preroll. * Fix ad loading when there is no preroll.

View File

@ -43,5 +43,7 @@ android {
dependencies { dependencies {
compile project(modulePrefix + 'library-core') compile project(modulePrefix + 'library-core')
compile project(modulePrefix + 'library-ui') compile project(modulePrefix + 'library-ui')
compile project(modulePrefix + 'library-dash')
compile project(modulePrefix + 'library-hls')
compile project(modulePrefix + 'extension-ima') compile project(modulePrefix + 'extension-ima')
} }

View File

@ -17,13 +17,21 @@ package com.google.android.exoplayer2.imademo;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.ContentType;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
@ -35,12 +43,12 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** /** Manages the {@link ExoPlayer}, the IMA plugin and all video playback. */
* Manages the {@link ExoPlayer}, the IMA plugin and all video playback. /* package */ final class PlayerManager implements AdsMediaSource.MediaSourceFactory {
*/
/* package */ final class PlayerManager {
private final ImaAdsLoader adsLoader; private final ImaAdsLoader adsLoader;
private final DataSource.Factory manifestDataSourceFactory;
private final DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private long contentPosition; private long contentPosition;
@ -48,6 +56,14 @@ import com.google.android.exoplayer2.util.Util;
public PlayerManager(Context context) { public PlayerManager(Context context) {
String adTag = context.getString(R.string.ad_tag_url); String adTag = context.getString(R.string.ad_tag_url);
adsLoader = new ImaAdsLoader(context, Uri.parse(adTag)); adsLoader = new ImaAdsLoader(context, Uri.parse(adTag));
manifestDataSourceFactory =
new DefaultDataSourceFactory(
context, Util.getUserAgent(context, context.getString(R.string.application_name)));
mediaDataSourceFactory =
new DefaultDataSourceFactory(
context,
Util.getUserAgent(context, context.getString(R.string.application_name)),
new DefaultBandwidthMeter());
} }
public void init(Context context, SimpleExoPlayerView simpleExoPlayerView) { public void init(Context context, SimpleExoPlayerView simpleExoPlayerView) {
@ -74,8 +90,14 @@ import com.google.android.exoplayer2.util.Util;
.createMediaSource(Uri.parse(contentUrl)); .createMediaSource(Uri.parse(contentUrl));
// Compose the content media source into a new AdsMediaSource with both ads and content. // Compose the content media source into a new AdsMediaSource with both ads and content.
MediaSource mediaSourceWithAds = new AdsMediaSource(contentMediaSource, dataSourceFactory, MediaSource mediaSourceWithAds =
adsLoader, simpleExoPlayerView.getOverlayFrameLayout()); new AdsMediaSource(
contentMediaSource,
/* adMediaSourceFactory= */ this,
adsLoader,
simpleExoPlayerView.getOverlayFrameLayout(),
/* eventHandler= */ null,
/* eventListener= */ null);
// Prepare the player with the source. // Prepare the player with the source.
player.seekTo(contentPosition); player.seekTo(contentPosition);
@ -99,4 +121,32 @@ import com.google.android.exoplayer2.util.Util;
adsLoader.release(); adsLoader.release();
} }
// AdsMediaSource.MediaSourceFactory implementation.
@Override
public MediaSource createMediaSource(
Uri uri, @Nullable Handler handler, @Nullable MediaSourceEventListener listener) {
@ContentType int type = Util.inferContentType(uri);
switch (type) {
case C.TYPE_DASH:
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
manifestDataSourceFactory)
.createMediaSource(uri, handler, listener);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, handler, listener);
case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, handler, listener);
case C.TYPE_SS:
default:
throw new IllegalStateException("Unsupported type: " + type);
}
}
@Override
public int[] getSupportedTypes() {
return new int[] {C.TYPE_DASH, C.TYPE_HLS, C.TYPE_OTHER};
}
} }

View File

@ -23,6 +23,7 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
@ -52,6 +53,7 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
@ -332,7 +334,7 @@ public class PlayerActivity extends Activity implements OnClickListener,
} }
MediaSource[] mediaSources = new MediaSource[uris.length]; MediaSource[] mediaSources = new MediaSource[uris.length];
for (int i = 0; i < uris.length; i++) { for (int i = 0; i < uris.length; i++) {
mediaSources[i] = buildMediaSource(uris[i], extensions[i]); mediaSources[i] = buildMediaSource(uris[i], extensions[i], mainHandler, eventLogger);
} }
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources); : new ConcatenatingMediaSource(mediaSources);
@ -360,26 +362,30 @@ public class PlayerActivity extends Activity implements OnClickListener,
updateButtonVisibilities(); updateButtonVisibilities();
} }
private MediaSource buildMediaSource(Uri uri, String overrideExtension) { private MediaSource buildMediaSource(
Uri uri,
String overrideExtension,
@Nullable Handler handler,
@Nullable MediaSourceEventListener listener) {
@ContentType int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) @ContentType int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)
: Util.inferContentType("." + overrideExtension); : Util.inferContentType("." + overrideExtension);
switch (type) { switch (type) {
case C.TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false))
.createMediaSource(uri, mainHandler, eventLogger);
case C.TYPE_DASH: case C.TYPE_DASH:
return new DashMediaSource.Factory( return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false)) buildDataSourceFactory(false))
.createMediaSource(uri, mainHandler, eventLogger); .createMediaSource(uri, handler, listener);
case C.TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false))
.createMediaSource(uri, handler, listener);
case C.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory) return new HlsMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, mainHandler, eventLogger); .createMediaSource(uri, handler, listener);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(mediaDataSourceFactory) return new ExtractorMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, mainHandler, eventLogger); .createMediaSource(uri, handler, listener);
default: { default: {
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }
@ -466,8 +472,22 @@ public class PlayerActivity extends Activity implements OnClickListener,
// The demo app has a non-null overlay frame layout. // The demo app has a non-null overlay frame layout.
simpleExoPlayerView.getOverlayFrameLayout().addView(adUiViewGroup); simpleExoPlayerView.getOverlayFrameLayout().addView(adUiViewGroup);
} }
return new AdsMediaSource(mediaSource, mediaDataSourceFactory, adsLoader, adUiViewGroup, AdsMediaSource.MediaSourceFactory adMediaSourceFactory =
mainHandler, eventLogger); new AdsMediaSource.MediaSourceFactory() {
@Override
public MediaSource createMediaSource(
Uri uri, @Nullable Handler handler, @Nullable MediaSourceEventListener listener) {
return PlayerActivity.this.buildMediaSource(
uri, /* overrideExtension= */ null, handler, listener);
}
@Override
public int[] getSupportedTypes() {
return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
}
};
return new AdsMediaSource(
mediaSource, adMediaSourceFactory, adsLoader, adUiViewGroup, mainHandler, eventLogger);
} }
private void releaseAdsLoader() { private void releaseAdsLoader() {

View File

@ -96,13 +96,13 @@ public final class AdsMediaSource implements MediaSource {
private static final String TAG = "AdsMediaSource"; private static final String TAG = "AdsMediaSource";
private final MediaSource contentMediaSource; private final MediaSource contentMediaSource;
private final MediaSourceFactory adMediaSourceFactory;
private final AdsLoader adsLoader; private final AdsLoader adsLoader;
private final ViewGroup adUiViewGroup; private final ViewGroup adUiViewGroup;
@Nullable private final Handler eventHandler; @Nullable private final Handler eventHandler;
@Nullable private final EventListener eventListener; @Nullable private final EventListener eventListener;
private final Handler mainHandler; private final Handler mainHandler;
private final ComponentListener componentListener; private final ComponentListener componentListener;
private final MediaSourceFactory adMediaSourceFactory;
private final Map<MediaSource, List<DeferredMediaPeriod>> deferredMediaPeriodByAdMediaSource; private final Map<MediaSource, List<DeferredMediaPeriod>> deferredMediaPeriodByAdMediaSource;
private final Timeline.Period period; private final Timeline.Period period;
@ -119,28 +119,31 @@ public final class AdsMediaSource implements MediaSource {
private MediaSource.Listener listener; private MediaSource.Listener listener;
/** /**
* 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
* {@code contentMediaSource}. * contentMediaSource}. Ad media is loaded using {@link ExtractorMediaSource}.
* <p>
* Ad media is loaded using {@link ExtractorMediaSource}. If {@code eventListener} is
* non-{@code null} it will be notified of both ad tag and ad media load errors.
* *
* @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 adsLoader The loader for ads. * @param adsLoader The loader for ads.
* @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.
*/ */
public AdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory, public AdsMediaSource(
AdsLoader adsLoader, ViewGroup adUiViewGroup) { MediaSource contentMediaSource,
this(contentMediaSource, dataSourceFactory, adsLoader, adUiViewGroup, null, null); DataSource.Factory dataSourceFactory,
AdsLoader adsLoader,
ViewGroup adUiViewGroup) {
this(
contentMediaSource,
dataSourceFactory,
adsLoader,
adUiViewGroup,
/* eventHandler= */ null,
/* eventListener= */ null);
} }
/** /**
* Constructs a new source that inserts ads linearly with the content specified by {@code * Constructs a new source that inserts ads linearly with the content specified by {@code
* contentMediaSource}. * contentMediaSource}. Ad media is loaded using {@link ExtractorMediaSource}.
*
* <p>Ad media is loaded using {@link ExtractorMediaSource}. If {@code eventListener} is
* non-{@code null} it will be notified of both ad tag and ad media load errors.
* *
* @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.
@ -156,14 +159,41 @@ public final class AdsMediaSource implements MediaSource {
ViewGroup adUiViewGroup, ViewGroup adUiViewGroup,
@Nullable Handler eventHandler, @Nullable Handler eventHandler,
@Nullable EventListener eventListener) { @Nullable EventListener eventListener) {
this(
contentMediaSource,
new ExtractorMediaSource.Factory(dataSourceFactory),
adsLoader,
adUiViewGroup,
eventHandler,
eventListener);
}
/**
* 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.
* @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.
*/
public AdsMediaSource(
MediaSource contentMediaSource,
MediaSourceFactory adMediaSourceFactory,
AdsLoader adsLoader,
ViewGroup adUiViewGroup,
@Nullable Handler eventHandler,
@Nullable EventListener eventListener) {
this.contentMediaSource = contentMediaSource; this.contentMediaSource = contentMediaSource;
this.adMediaSourceFactory = adMediaSourceFactory;
this.adsLoader = adsLoader; this.adsLoader = adsLoader;
this.adUiViewGroup = adUiViewGroup; this.adUiViewGroup = adUiViewGroup;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
mainHandler = new Handler(Looper.getMainLooper()); mainHandler = new Handler(Looper.getMainLooper());
componentListener = new ComponentListener(); componentListener = new ComponentListener();
adMediaSourceFactory = new ExtractorMediaSource.Factory(dataSourceFactory);
deferredMediaPeriodByAdMediaSource = new HashMap<>(); deferredMediaPeriodByAdMediaSource = new HashMap<>();
period = new Timeline.Period(); period = new Timeline.Period();
adGroupMediaSources = new MediaSource[0][]; adGroupMediaSources = new MediaSource[0][];