Commit cdac347f by andrewlewis Committed by Oliver Woodman

Open source IMA extension

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=156312761
parent 4359d443
...@@ -52,6 +52,7 @@ dependencies { ...@@ -52,6 +52,7 @@ dependencies {
compile project(':library-ui') compile project(':library-ui')
withExtensionsCompile project(path: ':extension-ffmpeg') withExtensionsCompile project(path: ':extension-ffmpeg')
withExtensionsCompile project(path: ':extension-flac') withExtensionsCompile project(path: ':extension-flac')
withExtensionsCompile project(path: ':extension-ima')
withExtensionsCompile project(path: ':extension-opus') withExtensionsCompile project(path: ':extension-opus')
withExtensionsCompile project(path: ':extension-vp9') withExtensionsCompile project(path: ':extension-vp9')
} }
...@@ -452,5 +452,85 @@ ...@@ -452,5 +452,85 @@
] ]
} }
] ]
},
{
"name": "IMA sample ad tags",
"samples": [
{
"name": "Single inline linear",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
},
{
"name": "Single skippable inline",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator="
},
{
"name": "Single redirect linear",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirectlinear&correlator="
},
{
"name": "Single redirect error",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirecterror&nofb=1&correlator="
},
{
"name": "Single redirect broken (fallback)",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirecterror&correlator="
},
{
"name": "VMAP pre-roll",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonly&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-roll + bumper",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonlybumper&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP post-roll",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonly&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP post-roll + bumper",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonlybumper&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-, mid- and post-rolls, single ads",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
},
{
"name": "VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpod&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-roll single ad, mid-roll optimized pod with 3 ads, post-roll single ad",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpod&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad (bumpers around all ad breaks)",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpodbumper&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-roll single ad, mid-roll optimized pod with 3 ads, post-roll single ad (bumpers around all ad breaks)",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpodbumper&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad",
"uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator="
}
]
} }
] ]
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
...@@ -26,7 +27,9 @@ import android.text.TextUtils; ...@@ -26,7 +27,9 @@ import android.text.TextUtils;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
...@@ -69,6 +72,7 @@ import com.google.android.exoplayer2.upstream.DataSource; ...@@ -69,6 +72,7 @@ import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 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.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
...@@ -92,6 +96,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -92,6 +96,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
"com.google.android.exoplayer.demo.action.VIEW_LIST"; "com.google.android.exoplayer.demo.action.VIEW_LIST";
public static final String URI_LIST_EXTRA = "uri_list"; public static final String URI_LIST_EXTRA = "uri_list";
public static final String EXTENSION_LIST_EXTRA = "extension_list"; public static final String EXTENSION_LIST_EXTRA = "extension_list";
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER; private static final CookieManager DEFAULT_COOKIE_MANAGER;
...@@ -312,6 +317,26 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -312,6 +317,26 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources); : new ConcatenatingMediaSource(mediaSources);
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.
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);
// The demo app has a non-null overlay frame layout.
simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup);
// Show a multi-window time bar, which will include ad break position markers.
simpleExoPlayerView.setShowMultiWindowTimeBar(true);
} 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);
}
}
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) { if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition); player.seekTo(resumeWindow, resumePosition);
......
...@@ -184,6 +184,7 @@ public class SampleChooserActivity extends Activity { ...@@ -184,6 +184,7 @@ public class SampleChooserActivity extends Activity {
String[] drmKeyRequestProperties = null; String[] drmKeyRequestProperties = null;
boolean preferExtensionDecoders = false; boolean preferExtensionDecoders = false;
ArrayList<UriSample> playlistSamples = null; ArrayList<UriSample> playlistSamples = null;
String adTagUri = null;
reader.beginObject(); reader.beginObject();
while (reader.hasNext()) { while (reader.hasNext()) {
...@@ -233,6 +234,9 @@ public class SampleChooserActivity extends Activity { ...@@ -233,6 +234,9 @@ public class SampleChooserActivity extends Activity {
} }
reader.endArray(); reader.endArray();
break; break;
case "ad_tag_uri":
adTagUri = reader.nextString();
break;
default: default:
throw new ParserException("Unsupported attribute name: " + name); throw new ParserException("Unsupported attribute name: " + name);
} }
...@@ -246,7 +250,7 @@ public class SampleChooserActivity extends Activity { ...@@ -246,7 +250,7 @@ public class SampleChooserActivity extends Activity {
preferExtensionDecoders, playlistSamplesArray); preferExtensionDecoders, playlistSamplesArray);
} else { } else {
return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties, return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
preferExtensionDecoders, uri, extension); preferExtensionDecoders, uri, extension, adTagUri);
} }
} }
...@@ -402,13 +406,15 @@ public class SampleChooserActivity extends Activity { ...@@ -402,13 +406,15 @@ public class SampleChooserActivity extends Activity {
public final String uri; public final String uri;
public final String extension; public final String extension;
public final String adTagUri;
public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl, public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri, String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri,
String extension) { String extension, String adTagUri) {
super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders); super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
this.uri = uri; this.uri = uri;
this.extension = extension; this.extension = extension;
this.adTagUri = adTagUri;
} }
@Override @Override
...@@ -416,6 +422,7 @@ public class SampleChooserActivity extends Activity { ...@@ -416,6 +422,7 @@ public class SampleChooserActivity extends Activity {
return super.buildIntent(context) return super.buildIntent(context)
.setData(Uri.parse(uri)) .setData(Uri.parse(uri))
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension) .putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
.putExtra(PlayerActivity.AD_TAG_URI_EXTRA, adTagUri)
.setAction(PlayerActivity.ACTION_VIEW); .setAction(PlayerActivity.ACTION_VIEW);
} }
......
...@@ -58,4 +58,6 @@ ...@@ -58,4 +58,6 @@
<string name="sample_list_load_error">One or more sample lists failed to load</string> <string name="sample_list_load_error">One or more sample lists failed to load</string>
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
</resources> </resources>
# ExoPlayer IMA extension #
## Description ##
The IMA extension is a [MediaSource][] implementation wrapping the
[Interactive Media Ads SDK for Android][IMA]. You can use it to insert ads
alongside content.
[IMA]: https://developers.google.com/interactive-media-ads/docs/sdks/android/
[MediaSource]: https://github.com/google/ExoPlayer/blob/release-v2/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java
## Using the extension ##
Pass a single-window content `MediaSource` to `ImaAdsMediaSource`'s constructor,
along with a `ViewGroup` that is on top of the player and the ad tag URI to
show. The IMA documentation includes some [sample ad tags][] for testing. Then
pass the `ImaAdsMediaSource` to `ExoPlayer.prepare`.
[sample ad tags]: https://developers.google.com/interactive-media-ads/docs/sdks/android/tags
## Known issues ##
This is a preview version with some known issues:
* Midroll ads are not yet fully supported. In particular, seeking with midroll
ads is not yet supported. Played ad periods are not removed. Also, `playAd` and
`AD_STARTED` events are sometimes delayed, meaning that midroll ads take a long
time to start and the ad overlay does not show immediately.
* Tapping the 'More info' button on an ad in the demo app will pause the
activity, which destroys the ImaAdsMediaSource. Played ad breaks will be
shown to the user again if the demo app returns to the foreground.
apply plugin: 'com.android.library'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion 14
targetSdkVersion project.ext.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
compile project(':library-core')
compile 'com.android.support:support-annotations:' + supportLibraryVersion
compile 'com.google.ads.interactivemedia.v3:interactivemedia:3.6.0'
compile 'com.google.android.gms:play-services-ads:10.2.4'
// There exists a dependency chain:
// com.google.android.gms:play-services-ads:10.2.4
// |-> com.google.android.gms:play-services-ads-lite:10.2.4
// |-> com.google.android.gms:play-services-basement:10.2.4
// |-> com.android.support:support-v4:24.0.0
// The support-v4:24.0.0 module directly includes older versions of the same
// classes as com.android.support:support-annotations. We need to manually
// force it to the version we're using to avoid a compilation failure. This
// will become unnecessary when the support-v4 dependency in the chain above
// has been updated to 24.2.0 or later.
compile 'com.android.support:support-v4:' + supportLibraryVersion
androidTestCompile project(':library')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
androidTestCompile 'com.android.support.test:runner:' + testSupportLibraryVersion
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.ext.ima">
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
</manifest>
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.ima;
import android.util.Pair;
import com.google.ads.interactivemedia.v3.api.Ad;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
/**
* A {@link Timeline} for {@link ImaAdsMediaSource}.
*/
/* package */ final class AdTimeline extends Timeline {
private static final Object AD_ID = new Object();
/**
* Builder for ad timelines.
*/
public static final class Builder {
private final Timeline contentTimeline;
private final long contentDurationUs;
private final ArrayList<Boolean> isAd;
private final ArrayList<Ad> ads;
private final ArrayList<Long> startTimesUs;
private final ArrayList<Long> endTimesUs;
private final ArrayList<Object> uids;
/**
* Creates a new ad timeline builder using the specified {@code contentTimeline} as the timeline
* of the content within which to insert ad breaks.
*
* @param contentTimeline The timeline of the content within which to insert ad breaks.
*/
public Builder(Timeline contentTimeline) {
this.contentTimeline = contentTimeline;
contentDurationUs = contentTimeline.getPeriod(0, new Period()).durationUs;
isAd = new ArrayList<>();
ads = new ArrayList<>();
startTimesUs = new ArrayList<>();
endTimesUs = new ArrayList<>();
uids = new ArrayList<>();
}
/**
* Adds an ad period. Each individual ad in an ad pod is represented by a separate ad period.
*
* @param ad The {@link Ad} instance representing the ad break, or {@code null} if not known.
* @param adBreakIndex The index of the ad break that contains the ad in the timeline.
* @param adIndexInAdBreak The index of the ad in its ad break.
* @param durationUs The duration of the ad, in microseconds. May be {@link C#TIME_UNSET}.
* @return The builder.
*/
public Builder addAdPeriod(Ad ad, int adBreakIndex, int adIndexInAdBreak, long durationUs) {
isAd.add(true);
ads.add(ad);
startTimesUs.add(0L);
endTimesUs.add(durationUs);
uids.add(Pair.create(adBreakIndex, adIndexInAdBreak));
return this;
}
/**
* Adds a content period.
*
* @param startTimeUs The start time of the period relative to the start of the content
* timeline, in microseconds.
* @param endTimeUs The end time of the period relative to the start of the content timeline, in
* microseconds. May be {@link C#TIME_UNSET} to include the rest of the content.
* @return The builder.
*/
public Builder addContent(long startTimeUs, long endTimeUs) {
ads.add(null);
isAd.add(false);
startTimesUs.add(startTimeUs);
endTimesUs.add(endTimeUs == C.TIME_UNSET ? contentDurationUs : endTimeUs);
uids.add(Pair.create(startTimeUs, endTimeUs));
return this;
}
/**
* Builds and returns the ad timeline.
*/
public AdTimeline build() {
int periodCount = uids.size();
Assertions.checkState(periodCount > 0);
Ad[] ads = new Ad[periodCount];
boolean[] isAd = new boolean[periodCount];
long[] startTimesUs = new long[periodCount];
long[] endTimesUs = new long[periodCount];
for (int i = 0; i < periodCount; i++) {
ads[i] = this.ads.get(i);
isAd[i] = this.isAd.get(i);
startTimesUs[i] = this.startTimesUs.get(i);
endTimesUs[i] = this.endTimesUs.get(i);
}
Object[] uids = this.uids.toArray(new Object[periodCount]);
return new AdTimeline(contentTimeline, isAd, ads, startTimesUs, endTimesUs, uids);
}
}
private final Period contentPeriod;
private final Window contentWindow;
private final boolean[] isAd;
private final Ad[] ads;
private final long[] startTimesUs;
private final long[] endTimesUs;
private final Object[] uids;
private AdTimeline(Timeline contentTimeline, boolean[] isAd, Ad[] ads, long[] startTimesUs,
long[] endTimesUs, Object[] uids) {
contentWindow = contentTimeline.getWindow(0, new Window(), true);
contentPeriod = contentTimeline.getPeriod(0, new Period(), true);
this.isAd = isAd;
this.ads = ads;
this.startTimesUs = startTimesUs;
this.endTimesUs = endTimesUs;
this.uids = uids;
}
/**
* Returns whether the period at {@code index} contains ad media.
*/
public boolean isPeriodAd(int index) {
return isAd[index];
}
/**
* Returns the duration of the content within which ads have been inserted, in microseconds.
*/
public long getContentDurationUs() {
return contentPeriod.durationUs;
}
/**
* Returns the start time of the period at {@code periodIndex} relative to the start of the
* content, in microseconds.
*
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not a content
* period.
*/
public long getContentStartTimeUs(int periodIndex) {
Assertions.checkArgument(!isAd[periodIndex]);
return startTimesUs[periodIndex];
}
/**
* Returns the end time of the period at {@code periodIndex} relative to the start of the content,
* in microseconds.
*
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not a content
* period.
*/
public long getContentEndTimeUs(int periodIndex) {
Assertions.checkArgument(!isAd[periodIndex]);
return endTimesUs[periodIndex];
}
/**
* Returns the index of the ad break to which the period at {@code periodIndex} belongs.
*
* @param periodIndex The period index.
* @return The index of the ad break to which the period belongs.
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not an ad.
*/
public int getAdBreakIndex(int periodIndex) {
Assertions.checkArgument(isAd[periodIndex]);
int adBreakIndex = 0;
for (int i = 1; i < periodIndex; i++) {
if (!isAd[i] && isAd[i - 1]) {
adBreakIndex++;
}
}
return adBreakIndex;
}
/**
* Returns the index of the ad at {@code periodIndex} in its ad break.
*
* @param periodIndex The period index.
* @return The index of the ad at {@code periodIndex} in its ad break.
* @throws IllegalArgumentException Thrown if the period at {@code periodIndex} is not an ad.
*/
public int getAdIndexInAdBreak(int periodIndex) {
Assertions.checkArgument(isAd[periodIndex]);
int adIndex = 0;
for (int i = 0; i < periodIndex; i++) {
if (isAd[i]) {
adIndex++;
} else {
adIndex = 0;
}
}
return adIndex;
}
@Override
public int getWindowCount() {
return uids.length;
}
@Override
public int getNextWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
if (repeatMode == ExoPlayer.REPEAT_MODE_ONE) {
repeatMode = ExoPlayer.REPEAT_MODE_ALL;
}
return super.getNextWindowIndex(windowIndex, repeatMode);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @ExoPlayer.RepeatMode int repeatMode) {
if (repeatMode == ExoPlayer.REPEAT_MODE_ONE) {
repeatMode = ExoPlayer.REPEAT_MODE_ALL;
}
return super.getPreviousWindowIndex(windowIndex, repeatMode);
}
@Override
public Window getWindow(int index, Window window, boolean setIds,
long defaultPositionProjectionUs) {
long startTimeUs = startTimesUs[index];
long durationUs = endTimesUs[index] - startTimeUs;
if (isAd[index]) {
window.set(ads[index], C.TIME_UNSET, C.TIME_UNSET, false, false, 0L, durationUs, index, index,
0L);
} else {
window.set(contentWindow.id, contentWindow.presentationStartTimeMs + C.usToMs(startTimeUs),
contentWindow.windowStartTimeMs + C.usToMs(startTimeUs), contentWindow.isSeekable, false,
0L, durationUs, index, index, 0L);
}
return window;
}
@Override
public int getPeriodCount() {
return uids.length;
}
@Override
public Period getPeriod(int index, Period period, boolean setIds) {
Object id = setIds ? (isAd[index] ? AD_ID : contentPeriod.id) : null;
return period.set(id, uids[index], index, endTimesUs[index] - startTimesUs[index], 0,
isAd[index]);
}
@Override
public int getIndexOfPeriod(Object uid) {
for (int i = 0; i < uids.length; i++) {
if (Util.areEqual(uid, uids[i])) {
return i;
}
}
return C.INDEX_UNSET;
}
}
...@@ -23,6 +23,7 @@ include ':playbacktests' ...@@ -23,6 +23,7 @@ include ':playbacktests'
include ':extension-ffmpeg' include ':extension-ffmpeg'
include ':extension-flac' include ':extension-flac'
include ':extension-gvr' include ':extension-gvr'
include ':extension-ima'
include ':extension-okhttp' include ':extension-okhttp'
include ':extension-opus' include ':extension-opus'
include ':extension-vp9' include ':extension-vp9'
...@@ -38,6 +39,7 @@ project(':library-ui').projectDir = new File(settingsDir, 'library/ui') ...@@ -38,6 +39,7 @@ project(':library-ui').projectDir = new File(settingsDir, 'library/ui')
project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg') project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg')
project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac') project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac')
project(':extension-gvr').projectDir = new File(settingsDir, 'extensions/gvr') project(':extension-gvr').projectDir = new File(settingsDir, 'extensions/gvr')
project(':extension-ima').projectDir = new File(settingsDir, 'extensions/ima')
project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp') project(':extension-okhttp').projectDir = new File(settingsDir, 'extensions/okhttp')
project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus') project(':extension-opus').projectDir = new File(settingsDir, 'extensions/opus')
project(':extension-vp9').projectDir = new File(settingsDir, 'extensions/vp9') project(':extension-vp9').projectDir = new File(settingsDir, 'extensions/vp9')
......
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