blob: ed9130bc72124e55471a09f878ba55b9127c1c78 [file] [log] [blame]
/*
* 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);
}
}
}
}