Merge trick play tracks into main track groups

Issue: #6054
PiperOrigin-RevId: 307285068
This commit is contained in:
olly 2020-04-19 17:12:26 +01:00 committed by Oliver Woodman
parent 8ea33e2315
commit fea4376779
4 changed files with 291 additions and 75 deletions

View File

@ -97,6 +97,11 @@
* Remove generics from DRM components. * Remove generics from DRM components.
* Downloads: Merge downloads in `SegmentDownloader` to improve overall * Downloads: Merge downloads in `SegmentDownloader` to improve overall
download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)). download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
* DASH:
* Merge trick play adaptation sets (i.e., adaptation sets marked with
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
the main adaptation sets to which they refer. Trick play tracks are
marked with the `C.ROLE_FLAG_TRICK_PLAY` flag.
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams * MP3: Add `IndexSeeker` for accurate seeks in VBR streams
([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker is ([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker is
enabled by passing `FLAG_ENABLE_INDEX_SEEKING` to the `Mp3Extractor`. It may enabled by passing `FLAG_ENABLE_INDEX_SEEKING` to the `Mp3Extractor`. It may

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source.dash; package com.google.android.exoplayer2.source.dash;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -516,51 +517,94 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return Pair.create(new TrackGroupArray(trackGroups), trackGroupInfos); return Pair.create(new TrackGroupArray(trackGroups), trackGroupInfos);
} }
/**
* Groups adaptation sets. Two adaptations sets belong to the same group if either:
*
* <ul>
* <li>One is a trick-play adaptation set and uses a {@code
* http://dashif.org/guidelines/trickmode} essential or supplemental property to indicate
* that the other is the main adaptation set to which it corresponds.
* <li>The two adaptation sets are marked as safe for switching using {@code
* urn:mpeg:dash:adaptation-set-switching:2016} supplemental properties.
* </ul>
*
* @param adaptationSets The adaptation sets to merge.
* @return An array of groups, where each group is an array of adaptation set indices.
*/
private static int[][] getGroupedAdaptationSetIndices(List<AdaptationSet> adaptationSets) { private static int[][] getGroupedAdaptationSetIndices(List<AdaptationSet> adaptationSets) {
int adaptationSetCount = adaptationSets.size(); int adaptationSetCount = adaptationSets.size();
SparseIntArray idToIndexMap = new SparseIntArray(adaptationSetCount); SparseIntArray adaptationSetIdToIndex = new SparseIntArray(adaptationSetCount);
List<List<Integer>> adaptationSetGroupedIndices = new ArrayList<>(adaptationSetCount);
SparseArray<List<Integer>> adaptationSetIndexToGroupedIndices =
new SparseArray<>(adaptationSetCount);
// Initially make each adaptation set belong to its own group. Also build the
// adaptationSetIdToIndex map.
for (int i = 0; i < adaptationSetCount; i++) { for (int i = 0; i < adaptationSetCount; i++) {
idToIndexMap.put(adaptationSets.get(i).id, i); adaptationSetIdToIndex.put(adaptationSets.get(i).id, i);
List<Integer> initialGroup = new ArrayList<>();
initialGroup.add(i);
adaptationSetGroupedIndices.add(initialGroup);
adaptationSetIndexToGroupedIndices.put(i, initialGroup);
} }
int[][] groupedAdaptationSetIndices = new int[adaptationSetCount][]; // Merge adaptation set groups.
boolean[] adaptationSetUsedFlags = new boolean[adaptationSetCount];
int groupCount = 0;
for (int i = 0; i < adaptationSetCount; i++) { for (int i = 0; i < adaptationSetCount; i++) {
if (adaptationSetUsedFlags[i]) { int mergedGroupIndex = i;
// This adaptation set has already been included in a group. AdaptationSet adaptationSet = adaptationSets.get(i);
continue;
} // Trick-play adaptation sets are merged with their corresponding main adaptation sets.
adaptationSetUsedFlags[i] = true;
@Nullable @Nullable
Descriptor adaptationSetSwitchingProperty = Descriptor trickPlayProperty = findTrickPlayProperty(adaptationSet.essentialProperties);
findAdaptationSetSwitchingProperty(adaptationSets.get(i).supplementalProperties); if (trickPlayProperty == null) {
if (adaptationSetSwitchingProperty == null) { // Trick-play can also be specified using a supplemental property.
groupedAdaptationSetIndices[groupCount++] = new int[] {i}; trickPlayProperty = findTrickPlayProperty(adaptationSet.supplementalProperties);
} else { }
String[] extraAdaptationSetIds = Util.split(adaptationSetSwitchingProperty.value, ","); if (trickPlayProperty != null) {
int[] adaptationSetIndices = new int[1 + extraAdaptationSetIds.length]; int mainAdaptationSetId = Integer.parseInt(trickPlayProperty.value);
adaptationSetIndices[0] = i; int mainAdaptationSetIndex =
int outputIndex = 1; adaptationSetIdToIndex.get(mainAdaptationSetId, /* valueIfKeyNotFound= */ -1);
for (String adaptationSetId : extraAdaptationSetIds) { if (mainAdaptationSetIndex != -1) {
int extraIndex = mergedGroupIndex = mainAdaptationSetIndex;
idToIndexMap.get(Integer.parseInt(adaptationSetId), /* valueIfKeyNotFound= */ -1); }
if (extraIndex != -1) { }
adaptationSetUsedFlags[extraIndex] = true;
adaptationSetIndices[outputIndex] = extraIndex; // Adaptation sets that are safe for switching are merged, using the smallest index for the
outputIndex++; // merged group.
if (mergedGroupIndex == i) {
@Nullable
Descriptor adaptationSetSwitchingProperty =
findAdaptationSetSwitchingProperty(adaptationSet.supplementalProperties);
if (adaptationSetSwitchingProperty != null) {
String[] otherAdaptationSetIds = Util.split(adaptationSetSwitchingProperty.value, ",");
for (String adaptationSetId : otherAdaptationSetIds) {
int otherAdaptationSetId =
adaptationSetIdToIndex.get(
Integer.parseInt(adaptationSetId), /* valueIfKeyNotFound= */ -1);
if (otherAdaptationSetId != -1) {
mergedGroupIndex = Math.min(mergedGroupIndex, otherAdaptationSetId);
}
} }
} }
if (outputIndex < adaptationSetIndices.length) { }
adaptationSetIndices = Arrays.copyOf(adaptationSetIndices, outputIndex);
} // Merge the groups if necessary.
groupedAdaptationSetIndices[groupCount++] = adaptationSetIndices; if (mergedGroupIndex != i) {
List<Integer> thisGroup = adaptationSetIndexToGroupedIndices.get(i);
List<Integer> mergedGroup = adaptationSetIndexToGroupedIndices.get(mergedGroupIndex);
mergedGroup.addAll(thisGroup);
adaptationSetIndexToGroupedIndices.put(i, mergedGroup);
adaptationSetGroupedIndices.remove(thisGroup);
} }
} }
return groupCount < adaptationSetCount int[][] groupedAdaptationSetIndices = new int[adaptationSetGroupedIndices.size()][];
? Arrays.copyOf(groupedAdaptationSetIndices, groupCount) : groupedAdaptationSetIndices; for (int i = 0; i < groupedAdaptationSetIndices.length; i++) {
groupedAdaptationSetIndices[i] = Util.toArray(adaptationSetGroupedIndices.get(i));
// Restore the original adaptation set order within each group.
Arrays.sort(groupedAdaptationSetIndices[i]);
}
return groupedAdaptationSetIndices;
} }
/** /**
@ -747,9 +791,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable @Nullable
private static Descriptor findAdaptationSetSwitchingProperty(List<Descriptor> descriptors) { private static Descriptor findAdaptationSetSwitchingProperty(List<Descriptor> descriptors) {
return findDescriptor(descriptors, "urn:mpeg:dash:adaptation-set-switching:2016");
}
@Nullable
private static Descriptor findTrickPlayProperty(List<Descriptor> descriptors) {
return findDescriptor(descriptors, "http://dashif.org/guidelines/trickmode");
}
@Nullable
private static Descriptor findDescriptor(List<Descriptor> descriptors, String schemeIdUri) {
for (int i = 0; i < descriptors.size(); i++) { for (int i = 0; i < descriptors.size(); i++) {
Descriptor descriptor = descriptors.get(i); Descriptor descriptor = descriptors.get(i);
if ("urn:mpeg:dash:adaptation-set-switching:2016".equals(descriptor.schemeIdUri)) { if (schemeIdUri.equals(descriptor.schemeIdUri)) {
return descriptor; return descriptor;
} }
} }

View File

@ -26,6 +26,8 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback; import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
@ -35,7 +37,6 @@ import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement; import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
import com.google.android.exoplayer2.testutil.MediaPeriodAsserts; import com.google.android.exoplayer2.testutil.MediaPeriodAsserts;
import com.google.android.exoplayer2.testutil.MediaPeriodAsserts.FilterableManifestMediaPeriodFactory;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
@ -43,6 +44,7 @@ import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.LooperMode;
@ -53,7 +55,7 @@ import org.robolectric.annotation.LooperMode;
public final class DashMediaPeriodTest { public final class DashMediaPeriodTest {
@Test @Test
public void getSteamKeys_isCompatibleWithDashManifestFilter() { public void getStreamKeys_isCompatibleWithDashManifestFilter() {
// Test manifest which covers various edge cases: // Test manifest which covers various edge cases:
// - Multiple periods. // - Multiple periods.
// - Single and multiple representations per adaptation set. // - Single and multiple representations per adaptation set.
@ -61,83 +63,220 @@ public final class DashMediaPeriodTest {
// - Embedded track groups. // - Embedded track groups.
// All cases are deliberately combined in one test to catch potential indexing problems which // All cases are deliberately combined in one test to catch potential indexing problems which
// only occur in combination. // only occur in combination.
DashManifest testManifest = DashManifest manifest =
createDashManifest( createDashManifest(
createPeriod( createPeriod(
createAdaptationSet( createAdaptationSet(
/* id= */ 0, /* id= */ 0,
/* trackType= */ C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_VIDEO,
/* descriptor= */ null, /* descriptor= */ null,
createVideoRepresentation(/* bitrate= */ 1000000))), createVideoRepresentation(/* bitrate= */ 1000000))),
createPeriod( createPeriod(
createAdaptationSet( createAdaptationSet(
/* id= */ 100, /* id= */ 100,
/* trackType= */ C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_VIDEO,
/* descriptor= */ createSwitchDescriptor(/* ids...= */ 103, 104), createSwitchDescriptor(/* ids...= */ 103, 104),
createVideoRepresentationWithInbandEventStream(/* bitrate= */ 200000), createVideoRepresentationWithInbandEventStream(/* bitrate= */ 200000),
createVideoRepresentationWithInbandEventStream(/* bitrate= */ 400000), createVideoRepresentationWithInbandEventStream(/* bitrate= */ 400000),
createVideoRepresentationWithInbandEventStream(/* bitrate= */ 600000)), createVideoRepresentationWithInbandEventStream(/* bitrate= */ 600000)),
createAdaptationSet( createAdaptationSet(
/* id= */ 101, /* id= */ 101,
/* trackType= */ C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_AUDIO,
/* descriptor= */ createSwitchDescriptor(/* ids...= */ 102), createSwitchDescriptor(/* ids...= */ 102),
createAudioRepresentation(/* bitrate= */ 48000), createAudioRepresentation(/* bitrate= */ 48000),
createAudioRepresentation(/* bitrate= */ 96000)), createAudioRepresentation(/* bitrate= */ 96000)),
createAdaptationSet( createAdaptationSet(
/* id= */ 102, /* id= */ 102,
/* trackType= */ C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_AUDIO,
/* descriptor= */ createSwitchDescriptor(/* ids...= */ 101), createSwitchDescriptor(/* ids...= */ 101),
createAudioRepresentation(/* bitrate= */ 256000)), createAudioRepresentation(/* bitrate= */ 256000)),
createAdaptationSet( createAdaptationSet(
/* id= */ 103, /* id= */ 103,
/* trackType= */ C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_VIDEO,
/* descriptor= */ createSwitchDescriptor(/* ids...= */ 100, 104), createSwitchDescriptor(/* ids...= */ 100, 104),
createVideoRepresentationWithInbandEventStream(/* bitrate= */ 800000), createVideoRepresentationWithInbandEventStream(/* bitrate= */ 800000),
createVideoRepresentationWithInbandEventStream(/* bitrate= */ 1000000)), createVideoRepresentationWithInbandEventStream(/* bitrate= */ 1000000)),
createAdaptationSet( createAdaptationSet(
/* id= */ 104, /* id= */ 104,
/* trackType= */ C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_VIDEO,
/* descriptor= */ createSwitchDescriptor(/* ids...= */ 100, 103), createSwitchDescriptor(/* ids...= */ 100, 103),
createVideoRepresentationWithInbandEventStream(/* bitrate= */ 2000000)), createVideoRepresentationWithInbandEventStream(/* bitrate= */ 2000000)),
createAdaptationSet( createAdaptationSet(
/* id= */ 105, /* id= */ 105,
/* trackType= */ C.TRACK_TYPE_TEXT, C.TRACK_TYPE_TEXT,
/* descriptor= */ null, /* descriptor= */ null,
createTextRepresentation(/* language= */ "eng")), createTextRepresentation(/* language= */ "eng")),
createAdaptationSet( createAdaptationSet(
/* id= */ 105, /* id= */ 105,
/* trackType= */ C.TRACK_TYPE_TEXT, C.TRACK_TYPE_TEXT,
/* descriptor= */ null, /* descriptor= */ null,
createTextRepresentation(/* language= */ "ger")))); createTextRepresentation(/* language= */ "ger"))));
FilterableManifestMediaPeriodFactory<DashManifest> mediaPeriodFactory =
(manifest, periodIndex) ->
new DashMediaPeriod(
/* id= */ periodIndex,
manifest,
periodIndex,
mock(DashChunkSource.Factory.class),
mock(TransferListener.class),
DrmSessionManager.getDummyDrmSessionManager(),
mock(LoadErrorHandlingPolicy.class),
new EventDispatcher()
.withParameters(
/* windowIndex= */ 0,
/* mediaPeriodId= */ new MediaPeriodId(/* periodUid= */ new Object()),
/* mediaTimeOffsetMs= */ 0),
/* elapsedRealtimeOffsetMs= */ 0,
mock(LoaderErrorThrower.class),
mock(Allocator.class),
mock(CompositeSequenceableLoaderFactory.class),
mock(PlayerEmsgCallback.class));
// Ignore embedded metadata as we don't want to select primary group just to get embedded track. // Ignore embedded metadata as we don't want to select primary group just to get embedded track.
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration( MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
mediaPeriodFactory, DashMediaPeriodTest::createDashMediaPeriod,
testManifest, manifest,
/* periodIndex= */ 1, /* periodIndex= */ 1,
/* ignoredMimeType= */ "application/x-emsg"); /* ignoredMimeType= */ "application/x-emsg");
} }
@Test
public void adaptationSetSwitchingProperty_mergesTrackGroups() {
DashManifest manifest =
createDashManifest(
createPeriod(
createAdaptationSet(
/* id= */ 0,
C.TRACK_TYPE_VIDEO,
createSwitchDescriptor(/* ids...= */ 1, 2),
createVideoRepresentation(/* bitrate= */ 0),
createVideoRepresentation(/* bitrate= */ 1)),
createAdaptationSet(
/* id= */ 3,
C.TRACK_TYPE_VIDEO,
/* descriptor= */ null,
createVideoRepresentation(/* bitrate= */ 300)),
createAdaptationSet(
/* id= */ 2,
C.TRACK_TYPE_VIDEO,
createSwitchDescriptor(/* ids...= */ 0, 1),
createVideoRepresentation(/* bitrate= */ 200),
createVideoRepresentation(/* bitrate= */ 201)),
createAdaptationSet(
/* id= */ 1,
C.TRACK_TYPE_VIDEO,
createSwitchDescriptor(/* ids...= */ 0, 2),
createVideoRepresentation(/* bitrate= */ 100))));
DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, 0);
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
// We expect the three adaptation sets with the switch descriptor to be merged, retaining the
// representations in their original order.
TrackGroupArray expectedTrackGroups =
new TrackGroupArray(
new TrackGroup(
adaptationSets.get(0).representations.get(0).format,
adaptationSets.get(0).representations.get(1).format,
adaptationSets.get(2).representations.get(0).format,
adaptationSets.get(2).representations.get(1).format,
adaptationSets.get(3).representations.get(0).format),
new TrackGroup(adaptationSets.get(1).representations.get(0).format));
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
@Test
public void trickPlayProperty_mergesTrackGroups() {
DashManifest manifest =
createDashManifest(
createPeriod(
createAdaptationSet(
/* id= */ 0,
C.TRACK_TYPE_VIDEO,
createTrickPlayDescriptor(/* mainAdaptationSetId= */ 1),
createVideoRepresentation(/* bitrate= */ 0),
createVideoRepresentation(/* bitrate= */ 1)),
createAdaptationSet(
/* id= */ 1,
C.TRACK_TYPE_VIDEO,
/* descriptor= */ null,
createVideoRepresentation(/* bitrate= */ 100)),
createAdaptationSet(
/* id= */ 2,
C.TRACK_TYPE_VIDEO,
/* descriptor= */ null,
createVideoRepresentation(/* bitrate= */ 200),
createVideoRepresentation(/* bitrate= */ 201)),
createAdaptationSet(
/* id= */ 3,
C.TRACK_TYPE_VIDEO,
createTrickPlayDescriptor(/* mainAdaptationSetId= */ 2),
createVideoRepresentation(/* bitrate= */ 300))));
DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, 0);
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
// We expect the trick play adaptation sets to be merged with the ones to which they refer,
// retaining representations in their original order.
TrackGroupArray expectedTrackGroups =
new TrackGroupArray(
new TrackGroup(
adaptationSets.get(0).representations.get(0).format,
adaptationSets.get(0).representations.get(1).format,
adaptationSets.get(1).representations.get(0).format),
new TrackGroup(
adaptationSets.get(2).representations.get(0).format,
adaptationSets.get(2).representations.get(1).format,
adaptationSets.get(3).representations.get(0).format));
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
@Test
public void adaptationSetSwitchingProperty_andTrickPlayProperty_mergesTrackGroups() {
DashManifest manifest =
createDashManifest(
createPeriod(
createAdaptationSet(
/* id= */ 0,
C.TRACK_TYPE_VIDEO,
createTrickPlayDescriptor(/* mainAdaptationSetId= */ 1),
createVideoRepresentation(/* bitrate= */ 0),
createVideoRepresentation(/* bitrate= */ 1)),
createAdaptationSet(
/* id= */ 1,
C.TRACK_TYPE_VIDEO,
createSwitchDescriptor(/* ids...= */ 2),
createVideoRepresentation(/* bitrate= */ 100)),
createAdaptationSet(
/* id= */ 2,
C.TRACK_TYPE_VIDEO,
createSwitchDescriptor(/* ids...= */ 1),
createVideoRepresentation(/* bitrate= */ 200),
createVideoRepresentation(/* bitrate= */ 201)),
createAdaptationSet(
/* id= */ 3,
C.TRACK_TYPE_VIDEO,
createTrickPlayDescriptor(/* mainAdaptationSetId= */ 2),
createVideoRepresentation(/* bitrate= */ 300))));
DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, 0);
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
// We expect all adaptation sets to be merged into one group, retaining representations in their
// original order.
TrackGroupArray expectedTrackGroups =
new TrackGroupArray(
new TrackGroup(
adaptationSets.get(0).representations.get(0).format,
adaptationSets.get(0).representations.get(1).format,
adaptationSets.get(1).representations.get(0).format,
adaptationSets.get(2).representations.get(0).format,
adaptationSets.get(2).representations.get(1).format,
adaptationSets.get(3).representations.get(0).format));
MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups);
}
private static DashMediaPeriod createDashMediaPeriod(DashManifest manifest, int periodIndex) {
return new DashMediaPeriod(
/* id= */ periodIndex,
manifest,
periodIndex,
mock(DashChunkSource.Factory.class),
mock(TransferListener.class),
DrmSessionManager.getDummyDrmSessionManager(),
mock(LoadErrorHandlingPolicy.class),
new EventDispatcher()
.withParameters(
/* windowIndex= */ 0,
/* mediaPeriodId= */ new MediaPeriodId(/* periodUid= */ new Object()),
/* mediaTimeOffsetMs= */ 0),
/* elapsedRealtimeOffsetMs= */ 0,
mock(LoaderErrorThrower.class),
mock(Allocator.class),
mock(CompositeSequenceableLoaderFactory.class),
mock(PlayerEmsgCallback.class));
}
private static DashManifest createDashManifest(Period... periods) { private static DashManifest createDashManifest(Period... periods) {
return new DashManifest( return new DashManifest(
/* availabilityStartTimeMs= */ 0, /* availabilityStartTimeMs= */ 0,
@ -228,6 +367,13 @@ public final class DashMediaPeriodTest {
/* id= */ null); /* id= */ null);
} }
private static Descriptor createTrickPlayDescriptor(int mainAdaptationSetId) {
return new Descriptor(
/* schemeIdUri= */ "http://dashif.org/guidelines/trickmode",
/* value= */ Integer.toString(mainAdaptationSetId),
/* id= */ null);
}
private static Descriptor getInbandEventDescriptor() { private static Descriptor getInbandEventDescriptor() {
return new Descriptor( return new Descriptor(
/* schemeIdUri= */ "inBandSchemeIdUri", /* value= */ "inBandValue", /* id= */ "inBandId"); /* schemeIdUri= */ "inBandSchemeIdUri", /* value= */ "inBandValue", /* id= */ "inBandId");

View File

@ -54,6 +54,17 @@ public final class MediaPeriodAsserts {
private MediaPeriodAsserts() {} private MediaPeriodAsserts() {}
/**
* Prepares the {@link MediaPeriod} and asserts that it provides the specified track groups.
*
* @param mediaPeriod The {@link MediaPeriod} to test.
* @param expectedGroups The expected track groups.
*/
public static void assertTrackGroups(MediaPeriod mediaPeriod, TrackGroupArray expectedGroups) {
TrackGroupArray actualGroups = prepareAndGetTrackGroups(mediaPeriod);
assertThat(actualGroups).isEqualTo(expectedGroups);
}
/** /**
* Asserts that the values returns by {@link MediaPeriod#getStreamKeys(List)} are compatible with * Asserts that the values returns by {@link MediaPeriod#getStreamKeys(List)} are compatible with
* a {@link FilterableManifest} using these stream keys. * a {@link FilterableManifest} using these stream keys.
@ -85,7 +96,7 @@ public final class MediaPeriodAsserts {
int periodIndex, int periodIndex,
@Nullable String ignoredMimeType) { @Nullable String ignoredMimeType) {
MediaPeriod mediaPeriod = mediaPeriodFactory.createMediaPeriod(manifest, periodIndex); MediaPeriod mediaPeriod = mediaPeriodFactory.createMediaPeriod(manifest, periodIndex);
TrackGroupArray trackGroupArray = getTrackGroups(mediaPeriod); TrackGroupArray trackGroupArray = prepareAndGetTrackGroups(mediaPeriod);
// Create test vector of query test selections: // Create test vector of query test selections:
// - One selection with one track per group, two tracks or all tracks. // - One selection with one track per group, two tracks or all tracks.
@ -146,7 +157,7 @@ public final class MediaPeriodAsserts {
// The filtered manifest should only have one period left. // The filtered manifest should only have one period left.
MediaPeriod filteredMediaPeriod = MediaPeriod filteredMediaPeriod =
mediaPeriodFactory.createMediaPeriod(filteredManifest, /* periodIndex= */ 0); mediaPeriodFactory.createMediaPeriod(filteredManifest, /* periodIndex= */ 0);
TrackGroupArray filteredTrackGroupArray = getTrackGroups(filteredMediaPeriod); TrackGroupArray filteredTrackGroupArray = prepareAndGetTrackGroups(filteredMediaPeriod);
for (TrackSelection trackSelection : testSelection) { for (TrackSelection trackSelection : testSelection) {
if (ignoredMimeType != null if (ignoredMimeType != null
&& ignoredMimeType.equals(trackSelection.getFormat(0).sampleMimeType)) { && ignoredMimeType.equals(trackSelection.getFormat(0).sampleMimeType)) {
@ -186,7 +197,7 @@ public final class MediaPeriodAsserts {
return true; return true;
} }
private static TrackGroupArray getTrackGroups(MediaPeriod mediaPeriod) { private static TrackGroupArray prepareAndGetTrackGroups(MediaPeriod mediaPeriod) {
AtomicReference<TrackGroupArray> trackGroupArray = new AtomicReference<>(); AtomicReference<TrackGroupArray> trackGroupArray = new AtomicReference<>();
DummyMainThread dummyMainThread = new DummyMainThread(); DummyMainThread dummyMainThread = new DummyMainThread();
ConditionVariable preparedCondition = new ConditionVariable(); ConditionVariable preparedCondition = new ConditionVariable();