From 22a8aa311bf37936e4e4d51b3ab40863ea5d885d Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 4 Dec 2018 23:28:11 +0000 Subject: [PATCH] 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 --- .../exoplayer2/offline/SegmentDownloader.java | 22 ++++-- .../exoplayer2/upstream/ParsingLoadable.java | 18 +++++ .../source/dash/offline/DashDownloader.java | 10 ++- .../source/hls/offline/HlsDownloader.java | 74 ++++++++++--------- .../smoothstreaming/offline/SsDownloader.java | 4 +- 5 files changed, 81 insertions(+), 47 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index e55d2a1baf..1b32abff60 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -62,7 +62,7 @@ public abstract class SegmentDownloader> 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> impleme */ public SegmentDownloader( Uri manifestUri, List 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> impleme @Override public final void remove() throws InterruptedException { try { - M manifest = getManifest(offlineDataSource, manifestUri); + M manifest = getManifest(offlineDataSource, manifestDataSpec); List 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> 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> 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> impleme // Writes to downloadedSegments and downloadedBytes are safe. See the comment on download(). @SuppressWarnings("NonAtomicVolatileUpdate") private List 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> 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); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java index 48e03a0083..b41f1aa09f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java @@ -69,6 +69,24 @@ public final class ParsingLoadable 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 load( + DataSource dataSource, Parser parser, DataSpec dataSpec, int type) + throws IOException { + ParsingLoadable loadable = new ParsingLoadable<>(dataSource, dataSpec, type, parser); + loadable.load(); + return Assertions.checkNotNull(loadable.getResult()); + } + /** * The {@link DataSpec} that defines the data to be loaded. */ diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 68120d6177..5dad468724 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -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 { } @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 { 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; } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java index 85f41df359..a0f64f298e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java @@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader { } @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 getSegments( DataSource dataSource, HlsPlaylist playlist, boolean allowIncompleteList) throws IOException { - ArrayList mediaPlaylistUris = new ArrayList<>(); + String baseUri = playlist.baseUri; + + ArrayList 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 segments = new ArrayList<>(); + ArrayList segments = new ArrayList<>(); HashSet 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 { 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 urls, List 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 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 seenEncryptionKeyUris) { - long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs; - if (hlsSegment.fullSegmentEncryptionKeyUri != null) { - Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, - hlsSegment.fullSegmentEncryptionKeyUri); + HlsMediaPlaylist.Segment segment, + HashSet seenEncryptionKeyUris, + ArrayList 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 urls, List 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)); } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java index 84ef251e5f..18820ca49c 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java @@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader { } @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