Add some playback tests for the IMA extension
These are about 5% flaky, so for now they are excluded from continuous testing. PiperOrigin-RevId: 303760340
This commit is contained in:
parent
ce297d40b1
commit
378593f551
@ -26,6 +26,13 @@ android {
|
|||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.targetSdkVersion
|
targetSdkVersion project.ext.targetSdkVersion
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
// Enable multidex for androidTests.
|
||||||
|
multiDexEnabled true
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidTest.assets.srcDir '../../testdata/src/test/assets/'
|
||||||
}
|
}
|
||||||
|
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
@ -37,6 +44,11 @@ dependencies {
|
|||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
|
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
|
||||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||||
|
androidTestImplementation project(modulePrefix + 'testutils')
|
||||||
|
androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
|
||||||
|
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
|
||||||
|
androidTestImplementation 'com.android.support:multidex:1.0.3'
|
||||||
|
androidTestCompileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
testImplementation project(modulePrefix + 'testutils')
|
testImplementation project(modulePrefix + 'testutils')
|
||||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||||
}
|
}
|
||||||
|
38
extensions/ima/src/androidTest/AndroidManifest.xml
Normal file
38
extensions/ima/src/androidTest/AndroidManifest.xml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2020 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.google.android.exoplayer2.ext.ima.test">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
<uses-sdk/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
|
||||||
|
<activity android:name="com.google.android.exoplayer2.testutil.HostActivity"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:label="ExoPlayerTest"/>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<instrumentation
|
||||||
|
android:targetPackage="com.google.android.exoplayer2.ext.ima.test"
|
||||||
|
android:name="androidx.test.runner.AndroidJUnitRunner"/>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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 static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.rule.ActivityTestRule;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||||
|
import com.google.android.exoplayer2.Player.EventListener;
|
||||||
|
import com.google.android.exoplayer2.Player.TimelineChangeReason;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Timeline.Window;
|
||||||
|
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider;
|
||||||
|
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||||
|
import com.google.android.exoplayer2.testutil.ExoHostedTest;
|
||||||
|
import com.google.android.exoplayer2.testutil.HostActivity;
|
||||||
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Playback tests using {@link ImaAdsLoader}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class ImaPlaybackTest {
|
||||||
|
|
||||||
|
private static final long TIMEOUT_MS = 5 * 60 * C.MILLIS_PER_SECOND;
|
||||||
|
|
||||||
|
private static final String CONTENT_URI =
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4";
|
||||||
|
private static final String PREROLL_ADS_RESPONSE_FILE_NAME = "ad-responses/preroll.xml";
|
||||||
|
private static final String MIDROLL_ADS_RESPONSE_FILE_NAME = "ad-responses/midroll.xml";
|
||||||
|
|
||||||
|
private static final AdId CONTENT = new AdId(C.INDEX_UNSET, C.INDEX_UNSET);
|
||||||
|
|
||||||
|
@Rule public ActivityTestRule<HostActivity> testRule = new ActivityTestRule<>(HostActivity.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackWithPrerollAdTag_playsAdAndContent() throws Exception {
|
||||||
|
AdId[] expectedAdIds = new AdId[] {ad(0), CONTENT};
|
||||||
|
String adsResponse =
|
||||||
|
TestUtil.getString(/* context= */ testRule.getActivity(), PREROLL_ADS_RESPONSE_FILE_NAME);
|
||||||
|
ImaHostedTest hostedTest =
|
||||||
|
new ImaHostedTest(Uri.parse(CONTENT_URI), adsResponse, expectedAdIds);
|
||||||
|
|
||||||
|
testRule.getActivity().runTest(hostedTest, TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackWithMidrolls_playsAdAndContent() throws Exception {
|
||||||
|
AdId[] expectedAdIds = new AdId[] {ad(0), CONTENT, ad(1), CONTENT, ad(2), CONTENT};
|
||||||
|
String adsResponse =
|
||||||
|
TestUtil.getString(/* context= */ testRule.getActivity(), MIDROLL_ADS_RESPONSE_FILE_NAME);
|
||||||
|
ImaHostedTest hostedTest =
|
||||||
|
new ImaHostedTest(Uri.parse(CONTENT_URI), adsResponse, expectedAdIds);
|
||||||
|
|
||||||
|
testRule.getActivity().runTest(hostedTest, TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AdId ad(int groupIndex) {
|
||||||
|
return new AdId(groupIndex, /* indexInGroup= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class AdId {
|
||||||
|
|
||||||
|
public final int groupIndex;
|
||||||
|
public final int indexInGroup;
|
||||||
|
|
||||||
|
public AdId(int groupIndex, int indexInGroup) {
|
||||||
|
this.groupIndex = groupIndex;
|
||||||
|
this.indexInGroup = indexInGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + groupIndex + ", " + indexInGroup + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdId that = (AdId) o;
|
||||||
|
|
||||||
|
if (groupIndex != that.groupIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return indexInGroup == that.indexInGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = groupIndex;
|
||||||
|
result = 31 * result + indexInGroup;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ImaHostedTest extends ExoHostedTest implements EventListener {
|
||||||
|
|
||||||
|
private final Uri contentUri;
|
||||||
|
private final String adsResponse;
|
||||||
|
private final List<AdId> expectedAdIds;
|
||||||
|
private final List<AdId> seenAdIds;
|
||||||
|
private @MonotonicNonNull ImaAdsLoader imaAdsLoader;
|
||||||
|
private @MonotonicNonNull SimpleExoPlayer player;
|
||||||
|
|
||||||
|
private ImaHostedTest(Uri contentUri, String adsResponse, AdId... expectedAdIds) {
|
||||||
|
// fullPlaybackNoSeeking is false as the playback lasts longer than the content source
|
||||||
|
// duration due to ad playback, so the hosted test shouldn't assert the playing duration.
|
||||||
|
super(ImaPlaybackTest.class.getSimpleName(), /* fullPlaybackNoSeeking= */ false);
|
||||||
|
this.contentUri = contentUri;
|
||||||
|
this.adsResponse = adsResponse;
|
||||||
|
this.expectedAdIds = Arrays.asList(expectedAdIds);
|
||||||
|
seenAdIds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SimpleExoPlayer buildExoPlayer(
|
||||||
|
HostActivity host, Surface surface, MappingTrackSelector trackSelector) {
|
||||||
|
player = super.buildExoPlayer(host, surface, trackSelector);
|
||||||
|
player.addAnalyticsListener(
|
||||||
|
new AnalyticsListener() {
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(EventTime eventTime, @TimelineChangeReason int reason) {
|
||||||
|
maybeUpdateSeenAdIdentifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(
|
||||||
|
EventTime eventTime, @DiscontinuityReason int reason) {
|
||||||
|
maybeUpdateSeenAdIdentifiers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Context context = host.getApplicationContext();
|
||||||
|
imaAdsLoader = new ImaAdsLoader.Builder(context).buildForAdsResponse(adsResponse);
|
||||||
|
imaAdsLoader.setPlayer(player);
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MediaSource buildSource(
|
||||||
|
HostActivity host,
|
||||||
|
String userAgent,
|
||||||
|
DrmSessionManager drmSessionManager,
|
||||||
|
FrameLayout overlayFrameLayout) {
|
||||||
|
Context context = host.getApplicationContext();
|
||||||
|
DataSource.Factory dataSourceFactory =
|
||||||
|
new DefaultDataSourceFactory(
|
||||||
|
context, Util.getUserAgent(context, ImaPlaybackTest.class.getSimpleName()));
|
||||||
|
MediaSource contentMediaSource =
|
||||||
|
DefaultMediaSourceFactory.newInstance(context)
|
||||||
|
.createMediaSource(MediaItem.fromUri(contentUri));
|
||||||
|
return new AdsMediaSource(
|
||||||
|
contentMediaSource,
|
||||||
|
dataSourceFactory,
|
||||||
|
Assertions.checkNotNull(imaAdsLoader),
|
||||||
|
new AdViewProvider() {
|
||||||
|
@Override
|
||||||
|
public ViewGroup getAdViewGroup() {
|
||||||
|
return overlayFrameLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View[] getAdOverlayViews() {
|
||||||
|
return new View[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assertPassed(DecoderCounters audioCounters, DecoderCounters videoCounters) {
|
||||||
|
assertThat(seenAdIds).isEqualTo(expectedAdIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateSeenAdIdentifiers() {
|
||||||
|
if (Assertions.checkNotNull(player)
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getWindow(/* windowIndex= */ 0, new Window())
|
||||||
|
.isPlaceholder) {
|
||||||
|
// The window is still an initial placeholder so do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AdId adId = new AdId(player.getCurrentAdGroupIndex(), player.getCurrentAdIndexInAdGroup());
|
||||||
|
if (seenAdIds.isEmpty() || !seenAdIds.get(seenAdIds.size() - 1).equals(adId)) {
|
||||||
|
seenAdIds.add(adId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user