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
This commit is contained in:
olly 2018-04-26 05:00:12 -07:00 committed by Oliver Woodman
parent 6ac252843e
commit 59f01ec333
7 changed files with 88 additions and 66 deletions

View file

@ -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.util.PriorityTaskManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -38,7 +39,8 @@ import java.util.List;
* @param <M> The type of the manifest 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. */
protected static class Segment implements Comparable<Segment> {
@ -68,9 +70,9 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
private final Cache cache;
private final CacheDataSource dataSource;
private final CacheDataSource offlineDataSource;
private final ArrayList<K> keys;
private M manifest;
private K[] keys;
private volatile int totalSegments;
private volatile int downloadedSegments;
private volatile long downloadedBytes;
@ -85,6 +87,7 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
this.dataSource = constructorHelper.buildCacheDataSource(false);
this.offlineDataSource = constructorHelper.buildCacheDataSource(true);
this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
keys = new ArrayList<>();
resetCounters();
}
@ -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
* 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) {
this.keys = keys.length > 0 ? keys.clone() : null;
this.keys.clear();
Collections.addAll(this.keys, keys);
resetCounters();
}
@ -223,7 +228,7 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
if (manifest != null) {
List<Segment> segments = null;
try {
segments = getSegments(offlineDataSource, manifest, getAllRepresentationKeys(), true);
segments = getSegments(offlineDataSource, manifest, true);
} catch (IOException e) {
// Ignore exceptions. We do our best with what's available offline.
}
@ -248,11 +253,10 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
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 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
* segments from being listed. If true then a partial segment list will be returned. If false
* an {@link IOException} will be thrown.
@ -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.
* @return A list of {@link Segment}s for given keys.
*/
protected abstract List<Segment> getSegments(DataSource dataSource, M manifest, K[] keys,
boolean allowIncompleteIndex) throws InterruptedException, IOException;
protected abstract List<Segment> getSegments(
DataSource dataSource, M manifest, boolean allowIncompleteIndex)
throws InterruptedException, IOException;
private void resetCounters() {
totalSegments = C.LENGTH_UNSET;
@ -283,10 +288,8 @@ public abstract class SegmentDownloader<M, K> implements Downloader {
private synchronized List<Segment> initStatus(boolean offline)
throws IOException, InterruptedException {
DataSource dataSource = getDataSource(offline);
if (keys == null) {
keys = getAllRepresentationKeys();
}
List<Segment> segments = getSegments(dataSource, manifest, keys, offline);
M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys);
List<Segment> segments = getSegments(dataSource, filteredManifest, offline);
CachingCounters cachingCounters = new CachingCounters();
totalSegments = segments.size();
downloadedSegments = 0;

View file

@ -89,77 +89,92 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
}
@Override
protected List<Segment> getSegments(DataSource dataSource, DashManifest manifest,
RepresentationKey[] keys, boolean allowIndexLoadErrors)
protected List<Segment> getSegments(
DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors)
throws InterruptedException, IOException {
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;
try {
index = getSegmentIndex(dataSource, manifest, key);
index = getSegmentIndex(dataSource, adaptationSet.type, representation);
if (index == null) {
// Loading succeeded but there was no index. This is always a failure.
throw new DownloadException("No index for representation: " + key);
// Loading succeeded but there was no index.
throw new DownloadException("Missing segment index");
}
} catch (IOException e) {
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;
} else {
throw e;
}
}
long periodDurationUs = manifest.getPeriodDurationUs(key.periodIndex);
int segmentCount = index.getSegmentCount(periodDurationUs);
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;
RangedUri initializationUri = representation.getInitializationUri();
if (initializationUri != null) {
addSegment(segments, startUs, baseUrl, initializationUri);
addSegment(periodStartUs, baseUrl, initializationUri, out);
}
RangedUri indexUri = representation.getIndexUri();
if (indexUri != null) {
addSegment(segments, startUs, baseUrl, indexUri);
addSegment(periodStartUs, baseUrl, indexUri, out);
}
long firstSegmentNum = index.getFirstSegmentNum();
long lastSegmentNum = firstSegmentNum + segmentCount - 1;
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;
}
/**
* Returns DashSegmentIndex for given representation.
*/
private DashSegmentIndex getSegmentIndex(DataSource dataSource, DashManifest manifest,
RepresentationKey key) throws IOException, InterruptedException {
AdaptationSet adaptationSet = manifest.getPeriod(key.periodIndex).adaptationSets.get(
key.adaptationSetIndex);
Representation representation = adaptationSet.representations.get(key.representationIndex);
private static void addSegment(
long startTimeUs, String baseUrl, RangedUri rangedUri, ArrayList<Segment> out) {
DataSpec dataSpec =
new DataSpec(rangedUri.resolveUri(baseUrl), rangedUri.start, rangedUri.length, null);
out.add(new Segment(startTimeUs, dataSpec));
}
private static DashSegmentIndex getSegmentIndex(
DataSource dataSource, int trackType, Representation representation)
throws IOException, InterruptedException {
DashSegmentIndex index = representation.getIndex();
if (index != null) {
return index;
}
ChunkIndex seekMap = DashUtil.loadChunkIndex(dataSource, adaptationSet.type, representation);
ChunkIndex seekMap = DashUtil.loadChunkIndex(dataSource, trackType, representation);
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));
}
}

View file

@ -73,14 +73,17 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
protected List<Segment> getSegments(
DataSource dataSource,
HlsMasterPlaylist manifest,
RenditionKey[] keys,
boolean allowIndexLoadErrors)
throws InterruptedException, IOException {
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<>();
for (RenditionKey renditionKey : keys) {
for (HlsUrl renditionUrl : renditionUrls) {
HlsMediaPlaylist mediaPlaylist = null;
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionKey.url);
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url);
try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri);
} catch (IOException e) {

View file

@ -21,10 +21,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Represents an HLS master playlist.
*/
public final class HlsMasterPlaylist extends HlsPlaylist {
/** Represents an HLS master playlist. */
public final class HlsMasterPlaylist extends HlsPlaylist<HlsMasterPlaylist> {
/**
* Represents a url in an HLS master playlist.

View file

@ -25,10 +25,8 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
/**
* Represents an HLS media playlist.
*/
public final class HlsMediaPlaylist extends HlsPlaylist {
/** Represents an HLS media playlist. */
public final class HlsMediaPlaylist extends HlsPlaylist<HlsMediaPlaylist> {
/** Media segment reference. */
@SuppressWarnings("ComparableType")

View file

@ -20,7 +20,8 @@ import java.util.Collections;
import java.util.List;
/** 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.

View file

@ -84,14 +84,18 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey>
}
@Override
protected List<Segment> getSegments(DataSource dataSource, SsManifest manifest,
TrackKey[] keys, boolean allowIndexLoadErrors) throws InterruptedException, IOException {
protected List<Segment> getSegments(
DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors)
throws InterruptedException, IOException {
ArrayList<Segment> segments = new ArrayList<>();
for (TrackKey key : keys) {
StreamElement streamElement = manifest.streamElements[key.streamElementIndex];
for (int i = 0; i < streamElement.chunkCount; i++) {
segments.add(new Segment(streamElement.getStartTimeUs(i),
new DataSpec(streamElement.buildRequestUri(key.trackIndex, i))));
for (StreamElement streamElement : manifest.streamElements) {
for (int i = 0; i < streamElement.formats.length; i++) {
for (int j = 0; j < streamElement.chunkCount; j++) {
segments.add(
new Segment(
streamElement.getStartTimeUs(j),
new DataSpec(streamElement.buildRequestUri(i, j))));
}
}
}
return segments;