Commit 22a8aa31 by olly Committed by Oliver Woodman

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
parent f8b85739
...@@ -62,7 +62,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -62,7 +62,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
private static final int BUFFER_SIZE_BYTES = 128 * 1024; private static final int BUFFER_SIZE_BYTES = 128 * 1024;
private final Uri manifestUri; private final DataSpec manifestDataSpec;
private final Cache cache; private final Cache cache;
private final CacheDataSource dataSource; private final CacheDataSource dataSource;
private final CacheDataSource offlineDataSource; private final CacheDataSource offlineDataSource;
...@@ -84,7 +84,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -84,7 +84,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
*/ */
public SegmentDownloader( public SegmentDownloader(
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) { Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
this.manifestUri = manifestUri; this.manifestDataSpec = getCompressibleDataSpec(manifestUri);
this.streamKeys = new ArrayList<>(streamKeys); this.streamKeys = new ArrayList<>(streamKeys);
this.cache = constructorHelper.getCache(); this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.createCacheDataSource(); this.dataSource = constructorHelper.createCacheDataSource();
...@@ -171,7 +171,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -171,7 +171,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
@Override @Override
public final void remove() throws InterruptedException { public final void remove() throws InterruptedException {
try { try {
M manifest = getManifest(offlineDataSource, manifestUri); M manifest = getManifest(offlineDataSource, manifestDataSpec);
List<Segment> segments = getSegments(offlineDataSource, manifest, true); List<Segment> segments = getSegments(offlineDataSource, manifest, true);
for (int i = 0; i < segments.size(); i++) { for (int i = 0; i < segments.size(); i++) {
removeDataSpec(segments.get(i).dataSpec); removeDataSpec(segments.get(i).dataSpec);
...@@ -180,7 +180,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -180,7 +180,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Ignore exceptions when removing. // Ignore exceptions when removing.
} finally { } finally {
// Always attempt to remove the manifest. // 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 ...@@ -190,11 +190,11 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
* Loads and parses the manifest. * Loads and parses the manifest.
* *
* @param dataSource The {@link DataSource} through which to load. * @param dataSource The {@link DataSource} through which to load.
* @param uri The manifest uri. * @param dataSpec The manifest {@link DataSpec}.
* @return The manifest. * @return The manifest.
* @throws IOException If an error occurs reading data. * @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. * 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 ...@@ -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(). // Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
@SuppressWarnings("NonAtomicVolatileUpdate") @SuppressWarnings("NonAtomicVolatileUpdate")
private List<Segment> initDownload() throws IOException, InterruptedException { private List<Segment> initDownload() throws IOException, InterruptedException {
M manifest = getManifest(dataSource, manifestUri); M manifest = getManifest(dataSource, manifestDataSpec);
if (!streamKeys.isEmpty()) { if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys); manifest = manifest.copy(streamKeys);
} }
...@@ -252,4 +252,12 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -252,4 +252,12 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
CacheUtil.remove(dataSpec, cache, cacheKeyFactory); 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);
}
} }
...@@ -70,6 +70,24 @@ public final class ParsingLoadable<T> implements Loadable { ...@@ -70,6 +70,24 @@ public final class ParsingLoadable<T> implements Loadable {
} }
/** /**
* 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. * The {@link DataSpec} that defines the data to be loaded.
*/ */
public final DataSpec dataSpec; public final DataSpec dataSpec;
......
...@@ -28,11 +28,13 @@ import com.google.android.exoplayer2.source.dash.DashUtil; ...@@ -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.DashWrappingSegmentIndex;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; 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.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.Period;
import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -73,8 +75,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> { ...@@ -73,8 +75,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
} }
@Override @Override
protected DashManifest getManifest(DataSource dataSource, Uri uri) throws IOException { protected DashManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return DashUtil.loadManifest(dataSource, uri); return ParsingLoadable.load(
dataSource, new DashManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
} }
@Override @Override
...@@ -121,8 +124,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> { ...@@ -121,8 +124,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
if (!allowIncompleteList) { if (!allowIncompleteList) {
throw e; throw e;
} }
// Loading failed, but generating an incomplete segment list is allowed. Advance to the next // Generating an incomplete segment list is allowed. Advance to the next representation.
// representation.
continue; continue;
} }
......
...@@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
} }
@Override @Override
protected HlsPlaylist getManifest(DataSource dataSource, Uri uri) throws IOException { protected HlsPlaylist getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return loadManifest(dataSource, uri); return loadManifest(dataSource, dataSpec);
} }
@Override @Override
protected List<Segment> getSegments( protected List<Segment> getSegments(
DataSource dataSource, HlsPlaylist playlist, boolean allowIncompleteList) throws IOException { 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) { if (playlist instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.variants, mediaPlaylistUris); addMediaPlaylistDataSpecs(baseUri, masterPlaylist.variants, mediaPlaylistDataSpecs);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.audios, mediaPlaylistUris); addMediaPlaylistDataSpecs(baseUri, masterPlaylist.audios, mediaPlaylistDataSpecs);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.subtitles, mediaPlaylistUris); addMediaPlaylistDataSpecs(baseUri, masterPlaylist.subtitles, mediaPlaylistDataSpecs);
} else { } 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<>(); HashSet<Uri> seenEncryptionKeyUris = new HashSet<>();
for (Uri mediaPlaylistUri : mediaPlaylistUris) { for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) {
segments.add(new Segment(/* startTimeUs= */ 0, mediaPlaylistDataSpec));
HlsMediaPlaylist mediaPlaylist; HlsMediaPlaylist mediaPlaylist;
try { try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistUri); mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistDataSpec);
segments.add(new Segment(mediaPlaylist.startTimeUs, new DataSpec(mediaPlaylistUri)));
} catch (IOException e) { } catch (IOException e) {
if (!allowIncompleteList) { if (!allowIncompleteList) {
throw e; throw e;
} }
segments.add(new Segment(0, new DataSpec(mediaPlaylistUri))); // Generating an incomplete segment list is allowed. Advance to the next media playlist.
continue; continue;
} }
HlsMediaPlaylist.Segment lastInitSegment = null; HlsMediaPlaylist.Segment lastInitSegment = null;
...@@ -109,39 +111,43 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -109,39 +111,43 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
HlsMediaPlaylist.Segment initSegment = segment.initializationSegment; HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
if (initSegment != null && initSegment != lastInitSegment) { if (initSegment != null && initSegment != lastInitSegment) {
lastInitSegment = initSegment; lastInitSegment = initSegment;
addSegment(segments, mediaPlaylist, initSegment, seenEncryptionKeyUris); addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments);
} }
addSegment(segments, mediaPlaylist, segment, seenEncryptionKeyUris); addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments);
} }
} }
return segments; return segments;
} }
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException { private void addMediaPlaylistDataSpecs(String baseUri, List<HlsUrl> urls, List<DataSpec> out) {
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST); 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( private static HlsPlaylist loadManifest(DataSource dataSource, DataSpec dataSpec)
ArrayList<Segment> segments, throws IOException {
return ParsingLoadable.load(
dataSource, new HlsPlaylistParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
private void addSegment(
HlsMediaPlaylist mediaPlaylist, HlsMediaPlaylist mediaPlaylist,
HlsMediaPlaylist.Segment hlsSegment, HlsMediaPlaylist.Segment segment,
HashSet<Uri> seenEncryptionKeyUris) { HashSet<Uri> seenEncryptionKeyUris,
long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs; ArrayList<Segment> out) {
if (hlsSegment.fullSegmentEncryptionKeyUri != null) { String baseUri = mediaPlaylist.baseUri;
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
hlsSegment.fullSegmentEncryptionKeyUri); if (segment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(baseUri, segment.fullSegmentEncryptionKeyUri);
if (seenEncryptionKeyUris.add(keyUri)) { 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); Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
segments.add(new Segment(startTimeUs, DataSpec dataSpec =
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null))); new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null);
} out.add(new Segment(startTimeUs, dataSpec));
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));
}
} }
} }
...@@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> { ...@@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
} }
@Override @Override
protected SsManifest getManifest(DataSource dataSource, Uri uri) throws IOException { protected SsManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); return ParsingLoadable.load(dataSource, new SsManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
} }
@Override @Override
......
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