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:
andrewlewis 2020-03-30 17:45:45 +01:00 committed by Oliver Woodman
parent ce297d40b1
commit 378593f551
3 changed files with 281 additions and 0 deletions

View File

@ -26,6 +26,13 @@ android {
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
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
@ -37,6 +44,11 @@ dependencies {
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
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 'org.robolectric:robolectric:' + robolectricVersion
}

View 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>

View File

@ -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);
}
}
}
}