mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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:
parent
f8b85739b1
commit
22a8aa311b
5 changed files with 81 additions and 47 deletions
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
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
|
||||||
@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
|
||||||
// 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
|
||||||
* 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
|
||||||
// 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
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,24 @@ public final class ParsingLoadable<T> implements Loadable {
|
||||||
return Assertions.checkNotNull(loadable.getResult());
|
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.
|
* The {@link DataSpec} that defines the data to be loaded.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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> {
|
||||||
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> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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> {
|
||||||
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> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue