mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
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:
parent
6ac252843e
commit
59f01ec333
7 changed files with 88 additions and 66 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue