From 4b706d948471c9d2e2794d4e780f3de9ac207796 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 15 Aug 2017 05:23:15 -0700 Subject: [PATCH] Add SsManifest.copy and TrackKey for SmoothStreaming downloads ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=165295985 --- .../manifest/SsManifestTest.java | 128 ++++++++++++++++++ .../smoothstreaming/manifest/SsManifest.java | 86 ++++++++++-- 2 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java diff --git a/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java b/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java new file mode 100644 index 0000000000..0a221b6932 --- /dev/null +++ b/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java @@ -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 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 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.emptyList(), 0); + } + + private static Format newFormat(String id) { + return Format.createContainerFormat(id, MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, + Format.NO_VALUE, 0, null); + } + +} diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java index 1bb877eb59..fbc3726a0e 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java @@ -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 trackKeys) { + LinkedList sortedKeys = new LinkedList<>(trackKeys); + Collections.sort(sortedKeys); + + StreamElement currentStreamElement = null; + List copiedStreamElements = new ArrayList<>(); + List 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 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 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); } /**