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;