From 59f01ec333dec260a9afe7de205bca9dcd725d45 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 26 Apr 2018 05:00:12 -0700 Subject: [PATCH] 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 --- .../exoplayer2/offline/SegmentDownloader.java | 29 ++++--- .../source/dash/offline/DashDownloader.java | 83 +++++++++++-------- .../source/hls/offline/HlsDownloader.java | 9 +- .../hls/playlist/HlsMasterPlaylist.java | 6 +- .../source/hls/playlist/HlsMediaPlaylist.java | 6 +- .../source/hls/playlist/HlsPlaylist.java | 3 +- .../smoothstreaming/offline/SsDownloader.java | 18 ++-- 7 files changed, 88 insertions(+), 66 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 a36c08e0f9..fe98bff5f1 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 @@ -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 The type of the manifest object. * @param The type of the representation key object. */ -public abstract class SegmentDownloader implements Downloader { +public abstract class SegmentDownloader, K> + implements Downloader { /** Smallest unit of content to be downloaded. */ protected static class Segment implements Comparable { @@ -68,9 +70,9 @@ public abstract class SegmentDownloader implements Downloader { private final Cache cache; private final CacheDataSource dataSource; private final CacheDataSource offlineDataSource; + private final ArrayList 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 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 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 implements Downloader { if (manifest != null) { List 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 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 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 getSegments(DataSource dataSource, M manifest, K[] keys, - boolean allowIncompleteIndex) throws InterruptedException, IOException; + protected abstract List 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 implements Downloader { private synchronized List initStatus(boolean offline) throws IOException, InterruptedException { DataSource dataSource = getDataSource(offline); - if (keys == null) { - keys = getAllRepresentationKeys(); - } - List segments = getSegments(dataSource, manifest, keys, offline); + M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys); + List segments = getSegments(dataSource, filteredManifest, offline); CachingCounters cachingCounters = new CachingCounters(); totalSegments = segments.size(); downloadedSegments = 0; 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 85048a27ae..23f1ff4f79 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 @@ -89,77 +89,92 @@ public final class DashDownloader extends SegmentDownloader getSegments(DataSource dataSource, DashManifest manifest, - RepresentationKey[] keys, boolean allowIndexLoadErrors) + protected List getSegments( + DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors) throws InterruptedException, IOException { ArrayList 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 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 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 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 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)); - } - } 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 1ede70c666..5794e4f246 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 @@ -73,14 +73,17 @@ public final class HlsDownloader extends SegmentDownloader getSegments( DataSource dataSource, HlsMasterPlaylist manifest, - RenditionKey[] keys, boolean allowIndexLoadErrors) throws InterruptedException, IOException { HashSet encryptionKeyUris = new HashSet<>(); + ArrayList renditionUrls = new ArrayList<>(); + renditionUrls.addAll(manifest.variants); + renditionUrls.addAll(manifest.audios); + renditionUrls.addAll(manifest.subtitles); ArrayList 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) { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index b98a3b4cd5..699436d8d9 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -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 { /** * Represents a url in an HLS master playlist. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 094ce56107..64c6c3749e 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -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 { /** Media segment reference. */ @SuppressWarnings("ComparableType") diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java index 34ecde229d..98e8ffb3b6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java @@ -20,7 +20,8 @@ import java.util.Collections; import java.util.List; /** Represents an HLS playlist. */ -public abstract class HlsPlaylist implements FilterableManifest { +public abstract class HlsPlaylist> + implements FilterableManifest { /** * The base uri. Used to resolve relative paths. 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 350d1723b8..5ec287537d 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 @@ -84,14 +84,18 @@ public final class SsDownloader extends SegmentDownloader } @Override - protected List getSegments(DataSource dataSource, SsManifest manifest, - TrackKey[] keys, boolean allowIndexLoadErrors) throws InterruptedException, IOException { + protected List getSegments( + DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors) + throws InterruptedException, IOException { ArrayList 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;