Clean up requesting non-media segments in downloader implementations

- Enable GZIP for media playlist + encryption key chunk requests in
  HLS, as we do during playback
- Pass around DataSpecs rather than Uris. This will be needed for if
  we add manifest cacheKey support (which seems like a good idea for
  completeness, if nothing else)

PiperOrigin-RevId: 224057139
This commit is contained in:
olly 2018-12-04 23:28:11 +00:00 committed by Oliver Woodman
parent f8b85739b1
commit 22a8aa311b
5 changed files with 81 additions and 47 deletions

View file

@ -62,7 +62,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
private static final int BUFFER_SIZE_BYTES = 128 * 1024;
private final Uri manifestUri;
private final DataSpec manifestDataSpec;
private final Cache cache;
private final CacheDataSource dataSource;
private final CacheDataSource offlineDataSource;
@ -84,7 +84,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
*/
public SegmentDownloader(
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
this.manifestUri = manifestUri;
this.manifestDataSpec = getCompressibleDataSpec(manifestUri);
this.streamKeys = new ArrayList<>(streamKeys);
this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.createCacheDataSource();
@ -171,7 +171,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
@Override
public final void remove() throws InterruptedException {
try {
M manifest = getManifest(offlineDataSource, manifestUri);
M manifest = getManifest(offlineDataSource, manifestDataSpec);
List<Segment> segments = getSegments(offlineDataSource, manifest, true);
for (int i = 0; i < segments.size(); i++) {
removeDataSpec(segments.get(i).dataSpec);
@ -180,7 +180,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Ignore exceptions when removing.
} finally {
// Always attempt to remove the manifest.
removeDataSpec(new DataSpec(manifestUri));
removeDataSpec(manifestDataSpec);
}
}
@ -190,11 +190,11 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
* Loads and parses the manifest.
*
* @param dataSource The {@link DataSource} through which to load.
* @param uri The manifest uri.
* @param dataSpec The manifest {@link DataSpec}.
* @return The manifest.
* @throws IOException If an error occurs reading data.
*/
protected abstract M getManifest(DataSource dataSource, Uri uri) throws IOException;
protected abstract M getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException;
/**
* Returns a list of all downloadable {@link Segment}s for a given manifest.
@ -217,7 +217,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
@SuppressWarnings("NonAtomicVolatileUpdate")
private List<Segment> initDownload() throws IOException, InterruptedException {
M manifest = getManifest(dataSource, manifestUri);
M manifest = getManifest(dataSource, manifestDataSpec);
if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys);
}
@ -252,4 +252,12 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
CacheUtil.remove(dataSpec, cache, cacheKeyFactory);
}
protected static DataSpec getCompressibleDataSpec(Uri uri) {
return new DataSpec(
uri,
/* absoluteStreamPosition= */ 0,
/* length= */ C.LENGTH_UNSET,
/* key= */ null,
/* flags= */ DataSpec.FLAG_ALLOW_GZIP);
}
}

View file

@ -69,6 +69,24 @@ public final class ParsingLoadable<T> implements Loadable {
return Assertions.checkNotNull(loadable.getResult());
}
/**
* Loads a single parsable object.
*
* @param dataSource The {@link DataSource} through which the object should be read.
* @param parser The {@link Parser} to parse the object from the response.
* @param dataSpec The {@link DataSpec} of the object to read.
* @param type The type of the data. One of the {@link C}{@code DATA_TYPE_*} constants.
* @return The parsed object
* @throws IOException Thrown if there is an error while loading or parsing.
*/
public static <T> T load(
DataSource dataSource, Parser<? extends T> parser, DataSpec dataSpec, int type)
throws IOException {
ParsingLoadable<T> loadable = new ParsingLoadable<>(dataSource, dataSpec, type, parser);
loadable.load();
return Assertions.checkNotNull(loadable.getResult());
}
/**
* The {@link DataSpec} that defines the data to be loaded.
*/

View file

@ -28,11 +28,13 @@ import com.google.android.exoplayer2.source.dash.DashUtil;
import com.google.android.exoplayer2.source.dash.DashWrappingSegmentIndex;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.dash.manifest.Period;
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -73,8 +75,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
}
@Override
protected DashManifest getManifest(DataSource dataSource, Uri uri) throws IOException {
return DashUtil.loadManifest(dataSource, uri);
protected DashManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return ParsingLoadable.load(
dataSource, new DashManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
@Override
@ -121,8 +124,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
if (!allowIncompleteList) {
throw e;
}
// Loading failed, but generating an incomplete segment list is allowed. Advance to the next
// representation.
// Generating an incomplete segment list is allowed. Advance to the next representation.
continue;
}

View file

@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
}
@Override
protected HlsPlaylist getManifest(DataSource dataSource, Uri uri) throws IOException {
return loadManifest(dataSource, uri);
protected HlsPlaylist getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return loadManifest(dataSource, dataSpec);
}
@Override
protected List<Segment> getSegments(
DataSource dataSource, HlsPlaylist playlist, boolean allowIncompleteList) throws IOException {
ArrayList<Uri> mediaPlaylistUris = new ArrayList<>();
String baseUri = playlist.baseUri;
ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
if (playlist instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.variants, mediaPlaylistUris);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.audios, mediaPlaylistUris);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.subtitles, mediaPlaylistUris);
addMediaPlaylistDataSpecs(baseUri, masterPlaylist.variants, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(baseUri, masterPlaylist.audios, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(baseUri, masterPlaylist.subtitles, mediaPlaylistDataSpecs);
} else {
mediaPlaylistUris.add(Uri.parse(playlist.baseUri));
mediaPlaylistDataSpecs.add(SegmentDownloader.getCompressibleDataSpec(Uri.parse(baseUri)));
}
ArrayList<Segment> segments = new ArrayList<>();
ArrayList<Segment> segments = new ArrayList<>();
HashSet<Uri> seenEncryptionKeyUris = new HashSet<>();
for (Uri mediaPlaylistUri : mediaPlaylistUris) {
for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) {
segments.add(new Segment(/* startTimeUs= */ 0, mediaPlaylistDataSpec));
HlsMediaPlaylist mediaPlaylist;
try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistUri);
segments.add(new Segment(mediaPlaylist.startTimeUs, new DataSpec(mediaPlaylistUri)));
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistDataSpec);
} catch (IOException e) {
if (!allowIncompleteList) {
throw e;
}
segments.add(new Segment(0, new DataSpec(mediaPlaylistUri)));
// Generating an incomplete segment list is allowed. Advance to the next media playlist.
continue;
}
HlsMediaPlaylist.Segment lastInitSegment = null;
@ -109,39 +111,43 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
if (initSegment != null && initSegment != lastInitSegment) {
lastInitSegment = initSegment;
addSegment(segments, mediaPlaylist, initSegment, seenEncryptionKeyUris);
addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments);
}
addSegment(segments, mediaPlaylist, segment, seenEncryptionKeyUris);
addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments);
}
}
return segments;
}
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException {
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
private void addMediaPlaylistDataSpecs(String baseUri, List<HlsUrl> urls, List<DataSpec> out) {
for (int i = 0; i < urls.size(); i++) {
Uri playlistUri = UriUtil.resolveToUri(baseUri, urls.get(i).url);
out.add(SegmentDownloader.getCompressibleDataSpec(playlistUri));
}
}
private static void addSegment(
ArrayList<Segment> segments,
private static HlsPlaylist loadManifest(DataSource dataSource, DataSpec dataSpec)
throws IOException {
return ParsingLoadable.load(
dataSource, new HlsPlaylistParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
private void addSegment(
HlsMediaPlaylist mediaPlaylist,
HlsMediaPlaylist.Segment hlsSegment,
HashSet<Uri> seenEncryptionKeyUris) {
long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs;
if (hlsSegment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri,
hlsSegment.fullSegmentEncryptionKeyUri);
HlsMediaPlaylist.Segment segment,
HashSet<Uri> seenEncryptionKeyUris,
ArrayList<Segment> out) {
String baseUri = mediaPlaylist.baseUri;
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
if (segment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(baseUri, segment.fullSegmentEncryptionKeyUri);
if (seenEncryptionKeyUris.add(keyUri)) {
segments.add(new Segment(startTimeUs, new DataSpec(keyUri)));
out.add(new Segment(startTimeUs, SegmentDownloader.getCompressibleDataSpec(keyUri)));
}
}
Uri resolvedUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, hlsSegment.url);
segments.add(new Segment(startTimeUs,
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null)));
}
private static void addResolvedUris(String baseUri, List<HlsUrl> urls, List<Uri> out) {
for (int i = 0; i < urls.size(); i++) {
out.add(UriUtil.resolveToUri(baseUri, urls.get(i).url));
}
Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
DataSpec dataSpec =
new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null);
out.add(new Segment(startTimeUs, dataSpec));
}
}

View file

@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
}
@Override
protected SsManifest getManifest(DataSource dataSource, Uri uri) throws IOException {
return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST);
protected SsManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return ParsingLoadable.load(dataSource, new SsManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
@Override