Merge trick play tracks into main track groups
Issue: #6054 PiperOrigin-RevId: 307285068
This commit is contained in:
parent
8ea33e2315
commit
fea4376779
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user