Add SsManifest.copy and TrackKey for SmoothStreaming downloads

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=165295985
This commit is contained in:
olly 2017-08-15 05:23:15 -07:00 committed by Oliver Woodman
parent c94bce17b4
commit 4b706d9484
2 changed files with 205 additions and 9 deletions

View file

@ -0,0 +1,128 @@
/*
* Copyright (C) 2017 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.source.smoothstreaming.manifest;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import junit.framework.TestCase;
/**
* Unit tests for {@link SsManifest}.
*/
public class SsManifestTest extends TestCase {
private static ProtectionElement DUMMY_PROTECTION_ELEMENT =
new ProtectionElement(C.WIDEVINE_UUID, new byte[] {0, 1, 2});
public void testCopy() throws Exception {
Format[][] formats = newFormats(2, 3);
SsManifest sourceManifest = newSsManifest(
newStreamElement("1",formats[0]),
newStreamElement("2", formats[1]));
List<TrackKey> keys = Arrays.asList(
new TrackKey(0, 0),
new TrackKey(0, 2),
new TrackKey(1, 0));
// Keys don't need to be in any particular order
Collections.shuffle(keys, new Random(0));
SsManifest copyManifest = sourceManifest.copy(keys);
SsManifest expectedManifest = newSsManifest(
newStreamElement("1", formats[0][0], formats[0][2]),
newStreamElement("2", formats[1][0]));
assertManifestEquals(expectedManifest, copyManifest);
}
public void testCopyRemoveStreamElement() throws Exception {
Format[][] formats = newFormats(2, 3);
SsManifest sourceManifest = newSsManifest(
newStreamElement("1", formats[0]),
newStreamElement("2", formats[1]));
List<TrackKey> keys = Arrays.asList(
new TrackKey(1, 0));
// Keys don't need to be in any particular order
Collections.shuffle(keys, new Random(0));
SsManifest copyManifest = sourceManifest.copy(keys);
SsManifest expectedManifest = newSsManifest(
newStreamElement("2", formats[1][0]));
assertManifestEquals(expectedManifest, copyManifest);
}
private static void assertManifestEquals(SsManifest expected, SsManifest actual) {
assertEquals(expected.durationUs, actual.durationUs);
assertEquals(expected.dvrWindowLengthUs, actual.dvrWindowLengthUs);
assertEquals(expected.isLive, actual.isLive);
assertEquals(expected.lookAheadCount, actual.lookAheadCount);
assertEquals(expected.majorVersion, actual.majorVersion);
assertEquals(expected.minorVersion, actual.minorVersion);
assertEquals(expected.protectionElement.uuid, actual.protectionElement.uuid);
assertEquals(expected.protectionElement, actual.protectionElement);
for (int i = 0; i < expected.streamElements.length; i++) {
StreamElement expectedStreamElement = expected.streamElements[i];
StreamElement actualStreamElement = actual.streamElements[i];
assertEquals(expectedStreamElement.chunkCount, actualStreamElement.chunkCount);
assertEquals(expectedStreamElement.displayHeight, actualStreamElement.displayHeight);
assertEquals(expectedStreamElement.displayWidth, actualStreamElement.displayWidth);
assertEquals(expectedStreamElement.language, actualStreamElement.language);
assertEquals(expectedStreamElement.maxHeight, actualStreamElement.maxHeight);
assertEquals(expectedStreamElement.maxWidth, actualStreamElement.maxWidth);
assertEquals(expectedStreamElement.name, actualStreamElement.name);
assertEquals(expectedStreamElement.subType, actualStreamElement.subType);
assertEquals(expectedStreamElement.timescale, actualStreamElement.timescale);
assertEquals(expectedStreamElement.type, actualStreamElement.type);
MoreAsserts.assertEquals(expectedStreamElement.formats, actualStreamElement.formats);
}
}
private static Format[][] newFormats(int streamElementCount, int trackCounts) {
Format[][] formats = new Format[streamElementCount][];
for (int i = 0; i < streamElementCount; i++) {
formats[i] = new Format[trackCounts];
for (int j = 0; j < trackCounts; j++) {
formats[i][j] = newFormat(i + "." + j);
}
}
return formats;
}
private static SsManifest newSsManifest(StreamElement... streamElements) {
return new SsManifest(1, 2, 1000, 5000, 0, 0, false, DUMMY_PROTECTION_ELEMENT, streamElements);
}
private static StreamElement newStreamElement(String name, Format... formats) {
return new StreamElement("baseUri", "chunkTemplate", C.TRACK_TYPE_VIDEO, "subType",
1000, name, 1024, 768, 1024, 768, null, formats, Collections.<Long>emptyList(), 0);
}
private static Format newFormat(String id) {
return Format.createContainerFormat(id, MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null,
Format.NO_VALUE, 0, null);
}
}

