Commit 4b706d94 by olly Committed by Oliver Woodman

Add SsManifest.copy and TrackKey for SmoothStreaming downloads

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=165295985
parent c94bce17
/*
* 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);
}
}
...@@ -21,6 +21,9 @@ import com.google.android.exoplayer2.Format; ...@@ -21,6 +21,9 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util; 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.List;
import java.util.UUID; import java.util.UUID;
...@@ -96,16 +99,60 @@ public class SsManifest { ...@@ -96,16 +99,60 @@ public class SsManifest {
public SsManifest(int majorVersion, int minorVersion, long timescale, long duration, public SsManifest(int majorVersion, int minorVersion, long timescale, long duration,
long dvrWindowLength, int lookAheadCount, boolean isLive, ProtectionElement protectionElement, long dvrWindowLength, int lookAheadCount, boolean isLive, ProtectionElement protectionElement,
StreamElement[] streamElements) { 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.majorVersion = majorVersion;
this.minorVersion = minorVersion; this.minorVersion = minorVersion;
this.durationUs = durationUs;
this.dvrWindowLengthUs = dvrWindowLengthUs;
this.lookAheadCount = lookAheadCount; this.lookAheadCount = lookAheadCount;
this.isLive = isLive; this.isLive = isLive;
this.protectionElement = protectionElement; this.protectionElement = protectionElement;
this.streamElements = streamElements; 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 { ...@@ -156,6 +203,16 @@ public class SsManifest {
long timescale, String name, int maxWidth, int maxHeight, int displayWidth, long timescale, String name, int maxWidth, int maxHeight, int displayWidth,
int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes, int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes,
long lastChunkDuration) { 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.baseUri = baseUri;
this.chunkTemplate = chunkTemplate; this.chunkTemplate = chunkTemplate;
this.type = type; this.type = type;
...@@ -168,12 +225,23 @@ public class SsManifest { ...@@ -168,12 +225,23 @@ public class SsManifest {
this.displayHeight = displayHeight; this.displayHeight = displayHeight;
this.language = language; this.language = language;
this.formats = formats; this.formats = formats;
this.chunkCount = chunkStartTimes.size();
this.chunkStartTimes = chunkStartTimes; this.chunkStartTimes = chunkStartTimes;
lastChunkDurationUs = this.chunkStartTimesUs = chunkStartTimesUs;
Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale); this.lastChunkDurationUs = lastChunkDurationUs;
chunkStartTimesUs = chunkCount = chunkStartTimes.size();
Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale); }
/**
* 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);
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment