Commit 59f01ec3 by olly Committed by Oliver Woodman

Use manifest filtering when downloading.

When we play downloaded content, we rely on the manifest filters
to produce a manifest that contains only the content that was
downloaded. It makes sense just to use the same filters during
download too, so we don't have to worry about any implementation
differences.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=194380704
parent 6ac25284
...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheUtil; ...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheUtil;
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters; import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.PriorityTaskManager;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -38,7 +39,8 @@ import java.util.List; ...@@ -38,7 +39,8 @@ import java.util.List;
* @param <M> The type of the manifest object. * @param <M> The type of the manifest object.
* @param <K> The type of the representation key object. * @param <K> The type of the representation key object.
*/ */
public abstract class SegmentDownloader<M, K> implements Downloader { public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
implements Downloader {
/** Smallest unit of content to be downloaded. */ /** Smallest unit of content to be downloaded. */
protected static class Segment implements Comparable<Segment> { protected static class Segment implements Comparable<Segment> {
...@@ -68,9 +70,9 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -68,9 +70,9 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
private final Cache cache; private final Cache cache;
private final CacheDataSource dataSource; private final CacheDataSource dataSource;
private final CacheDataSource offlineDataSource; private final CacheDataSource offlineDataSource;
private final ArrayList<K> keys;
private M manifest; private M manifest;
private K[] keys;
private volatile int totalSegments; private volatile int totalSegments;
private volatile int downloadedSegments; private volatile int downloadedSegments;
private volatile long downloadedBytes; private volatile long downloadedBytes;
...@@ -85,6 +87,7 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -85,6 +87,7 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
this.dataSource = constructorHelper.buildCacheDataSource(false); this.dataSource = constructorHelper.buildCacheDataSource(false);
this.offlineDataSource = constructorHelper.buildCacheDataSource(true); this.offlineDataSource = constructorHelper.buildCacheDataSource(true);
this.priorityTaskManager = constructorHelper.getPriorityTaskManager(); this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
keys = new ArrayList<>();
resetCounters(); resetCounters();
} }
...@@ -100,10 +103,12 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -100,10 +103,12 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
/** /**
* Selects multiple representations pointed to by the keys for downloading, checking status. Any * Selects multiple representations pointed to by the keys for downloading, checking status. Any
* previous selection is cleared. If keys array is empty, all representations are downloaded. * previous selection is cleared. If keys array is null or empty then all representations are
* downloaded.
*/ */
public final void selectRepresentations(K[] keys) { public final void selectRepresentations(K[] keys) {
this.keys = keys.length > 0 ? keys.clone() : null; this.keys.clear();
Collections.addAll(this.keys, keys);
resetCounters(); resetCounters();
} }
...@@ -223,7 +228,7 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -223,7 +228,7 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
if (manifest != null) { if (manifest != null) {
List<Segment> segments = null; List<Segment> segments = null;
try { try {
segments = getSegments(offlineDataSource, manifest, getAllRepresentationKeys(), true); segments = getSegments(offlineDataSource, manifest, true);
} catch (IOException e) { } catch (IOException e) {
// Ignore exceptions. We do our best with what's available offline. // Ignore exceptions. We do our best with what's available offline.
} }
...@@ -248,11 +253,10 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -248,11 +253,10 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
protected abstract M getManifest(DataSource dataSource, Uri uri) throws IOException; protected abstract M getManifest(DataSource dataSource, Uri uri) throws IOException;
/** /**
* Returns a list of {@link Segment}s for given keys. * Returns a list of all downloadable {@link Segment}s for a given manifest.
* *
* @param dataSource The {@link DataSource} through which to load any required data. * @param dataSource The {@link DataSource} through which to load any required data.
* @param manifest The manifest containing the segments. * @param manifest The manifest containing the segments.
* @param keys The selected representation keys.
* @param allowIncompleteIndex Whether to continue in the case that a load error prevents all * @param allowIncompleteIndex Whether to continue in the case that a load error prevents all
* segments from being listed. If true then a partial segment list will be returned. If false * segments from being listed. If true then a partial segment list will be returned. If false
* an {@link IOException} will be thrown. * an {@link IOException} will be thrown.
...@@ -261,8 +265,9 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -261,8 +265,9 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
* the media is not in a form that allows for its segments to be listed. * the media is not in a form that allows for its segments to be listed.
* @return A list of {@link Segment}s for given keys. * @return A list of {@link Segment}s for given keys.
*/ */
protected abstract List<Segment> getSegments(DataSource dataSource, M manifest, K[] keys, protected abstract List<Segment> getSegments(
boolean allowIncompleteIndex) throws InterruptedException, IOException; DataSource dataSource, M manifest, boolean allowIncompleteIndex)
throws InterruptedException, IOException;
private void resetCounters() { private void resetCounters() {
totalSegments = C.LENGTH_UNSET; totalSegments = C.LENGTH_UNSET;
...@@ -283,10 +288,8 @@ public abstract class SegmentDownloader<M, K> implements Downloader { ...@@ -283,10 +288,8 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
private synchronized List<Segment> initStatus(boolean offline) private synchronized List<Segment> initStatus(boolean offline)
throws IOException, InterruptedException { throws IOException, InterruptedException {
DataSource dataSource = getDataSource(offline); DataSource dataSource = getDataSource(offline);
if (keys == null) { M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys);
keys = getAllRepresentationKeys(); List<Segment> segments = getSegments(dataSource, filteredManifest, offline);
}
List<Segment> segments = getSegments(dataSource, manifest, keys, offline);
CachingCounters cachingCounters = new CachingCounters(); CachingCounters cachingCounters = new CachingCounters();
totalSegments = segments.size(); totalSegments = segments.size();
downloadedSegments = 0; downloadedSegments = 0;
......
...@@ -89,77 +89,92 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres ...@@ -89,77 +89,92 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
} }
@Override @Override
protected List<Segment> getSegments(DataSource dataSource, DashManifest manifest, protected List<Segment> getSegments(
RepresentationKey[] keys, boolean allowIndexLoadErrors) DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors)
throws InterruptedException, IOException { throws InterruptedException, IOException {
ArrayList<Segment> segments = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>();
for (RepresentationKey key : keys) { for (int i = 0; i < manifest.getPeriodCount(); i++) {
Period period = manifest.getPeriod(i);
long periodStartUs = C.msToUs(period.startMs);
long periodDurationUs = manifest.getPeriodDurationUs(i);
List<AdaptationSet> adaptationSets = period.adaptationSets;
for (int j = 0; j < adaptationSets.size(); j++) {
addSegmentsForAdaptationSet(
dataSource,
adaptationSets.get(j),
periodStartUs,
periodDurationUs,
allowIndexLoadErrors,
segments);
}
}
return segments;
}
private static void addSegmentsForAdaptationSet(
DataSource dataSource,
AdaptationSet adaptationSet,
long periodStartUs,
long periodDurationUs,
boolean allowIndexLoadErrors,
ArrayList<Segment> out)
throws IOException, InterruptedException {
for (int i = 0; i < adaptationSet.representations.size(); i++) {
Representation representation = adaptationSet.representations.get(i);
DashSegmentIndex index; DashSegmentIndex index;
try { try {
index = getSegmentIndex(dataSource, manifest, key); index = getSegmentIndex(dataSource, adaptationSet.type, representation);
if (index == null) { if (index == null) {
// Loading succeeded but there was no index. This is always a failure. // Loading succeeded but there was no index.
throw new DownloadException("No index for representation: " + key); throw new DownloadException("Missing segment index");
} }
} catch (IOException e) { } catch (IOException e) {
if (allowIndexLoadErrors) { if (allowIndexLoadErrors) {
// Loading failed, but load errors are allowed. Advance to the next key. // Loading failed, but load errors are allowed. Advance to the next representation.
continue; continue;
} else { } else {
throw e; throw e;
} }
} }
long periodDurationUs = manifest.getPeriodDurationUs(key.periodIndex);
int segmentCount = index.getSegmentCount(periodDurationUs); int segmentCount = index.getSegmentCount(periodDurationUs);
if (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED) { if (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
throw new DownloadException("Unbounded index for representation: " + key); throw new DownloadException("Unbounded segment index");
} }
Period period = manifest.getPeriod(key.periodIndex);
Representation representation = period.adaptationSets.get(key.adaptationSetIndex)
.representations.get(key.representationIndex);
long startUs = C.msToUs(period.startMs);
String baseUrl = representation.baseUrl; String baseUrl = representation.baseUrl;
RangedUri initializationUri = representation.getInitializationUri(); RangedUri initializationUri = representation.getInitializationUri();
if (initializationUri != null) { if (initializationUri != null) {
addSegment(segments, startUs, baseUrl, initializationUri); addSegment(periodStartUs, baseUrl, initializationUri, out);
} }
RangedUri indexUri = representation.getIndexUri(); RangedUri indexUri = representation.getIndexUri();
if (indexUri != null) { if (indexUri != null) {
addSegment(segments, startUs, baseUrl, indexUri); addSegment(periodStartUs, baseUrl, indexUri, out);
} }
long firstSegmentNum = index.getFirstSegmentNum(); long firstSegmentNum = index.getFirstSegmentNum();
long lastSegmentNum = firstSegmentNum + segmentCount - 1; long lastSegmentNum = firstSegmentNum + segmentCount - 1;
for (long j = firstSegmentNum; j <= lastSegmentNum; j++) { for (long j = firstSegmentNum; j <= lastSegmentNum; j++) {
addSegment(segments, startUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j)); addSegment(periodStartUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j), out);
} }
} }
return segments;
} }
/** private static void addSegment(
* Returns DashSegmentIndex for given representation. long startTimeUs, String baseUrl, RangedUri rangedUri, ArrayList<Segment> out) {
*/ DataSpec dataSpec =
private DashSegmentIndex getSegmentIndex(DataSource dataSource, DashManifest manifest, new DataSpec(rangedUri.resolveUri(baseUrl), rangedUri.start, rangedUri.length, null);
RepresentationKey key) throws IOException, InterruptedException { out.add(new Segment(startTimeUs, dataSpec));
AdaptationSet adaptationSet = manifest.getPeriod(key.periodIndex).adaptationSets.get( }
key.adaptationSetIndex);
Representation representation = adaptationSet.representations.get(key.representationIndex); private static DashSegmentIndex getSegmentIndex(
DataSource dataSource, int trackType, Representation representation)
throws IOException, InterruptedException {
DashSegmentIndex index = representation.getIndex(); DashSegmentIndex index = representation.getIndex();
if (index != null) { if (index != null) {
return index; return index;
} }
ChunkIndex seekMap = DashUtil.loadChunkIndex(dataSource, adaptationSet.type, representation); ChunkIndex seekMap = DashUtil.loadChunkIndex(dataSource, trackType, representation);
return seekMap == null ? null : new DashWrappingSegmentIndex(seekMap); return seekMap == null ? null : new DashWrappingSegmentIndex(seekMap);
} }
private static void addSegment(ArrayList<Segment> segments, long startTimeUs, String baseUrl,
RangedUri rangedUri) {
DataSpec dataSpec = new DataSpec(rangedUri.resolveUri(baseUrl), rangedUri.start,
rangedUri.length, null);
segments.add(new Segment(startTimeUs, dataSpec));
}
} }
...@@ -73,14 +73,17 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re ...@@ -73,14 +73,17 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
protected List<Segment> getSegments( protected List<Segment> getSegments(
DataSource dataSource, DataSource dataSource,
HlsMasterPlaylist manifest, HlsMasterPlaylist manifest,
RenditionKey[] keys,
boolean allowIndexLoadErrors) boolean allowIndexLoadErrors)
throws InterruptedException, IOException { throws InterruptedException, IOException {
HashSet<Uri> encryptionKeyUris = new HashSet<>(); HashSet<Uri> encryptionKeyUris = new HashSet<>();
ArrayList<HlsUrl> renditionUrls = new ArrayList<>();
renditionUrls.addAll(manifest.variants);
renditionUrls.addAll(manifest.audios);
renditionUrls.addAll(manifest.subtitles);
ArrayList<Segment> segments = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>();
for (RenditionKey renditionKey : keys) { for (HlsUrl renditionUrl : renditionUrls) {
HlsMediaPlaylist mediaPlaylist = null; HlsMediaPlaylist mediaPlaylist = null;
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionKey.url); Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url);
try { try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri); mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri);
} catch (IOException e) { } catch (IOException e) {
......
...@@ -21,10 +21,8 @@ import java.util.ArrayList; ...@@ -21,10 +21,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
/** /** Represents an HLS master playlist. */
* Represents an HLS master playlist. public final class HlsMasterPlaylist extends HlsPlaylist<HlsMasterPlaylist> {
*/
public final class HlsMasterPlaylist extends HlsPlaylist {
/** /**
* Represents a url in an HLS master playlist. * Represents a url in an HLS master playlist.
......
...@@ -25,10 +25,8 @@ import java.lang.annotation.RetentionPolicy; ...@@ -25,10 +25,8 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
/** /** Represents an HLS media playlist. */
* Represents an HLS media playlist. public final class HlsMediaPlaylist extends HlsPlaylist<HlsMediaPlaylist> {
*/
public final class HlsMediaPlaylist extends HlsPlaylist {
/** Media segment reference. */ /** Media segment reference. */
@SuppressWarnings("ComparableType") @SuppressWarnings("ComparableType")
......
...@@ -20,7 +20,8 @@ import java.util.Collections; ...@@ -20,7 +20,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** Represents an HLS playlist. */ /** Represents an HLS playlist. */
public abstract class HlsPlaylist implements FilterableManifest<HlsPlaylist, RenditionKey> { public abstract class HlsPlaylist<T extends HlsPlaylist<T>>
implements FilterableManifest<T, RenditionKey> {
/** /**
* The base uri. Used to resolve relative paths. * The base uri. Used to resolve relative paths.
......
...@@ -84,14 +84,18 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey> ...@@ -84,14 +84,18 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey>
} }
@Override @Override
protected List<Segment> getSegments(DataSource dataSource, SsManifest manifest, protected List<Segment> getSegments(
TrackKey[] keys, boolean allowIndexLoadErrors) throws InterruptedException, IOException { DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors)
throws InterruptedException, IOException {
ArrayList<Segment> segments = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>();
for (TrackKey key : keys) { for (StreamElement streamElement : manifest.streamElements) {
StreamElement streamElement = manifest.streamElements[key.streamElementIndex]; for (int i = 0; i < streamElement.formats.length; i++) {
for (int i = 0; i < streamElement.chunkCount; i++) { for (int j = 0; j < streamElement.chunkCount; j++) {
segments.add(new Segment(streamElement.getStartTimeUs(i), segments.add(
new DataSpec(streamElement.buildRequestUri(key.trackIndex, i)))); new Segment(
streamElement.getStartTimeUs(j),
new DataSpec(streamElement.buildRequestUri(i, j))));
}
} }
} }
return segments; return segments;
......
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