View file

@ -21,6 +21,9 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@ -96,16 +99,60 @@ public class SsManifest {
public SsManifest(int majorVersion, int minorVersion, long timescale, long duration,
long dvrWindowLength, int lookAheadCount, boolean isLive, ProtectionElement protectionElement,
StreamElement[] streamElements) {
this(majorVersion, minorVersion,
duration == 0 ? C.TIME_UNSET
: Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale),
dvrWindowLength == 0 ? C.TIME_UNSET
: Util.scaleLargeTimestamp(dvrWindowLength, C.MICROS_PER_SECOND, timescale),
lookAheadCount, isLive, protectionElement, streamElements);
}
private SsManifest(int majorVersion, int minorVersion, long durationUs, long dvrWindowLengthUs,
int lookAheadCount, boolean isLive, ProtectionElement protectionElement,
StreamElement[] streamElements) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.durationUs = durationUs;
this.dvrWindowLengthUs = dvrWindowLengthUs;
this.lookAheadCount = lookAheadCount;
this.isLive = isLive;
this.protectionElement = protectionElement;
this.streamElements = streamElements;
dvrWindowLengthUs = dvrWindowLength == 0 ? C.TIME_UNSET
: Util.scaleLargeTimestamp(dvrWindowLength, C.MICROS_PER_SECOND, timescale);
durationUs = duration == 0 ? C.TIME_UNSET
: Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale);
}
/**
* Creates a copy of this manifest which includes only the tracks identified by the given keys.
*
* @param trackKeys List of keys for the tracks to be included in the copy.
* @return A copy of this manifest with the selected tracks.
* @throws IndexOutOfBoundsException If a key has an invalid index.
*/
public final SsManifest copy(List<TrackKey> trackKeys) {
LinkedList<TrackKey> sortedKeys = new LinkedList<>(trackKeys);
Collections.sort(sortedKeys);
StreamElement currentStreamElement = null;
List<StreamElement> copiedStreamElements = new ArrayList<>();
List<Format> copiedFormats = new ArrayList<>();
for (int i = 0; i < sortedKeys.size(); i++) {
TrackKey key = sortedKeys.get(i);
StreamElement streamElement = streamElements[key.streamElementIndex];
if (streamElement != currentStreamElement && currentStreamElement != null) {
// We're advancing to a new stream element. Add the current one.
copiedStreamElements.add(currentStreamElement.copy(copiedFormats.toArray(new Format[0])));
copiedFormats.clear();
}
currentStreamElement = streamElement;
copiedFormats.add(streamElement.formats[key.trackIndex]);
}
if (currentStreamElement != null) {
// Add the last stream element.
copiedStreamElements.add(currentStreamElement.copy(copiedFormats.toArray(new Format[0])));
}
StreamElement[] copiedStreamElementsArray = copiedStreamElements.toArray(new StreamElement[0]);
return new SsManifest(majorVersion, minorVersion, durationUs, dvrWindowLengthUs, lookAheadCount,
isLive, protectionElement, copiedStreamElementsArray);
}
/**
@ -156,6 +203,16 @@ public class SsManifest {
long timescale, String name, int maxWidth, int maxHeight, int displayWidth,
int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes,
long lastChunkDuration) {
this (baseUri, chunkTemplate, type, subType, timescale, name, maxWidth, maxHeight,
displayWidth, displayHeight, language, formats, chunkStartTimes,
Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale),
Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale));
}
private StreamElement(String baseUri, String chunkTemplate, int type, String subType,
long timescale, String name, int maxWidth, int maxHeight, int displayWidth,
int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes,
long[] chunkStartTimesUs, long lastChunkDurationUs) {
this.baseUri = baseUri;
this.chunkTemplate = chunkTemplate;
this.type = type;
@ -168,12 +225,23 @@ public class SsManifest {
this.displayHeight = displayHeight;
this.language = language;
this.formats = formats;
this.chunkCount = chunkStartTimes.size();
this.chunkStartTimes = chunkStartTimes;
lastChunkDurationUs =
Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale);
chunkStartTimesUs =
Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale);
this.chunkStartTimesUs = chunkStartTimesUs;
this.lastChunkDurationUs = lastChunkDurationUs;
chunkCount = chunkStartTimes.size();
}
/**
* Creates a copy of this stream element with the formats replaced with those specified.
*
* @param formats The formats to be included in the copy.
* @return A copy of this stream element with the formats replaced.
* @throws IndexOutOfBoundsException If a key has an invalid index.
*/
public StreamElement copy(Format[] formats) {
return new StreamElement(baseUri, chunkTemplate, type, subType, timescale, name, maxWidth,
maxHeight, displayWidth, displayHeight, language, formats, chunkStartTimes,
chunkStartTimesUs, lastChunkDurationUs);
}
/**