blob: 27d6b086dcda21e34d2e8106e426ddcf33dff812 [file] [log] [blame]
/*
* Copyright (C) 2018 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.testutil;
import static com.google.common.truth.Truth.assertThat;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.FilterableManifest;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.ConditionVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/** Assertion methods for {@link MediaPeriod}. */
public final class MediaPeriodAsserts {
/**
* Interface to create media periods for testing based on a {@link FilterableManifest}.
*
* @param <T> The type of {@link FilterableManifest}.
*/
public interface FilterableManifestMediaPeriodFactory<T extends FilterableManifest<T>> {
/** Returns media period based on the provided filterable manifest. */
MediaPeriod createMediaPeriod(T manifest, int periodIndex);
}
private MediaPeriodAsserts() {}
/**
* Asserts that the values returns by {@link MediaPeriod#getStreamKeys(List)} are compatible with
* a {@link FilterableManifest} using these stream keys.
*
* @param mediaPeriodFactory A factory to create a {@link MediaPeriod} based on a manifest.
* @param manifest The manifest which is to be tested.
*/
public static <T extends FilterableManifest<T>>
void assertGetStreamKeysAndManifestFilterIntegration(
FilterableManifestMediaPeriodFactory<T> mediaPeriodFactory, T manifest) {
assertGetStreamKeysAndManifestFilterIntegration(
mediaPeriodFactory, manifest, /* periodIndex= */ 0, /* ignoredMimeType= */ null);
}
/**
* Asserts that the values returns by {@link MediaPeriod#getStreamKeys(List)} are compatible with
* a {@link FilterableManifest} using these stream keys.
*
* @param mediaPeriodFactory A factory to create a {@link MediaPeriod} based on a manifest.
* @param manifest The manifest which is to be tested.
* @param periodIndex The index of period in the manifest.
* @param ignoredMimeType Optional mime type whose existence in the filtered track groups is not
* asserted.
*/
public static <T extends FilterableManifest<T>>
void assertGetStreamKeysAndManifestFilterIntegration(
FilterableManifestMediaPeriodFactory<T> mediaPeriodFactory,
T manifest,
int periodIndex,
@Nullable String ignoredMimeType) {
MediaPeriod mediaPeriod = mediaPeriodFactory.createMediaPeriod(manifest, periodIndex);
TrackGroupArray trackGroupArray = getTrackGroups(mediaPeriod);
// Create test vector of query test selections:
// - One selection with one track per group, two tracks or all tracks.
// - Two selections with tracks from multiple groups, or tracks from a single group.
// - Multiple selections with tracks from all groups.
List<List<TrackSelection>> testSelections = new ArrayList<>();
for (int i = 0; i < trackGroupArray.length; i++) {
TrackGroup trackGroup = trackGroupArray.get(i);
for (int j = 0; j < trackGroup.length; j++) {
testSelections.add(Collections.singletonList(new TestTrackSelection(trackGroup, j)));
}
if (trackGroup.length > 1) {
testSelections.add(Collections.singletonList(new TestTrackSelection(trackGroup, 0, 1)));
testSelections.add(
Arrays.asList(
new TrackSelection[] {
new TestTrackSelection(trackGroup, 0), new TestTrackSelection(trackGroup, 1)
}));
}
if (trackGroup.length > 2) {
int[] allTracks = new int[trackGroup.length];
for (int j = 0; j < trackGroup.length; j++) {
allTracks[j] = j;
}
testSelections.add(
Collections.singletonList(new TestTrackSelection(trackGroup, allTracks)));
}
}
if (trackGroupArray.length > 1) {
for (int i = 0; i < trackGroupArray.length - 1; i++) {
for (int j = i + 1; j < trackGroupArray.length; j++) {
testSelections.add(
Arrays.asList(
new TrackSelection[] {
new TestTrackSelection(trackGroupArray.get(i), 0),
new TestTrackSelection(trackGroupArray.get(j), 0)
}));
}
}
}
if (trackGroupArray.length > 2) {
List<TrackSelection> selectionsFromAllGroups = new ArrayList<>();
for (int i = 0; i < trackGroupArray.length; i++) {
selectionsFromAllGroups.add(new TestTrackSelection(trackGroupArray.get(i), 0));
}
testSelections.add(selectionsFromAllGroups);
}
// Verify for each case that stream keys can be used to create filtered tracks which still
// contain at least all requested formats.
for (List<TrackSelection> testSelection : testSelections) {
List<StreamKey> streamKeys = mediaPeriod.getStreamKeys(testSelection);
if (streamKeys.isEmpty()) {
// Manifests won't be filtered if stream key is empty.
continue;
}
T filteredManifest = manifest.copy(streamKeys);
// The filtered manifest should only have one period left.
MediaPeriod filteredMediaPeriod =
mediaPeriodFactory.createMediaPeriod(filteredManifest, /* periodIndex= */ 0);
TrackGroupArray filteredTrackGroupArray = getTrackGroups(filteredMediaPeriod);
for (TrackSelection trackSelection : testSelection) {
if (ignoredMimeType != null
&& ignoredMimeType.equals(trackSelection.getFormat(0).sampleMimeType)) {
continue;
}
Format[] expectedFormats = new Format[trackSelection.length()];
for (int k = 0; k < trackSelection.length(); k++) {
expectedFormats[k] = trackSelection.getFormat(k);
}
assertOneTrackGroupContainsFormats(filteredTrackGroupArray, expectedFormats);
}
}
}
private static void assertOneTrackGroupContainsFormats(
TrackGroupArray trackGroupArray, Format[] formats) {
boolean foundSubset = false;
for (int i = 0; i < trackGroupArray.length; i++) {
if (containsFormats(trackGroupArray.get(i), formats)) {
foundSubset = true;
break;
}
}
assertThat(foundSubset).isTrue();
}
private static boolean containsFormats(TrackGroup trackGroup, Format[] formats) {
HashSet<Format> allFormats = new HashSet<>();
for (int i = 0; i < trackGroup.length; i++) {
allFormats.add(trackGroup.getFormat(i));
}
for (Format format : formats) {
if (!allFormats.remove(format)) {
return false;
}
}
return true;
}
private static TrackGroupArray getTrackGroups(MediaPeriod mediaPeriod) {
AtomicReference<TrackGroupArray> trackGroupArray = new AtomicReference<>();
DummyMainThread dummyMainThread = new DummyMainThread();
ConditionVariable preparedCondition = new ConditionVariable();
dummyMainThread.runOnMainThread(
() ->
mediaPeriod.prepare(
new Callback() {
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
trackGroupArray.set(mediaPeriod.getTrackGroups());
preparedCondition.open();
}
@Override
public void onContinueLoadingRequested(MediaPeriod source) {
// Ignore.
}
},
/* positionUs= */ 0));
try {
preparedCondition.block();
} catch (InterruptedException e) {
// Ignore.
}
dummyMainThread.release();
return trackGroupArray.get();
}
private static final class TestTrackSelection extends BaseTrackSelection {
public TestTrackSelection(TrackGroup trackGroup, int... tracks) {
super(trackGroup, tracks);
}
@Override
public int getSelectedIndex() {
return 0;
}
@Override
public int getSelectionReason() {
return C.SELECTION_REASON_UNKNOWN;
}
@Override
@Nullable
public Object getSelectionData() {
return null;
}
@Override
public void updateSelectedTrack(
long playbackPositionUs,
long bufferedDurationUs,
long availableDurationUs,
List<? extends MediaChunk> queue,
MediaChunkIterator[] mediaChunkIterators) {
// Do nothing.
}
}
}