From 175a0100d093bd2ea3f1bacd1d9a9839f0b03134 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 1 May 2018 07:10:36 -0700 Subject: [PATCH] Remove ability to query Downloader implementations This was adding a lot of code, and the multiple use cases for Downloader was pretty confusing (in particular the ordering of method calls was unclear). It's also not performant (e.g. it requires loading/parsing manifest(s) and initialization segments from disk). In practice I think apps will need to keep a record of what's offlined in their app's database (or equivalent), which they can update by registering as a listener on DownloadManager. This will be done for the demo app in a subsequent change. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=194932876 --- .../exoplayer2/demo/DownloadActivity.java | 211 ++++++++---------- .../exoplayer2/offline/Downloader.java | 23 +- .../offline/ProgressiveDownloader.java | 26 +-- .../exoplayer2/offline/SegmentDownloader.java | 176 +++++---------- .../exoplayer2/upstream/ParsingLoadable.java | 16 ++ .../offline/DownloadManagerTest.java | 5 - .../exoplayer2/source/dash/DashUtil.java | 5 +- .../dash/offline/DashDownloadAction.java | 4 +- .../source/dash/offline/DashDownloader.java | 67 ++---- .../dash/offline/DashDownloaderTest.java | 142 +++--------- .../source/hls/offline/HlsDownloadAction.java | 4 +- .../source/hls/offline/HlsDownloader.java | 43 +--- .../source/hls/offline/HlsDownloaderTest.java | 94 ++------ .../offline/SsDownloadAction.java | 4 +- .../smoothstreaming/offline/SsDownloader.java | 38 +--- .../playbacktests/gts/DashDownloadTest.java | 81 +++---- 16 files changed, 320 insertions(+), 619 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java index daf2df58dd..5809a6ec9c 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java @@ -29,28 +29,34 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadService; -import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.ProgressiveDownloadAction; -import com.google.android.exoplayer2.offline.ProgressiveDownloader; +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.Representation; import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction; -import com.google.android.exoplayer2.source.dash.offline.DashDownloader; import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction; -import com.google.android.exoplayer2.source.hls.offline.HlsDownloader; +import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; +import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser; import com.google.android.exoplayer2.source.hls.playlist.RenditionKey; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.source.smoothstreaming.manifest.TrackKey; import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction; -import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader; import com.google.android.exoplayer2.ui.DefaultTrackNameProvider; import com.google.android.exoplayer2.ui.TrackNameProvider; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.util.ParcelableArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** An activity for downloading media. */ @@ -87,23 +93,22 @@ public class DownloadActivity extends Activity { representationList.setAdapter(arrayAdapter); DemoApplication application = (DemoApplication) getApplication(); - DownloaderConstructorHelper constructorHelper = - new DownloaderConstructorHelper( - application.getDownloadCache(), application.buildHttpDataSourceFactory(null)); + DataSource.Factory manifestDataSourceFactory = + application.buildDataSourceFactory(/* listener= */ null); String extension = playerIntent.getStringExtra(EXTENSION_EXTRA); int type = Util.inferContentType(sampleUri, extension); switch (type) { case C.TYPE_DASH: - downloadUtilMethods = new DashDownloadUtilMethods(sampleUri, constructorHelper); + downloadUtilMethods = new DashDownloadUtilMethods(sampleUri, manifestDataSourceFactory); break; case C.TYPE_SS: - downloadUtilMethods = new SsDownloadUtilMethods(sampleUri, constructorHelper); + downloadUtilMethods = new SsDownloadUtilMethods(sampleUri, manifestDataSourceFactory); break; case C.TYPE_HLS: - downloadUtilMethods = new HlsDownloadUtilMethods(sampleUri, constructorHelper); + downloadUtilMethods = new HlsDownloadUtilMethods(sampleUri, manifestDataSourceFactory); break; case C.TYPE_OTHER: - downloadUtilMethods = new ProgressiveDownloadUtilMethods(sampleUri, constructorHelper); + downloadUtilMethods = new ProgressiveDownloadUtilMethods(sampleUri); break; default: throw new IllegalStateException("Unsupported type: " + type); @@ -148,13 +153,17 @@ public class DownloadActivity extends Activity { DownloadService.addDownloadAction( this, DemoDownloadService.class, - downloadUtilMethods.getDownloadAction(sampleName, representationKeys)); + downloadUtilMethods.getDownloadAction( + /* isRemoveAction= */ false, sampleName, representationKeys)); } } private void removeDownload() { DownloadService.addDownloadAction( - this, DemoDownloadService.class, downloadUtilMethods.getRemoveAction()); + this, + DemoDownloadService.class, + downloadUtilMethods.getDownloadAction( + /* isRemoveAction= */ true, sampleName, Collections.emptyList())); for (int i = 0; i < representationList.getChildCount(); i++) { representationList.setItemChecked(i, false); } @@ -199,18 +208,15 @@ public class DownloadActivity extends Activity { public final Parcelable key; public final String title; - public final int percentDownloaded; - public RepresentationItem(Parcelable key, String title, float percentDownloaded) { + public RepresentationItem(Parcelable key, String title) { this.key = key; this.title = title; - this.percentDownloaded = - (int) (percentDownloaded == C.PERCENTAGE_UNSET ? 0 : percentDownloaded); } @Override public String toString() { - return title + " (" + percentDownloaded + "%)"; + return title; } } @@ -220,7 +226,7 @@ public class DownloadActivity extends Activity { @Override protected List doInBackground(Void... ignore) { try { - return downloadUtilMethods.getRepresentationItems(trackNameProvider); + return downloadUtilMethods.loadRepresentationItems(trackNameProvider); } catch (IOException | InterruptedException e) { return null; } @@ -244,173 +250,152 @@ public class DownloadActivity extends Activity { private abstract static class DownloadUtilMethods { protected final Uri manifestUri; - protected final DownloaderConstructorHelper constructorHelper; - public DownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + public DownloadUtilMethods(Uri manifestUri) { this.manifestUri = manifestUri; - this.constructorHelper = constructorHelper; } - public abstract List getRepresentationItems( + public abstract List loadRepresentationItems( TrackNameProvider trackNameProvider) throws IOException, InterruptedException; public abstract DownloadAction getDownloadAction( - String sampleName, ArrayList representationKeys); - - public abstract DownloadAction getRemoveAction(); + boolean isRemoveAction, String sampleName, List representationKeys); } private static final class DashDownloadUtilMethods extends DownloadUtilMethods { - public DashDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); + private final DataSource.Factory manifestDataSourceFactory; + + public DashDownloadUtilMethods(Uri manifestUri, DataSource.Factory manifestDataSourceFactory) { + super(manifestUri); + this.manifestDataSourceFactory = manifestDataSourceFactory; } @Override - public List getRepresentationItems(TrackNameProvider trackNameProvider) + public List loadRepresentationItems(TrackNameProvider trackNameProvider) throws IOException, InterruptedException { - DashDownloader downloader = new DashDownloader(manifestUri, constructorHelper); + DataSource dataSource = manifestDataSourceFactory.createDataSource(); + DashManifest manifest = + ParsingLoadable.load(dataSource, new DashManifestParser(), manifestUri); + ArrayList items = new ArrayList<>(); - for (RepresentationKey key : downloader.getAllRepresentationKeys()) { - downloader.selectRepresentations(new RepresentationKey[] {key}); - try { - downloader.init(); - } catch (IOException e) { - continue; + for (int periodIndex = 0; periodIndex < manifest.getPeriodCount(); periodIndex++) { + List adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; + for (int adaptationIndex = 0; adaptationIndex < adaptationSets.size(); adaptationIndex++) { + List representations = + adaptationSets.get(adaptationIndex).representations; + int representationsCount = representations.size(); + for (int i = 0; i < representationsCount; i++) { + RepresentationKey key = new RepresentationKey(periodIndex, adaptationIndex, i); + String trackName = trackNameProvider.getTrackName(representations.get(i).format); + items.add(new RepresentationItem(key, trackName)); + } } - Representation representation = - downloader - .getManifest() - .getPeriod(key.periodIndex) - .adaptationSets - .get(key.adaptationSetIndex) - .representations - .get(key.representationIndex); - String trackName = trackNameProvider.getTrackName(representation.format); - items.add(new RepresentationItem(key, trackName, downloader.getDownloadPercentage())); } return items; } @Override public DownloadAction getDownloadAction( - String sampleName, ArrayList representationKeys) { + boolean isRemoveAction, String sampleName, List representationKeys) { RepresentationKey[] keys = representationKeys.toArray(new RepresentationKey[representationKeys.size()]); - return new DashDownloadAction(/* isRemoveAction= */ false, sampleName, manifestUri, keys); + return new DashDownloadAction(isRemoveAction, sampleName, manifestUri, keys); } - @Override - public DownloadAction getRemoveAction() { - return new DashDownloadAction(/* isRemoveAction= */ true, /* data= */ null, manifestUri); - } } private static final class HlsDownloadUtilMethods extends DownloadUtilMethods { - public HlsDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); + private final DataSource.Factory manifestDataSourceFactory; + + public HlsDownloadUtilMethods(Uri manifestUri, DataSource.Factory manifestDataSourceFactory) { + super(manifestUri); + this.manifestDataSourceFactory = manifestDataSourceFactory; } @Override - public List getRepresentationItems(TrackNameProvider trackNameProvider) + public List loadRepresentationItems(TrackNameProvider trackNameProvider) throws IOException, InterruptedException { - HlsDownloader downloader = new HlsDownloader(manifestUri, constructorHelper); + DataSource dataSource = manifestDataSourceFactory.createDataSource(); + HlsPlaylist playlist = + ParsingLoadable.load(dataSource, new HlsPlaylistParser(), manifestUri); + ArrayList items = new ArrayList<>(); - for (RenditionKey key : downloader.getAllRepresentationKeys()) { - downloader.selectRepresentations(new RenditionKey[] {key}); - try { - downloader.init(); - } catch (IOException e) { - continue; + if (playlist instanceof HlsMediaPlaylist) { + items.add(new RepresentationItem(null, "Stream")); + } else { + HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; + ArrayList hlsUrls = new ArrayList<>(); + hlsUrls.addAll(masterPlaylist.variants); + hlsUrls.addAll(masterPlaylist.audios); + hlsUrls.addAll(masterPlaylist.subtitles); + for (HlsMasterPlaylist.HlsUrl hlsUrl : hlsUrls) { + items.add(new RepresentationItem(new RenditionKey(hlsUrl.url), hlsUrl.url)); } - items.add(new RepresentationItem(key, key.url, downloader.getDownloadPercentage())); } return items; } @Override public DownloadAction getDownloadAction( - String sampleName, ArrayList representationKeys) { + boolean isRemoveAction, String sampleName, List representationKeys) { RenditionKey[] keys = representationKeys.toArray(new RenditionKey[representationKeys.size()]); - return new HlsDownloadAction(/* isRemoveAction= */ false, sampleName, manifestUri, keys); - } - - @Override - public DownloadAction getRemoveAction() { - return new HlsDownloadAction(/* isRemoveAction= */ true, /* data= */ null, manifestUri); + return new HlsDownloadAction(isRemoveAction, sampleName, manifestUri, keys); } } private static final class SsDownloadUtilMethods extends DownloadUtilMethods { - public SsDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); + private final DataSource.Factory manifestDataSourceFactory; + + public SsDownloadUtilMethods(Uri manifestUri, DataSource.Factory manifestDataSourceFactory) { + super(manifestUri); + this.manifestDataSourceFactory = manifestDataSourceFactory; } @Override - public List getRepresentationItems(TrackNameProvider trackNameProvider) + public List loadRepresentationItems(TrackNameProvider trackNameProvider) throws IOException, InterruptedException { - SsDownloader downloader = new SsDownloader(manifestUri, constructorHelper); + DataSource dataSource = manifestDataSourceFactory.createDataSource(); + SsManifest manifest = ParsingLoadable.load(dataSource, new SsManifestParser(), manifestUri); + ArrayList items = new ArrayList<>(); - for (TrackKey key : downloader.getAllRepresentationKeys()) { - downloader.selectRepresentations(new TrackKey[] {key}); - try { - downloader.init(); - } catch (IOException e) { - continue; + for (int i = 0; i < manifest.streamElements.length; i++) { + SsManifest.StreamElement streamElement = manifest.streamElements[i]; + for (int j = 0; j < streamElement.formats.length; j++) { + TrackKey key = new TrackKey(i, j); + String trackName = trackNameProvider.getTrackName(streamElement.formats[j]); + items.add(new RepresentationItem(key, trackName)); } - Format format = - downloader.getManifest().streamElements[key.streamElementIndex].formats[key.trackIndex]; - String trackName = trackNameProvider.getTrackName(format); - items.add(new RepresentationItem(key, trackName, downloader.getDownloadPercentage())); } return items; } @Override public DownloadAction getDownloadAction( - String sampleName, ArrayList representationKeys) { + boolean isRemoveAction, String sampleName, List representationKeys) { TrackKey[] keys = representationKeys.toArray(new TrackKey[representationKeys.size()]); - return new SsDownloadAction(/* isRemoveAction= */ false, sampleName, manifestUri, keys); - } - - @Override - public DownloadAction getRemoveAction() { - return new SsDownloadAction(/* isRemoveAction= */ true, /* data= */ null, manifestUri); + return new SsDownloadAction(isRemoveAction, sampleName, manifestUri, keys); } } private static final class ProgressiveDownloadUtilMethods extends DownloadUtilMethods { - public ProgressiveDownloadUtilMethods( - Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); + public ProgressiveDownloadUtilMethods(Uri manifestUri) { + super(manifestUri); } @Override - public List getRepresentationItems(TrackNameProvider trackNameProvider) { - ProgressiveDownloader downloader = - new ProgressiveDownloader(manifestUri, null, constructorHelper); - ArrayList items = new ArrayList<>(); - { - downloader.init(); - items.add(new RepresentationItem(null, "Stream", downloader.getDownloadPercentage())); - } - return items; + public List loadRepresentationItems(TrackNameProvider trackNameProvider) { + return Collections.singletonList(new RepresentationItem(null, "Stream")); } @Override public DownloadAction getDownloadAction( - String sampleName, ArrayList representationKeys) { + boolean isRemoveAction, String sampleName, List representationKeys) { return new ProgressiveDownloadAction( - /* isRemoveAction= */ false, /* data= */ null, manifestUri, /* customCacheKey= */ null); - } - - @Override - public DownloadAction getRemoveAction() { - return new ProgressiveDownloadAction( - /* isRemoveAction= */ true, /* data= */ null, manifestUri, /* customCacheKey= */ null); + isRemoveAction, sampleName, manifestUri, /* customCacheKey= */ null); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/Downloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/Downloader.java index 3390a4ed44..bbcd955e11 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/Downloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/Downloader.java @@ -23,15 +23,6 @@ import java.io.IOException; */ public interface Downloader { - /** - * Initializes the downloader. - * - * @throws DownloadException Thrown if the media cannot be downloaded. - * @throws InterruptedException If the thread has been interrupted. - * @throws IOException Thrown when there is an io error while reading from cache. - */ - void init() throws InterruptedException, IOException; - /** * Downloads the media. * @@ -41,13 +32,6 @@ public interface Downloader { */ void download() throws InterruptedException, IOException; - /** - * Removes all of the downloaded data of the media. - * - * @throws InterruptedException Thrown if the thread was interrupted. - */ - void remove() throws InterruptedException; - /** Returns the total number of downloaded bytes. */ long getDownloadedBytes(); @@ -56,4 +40,11 @@ public interface Downloader { * available. */ float getDownloadPercentage(); + + /** + * Removes the media. + * + * @throws InterruptedException Thrown if the thread was interrupted. + */ + void remove() throws InterruptedException; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java index ea38357af5..c24d6b1ac3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java @@ -53,28 +53,24 @@ public final class ProgressiveDownloader implements Downloader { cachingCounters = new CachingCounters(); } - @Override - public void init() { - CacheUtil.getCached(dataSpec, cache, cachingCounters); - } - @Override public void download() throws InterruptedException, IOException { priorityTaskManager.add(C.PRIORITY_DOWNLOAD); try { - byte[] buffer = new byte[BUFFER_SIZE_BYTES]; - CacheUtil.cache(dataSpec, cache, dataSource, buffer, priorityTaskManager, C.PRIORITY_DOWNLOAD, - cachingCounters, true); + CacheUtil.cache( + dataSpec, + cache, + dataSource, + new byte[BUFFER_SIZE_BYTES], + priorityTaskManager, + C.PRIORITY_DOWNLOAD, + cachingCounters, + /* enableEOFException= */ true); } finally { priorityTaskManager.remove(C.PRIORITY_DOWNLOAD); } } - @Override - public void remove() { - CacheUtil.remove(cache, CacheUtil.getKey(dataSpec)); - } - @Override public long getDownloadedBytes() { return cachingCounters.totalCachedBytes(); @@ -88,4 +84,8 @@ public final class ProgressiveDownloader implements Downloader { : ((cachingCounters.totalCachedBytes() * 100f) / contentLength); } + @Override + public void remove() { + CacheUtil.remove(cache, CacheUtil.getKey(dataSpec)); + } } 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 2ef9f73e6e..915da3f073 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 @@ -17,7 +17,7 @@ package com.google.android.exoplayer2.offline; import android.net.Uri; import android.support.annotation.NonNull; -import android.util.Pair; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -35,7 +35,7 @@ import java.util.List; * Base class for multi segment stream downloaders. * * @param The type of the manifest object. - * @param The type of the representation key object. + * @param The type of the track key object. */ public abstract class SegmentDownloader, K> implements Downloader { @@ -70,7 +70,6 @@ public abstract class SegmentDownloader, K> private final CacheDataSource offlineDataSource; private final ArrayList keys; - private M manifest; private volatile int totalSegments; private volatile int downloadedSegments; private volatile long downloadedBytes; @@ -78,84 +77,57 @@ public abstract class SegmentDownloader, K> /** * @param manifestUri The {@link Uri} of the manifest to be downloaded. * @param constructorHelper a {@link DownloaderConstructorHelper} instance. + * @param trackKeys Track keys to select when downloading. If null or empty, all tracks are + * downloaded. */ - public SegmentDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + public SegmentDownloader( + Uri manifestUri, DownloaderConstructorHelper constructorHelper, @Nullable K[] trackKeys) { this.manifestUri = manifestUri; this.cache = constructorHelper.getCache(); this.dataSource = constructorHelper.buildCacheDataSource(false); this.offlineDataSource = constructorHelper.buildCacheDataSource(true); this.priorityTaskManager = constructorHelper.getPriorityTaskManager(); keys = new ArrayList<>(); - resetCounters(); + if (trackKeys != null) { + Collections.addAll(this.keys, trackKeys); + } + totalSegments = C.LENGTH_UNSET; } /** - * Returns the manifest. Downloads and parses it if necessary. - * - * @return The manifest. - * @throws IOException If an error occurs reading data. - */ - public final M getManifest() throws IOException { - return getManifestIfNeeded(false); - } - - /** Returns keys for all representations. */ - public abstract K[] getAllRepresentationKeys() throws IOException; - - /** - * Selects multiple representations pointed to by the keys for downloading, checking status. Any - * previous selection is cleared. If keys array is null or empty then all representations are - * downloaded. - */ - public final void selectRepresentations(K[] keys) { - this.keys.clear(); - Collections.addAll(this.keys, keys); - resetCounters(); - } - - /** - * Initializes the downloader for the selected representations. + * Downloads the selected tracks in the media. If multiple tracks are selected, they are + * downloaded in sync with one another. * * @throws IOException Thrown when there is an error downloading. * @throws InterruptedException If the thread has been interrupted. */ + // downloadedSegments and downloadedBytes are only written from this method, and this method + // should not be called from more than one thread. Hence non-atomic updates are valid. + @SuppressWarnings("NonAtomicVolatileUpdate") @Override - public final void init() throws IOException, InterruptedException { - try { - getManifestIfNeeded(true); - } catch (IOException e) { - // Either the manifest file isn't available offline or not parsable. - return; - } - try { - initStatus(true); - } catch (IOException e) { - resetCounters(); - throw e; - } - } - - /** - * Downloads the content for the selected representations in sync or resumes a previously stopped - * download. - * - * @throws IOException Thrown when there is an error downloading. - * @throws InterruptedException If the thread has been interrupted. - */ - @Override - public final synchronized void download() throws IOException, InterruptedException { + public final void download() throws IOException, InterruptedException { priorityTaskManager.add(C.PRIORITY_DOWNLOAD); + try { - getManifestIfNeeded(false); - List segments = initStatus(false); + List segments = initDownload(); Collections.sort(segments); byte[] buffer = new byte[BUFFER_SIZE_BYTES]; CachingCounters cachingCounters = new CachingCounters(); for (int i = 0; i < segments.size(); i++) { - CacheUtil.cache(segments.get(i).dataSpec, cache, dataSource, buffer, - priorityTaskManager, C.PRIORITY_DOWNLOAD, cachingCounters, true); - downloadedBytes += cachingCounters.newlyCachedBytes; - downloadedSegments++; + try { + CacheUtil.cache( + segments.get(i).dataSpec, + cache, + dataSource, + buffer, + priorityTaskManager, + C.PRIORITY_DOWNLOAD, + cachingCounters, + true); + downloadedSegments++; + } finally { + downloadedBytes += cachingCounters.newlyCachedBytes; + } } } finally { priorityTaskManager.remove(C.PRIORITY_DOWNLOAD); @@ -168,7 +140,7 @@ public abstract class SegmentDownloader, K> } @Override - public float getDownloadPercentage() { + public final float getDownloadPercentage() { // Take local snapshot of the volatile fields int totalSegments = this.totalSegments; int downloadedSegments = this.downloadedSegments; @@ -181,29 +153,21 @@ public abstract class SegmentDownloader, K> @Override public final void remove() throws InterruptedException { try { - getManifestIfNeeded(true); + M manifest = getManifest(offlineDataSource, manifestUri); + List segments = getSegments(offlineDataSource, manifest, true); + for (int i = 0; i < segments.size(); i++) { + removeUri(segments.get(i).dataSpec.uri); + } } catch (IOException e) { - // Either the manifest file isn't available offline, or it's not parsable. Continue anyway to - // reset the counters and attempt to remove the manifest file. + // Ignore exceptions when removing. + } finally { + // Always attempt to remove the manifest. + removeUri(manifestUri); } - resetCounters(); - if (manifest != null) { - List segments = null; - try { - segments = getSegments(offlineDataSource, manifest, true).first; - } catch (IOException e) { - // Ignore exceptions. We do our best with what's available offline. - } - if (segments != null) { - for (int i = 0; i < segments.size(); i++) { - remove(segments.get(i).dataSpec.uri); - } - } - manifest = null; - } - remove(manifestUri); } + // Internal methods. + /** * Loads and parses the manifest. * @@ -219,44 +183,27 @@ public abstract class SegmentDownloader, K> * * @param dataSource The {@link DataSource} through which to load any required data. * @param manifest The manifest containing the segments. - * @param allowIncompleteIndex Whether to continue in the case that a load error prevents all + * @param allowIncompleteList 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. * @throws InterruptedException Thrown if the thread was interrupted. * @throws IOException Thrown if {@code allowPartialIndex} is false and a load error occurs, or if * 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, and a boolean indicating whether the list is - * complete. + * @return The list of downloadable {@link Segment}s. */ - protected abstract Pair, Boolean> getSegments( - DataSource dataSource, M manifest, boolean allowIncompleteIndex) + protected abstract List getSegments( + DataSource dataSource, M manifest, boolean allowIncompleteList) throws InterruptedException, IOException; - private void resetCounters() { - totalSegments = C.LENGTH_UNSET; - downloadedSegments = 0; - downloadedBytes = 0; - } - - private void remove(Uri uri) { - CacheUtil.remove(cache, CacheUtil.generateKey(uri)); - } - - /** - * Initializes totalSegments, downloadedSegments and downloadedBytes for selected representations. - * If not offline then downloads missing metadata. - * - * @return A list of not fully downloaded segments. - */ - private synchronized List initStatus(boolean offline) - throws IOException, InterruptedException { - DataSource dataSource = getDataSource(offline); - M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys); - Pair, Boolean> result = getSegments(dataSource, filteredManifest, offline); - List segments = result.first; - boolean isSegmentListComplete = result.second; + /** Initializes the download, returning a list of {@link Segment}s that need to be downloaded. */ + private List initDownload() throws IOException, InterruptedException { + M manifest = getManifest(dataSource, manifestUri); + if (!keys.isEmpty()) { + manifest = manifest.copy(keys); + } + List segments = getSegments(dataSource, manifest, /* allowIncompleteList= */ false); CachingCounters cachingCounters = new CachingCounters(); - totalSegments = isSegmentListComplete ? segments.size() : C.LENGTH_UNSET; + totalSegments = segments.size(); downloadedSegments = 0; downloadedBytes = 0; for (int i = segments.size() - 1; i >= 0; i--) { @@ -272,15 +219,8 @@ public abstract class SegmentDownloader, K> return segments; } - private M getManifestIfNeeded(boolean offline) throws IOException { - if (manifest == null) { - manifest = getManifest(getDataSource(offline), manifestUri); - } - return manifest; - } - - private DataSource getDataSource(boolean offline) { - return offline ? offlineDataSource : dataSource; + private void removeUri(Uri uri) { + CacheUtil.remove(cache, CacheUtil.generateKey(uri)); } } 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 a9927f6e86..7ef79b8963 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 @@ -48,6 +48,22 @@ public final class ParsingLoadable implements Loadable { } + /** + * Loads a single parsable object. + * + * @param dataSource The {@link DataSource} through which the object should be read. + * @param uri The {@link Uri} of the object to read. + * @return The parsed object + * @throws IOException Thrown if there is an error while loading or parsing. + */ + public static T load(DataSource dataSource, Parser parser, Uri uri) + throws IOException { + ParsingLoadable loadable = + new ParsingLoadable<>(dataSource, uri, C.DATA_TYPE_UNKNOWN, parser); + loadable.load(); + return loadable.getResult(); + } + /** * The {@link DataSpec} that defines the data to be loaded. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index 2e15f81ad5..6f0af064a0 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -662,11 +662,6 @@ public class DownloadManagerTest { this.blocker = new com.google.android.exoplayer2.util.ConditionVariable(); } - @Override - public void init() throws InterruptedException, IOException { - // do nothing. - } - @Override public void download() throws InterruptedException, IOException { assertThat(isRemoveAction).isFalse(); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 2227044da7..54e356f6db 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -53,10 +53,7 @@ public final class DashUtil { */ public static DashManifest loadManifest(DataSource dataSource, Uri uri) throws IOException { - ParsingLoadable loadable = - new ParsingLoadable<>(dataSource, uri, C.DATA_TYPE_MANIFEST, new DashManifestParser()); - loadable.load(); - return loadable.getResult(); + return ParsingLoadable.load(dataSource, new DashManifestParser(), uri); } /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java index bd4c3f25fb..0c5e5a01af 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadAction.java @@ -60,9 +60,7 @@ public final class DashDownloadAction extends SegmentDownloadActionExample usage: * @@ -47,9 +47,11 @@ import java.util.List; * DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null); * DownloaderConstructorHelper constructorHelper = * new DownloaderConstructorHelper(cache, factory); - * DashDownloader dashDownloader = new DashDownloader(manifestUrl, constructorHelper); - * // Select the first representation of the first adaptation set of the first period - * dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); + * // Create a downloader for the first representation of the first adaptation set of the first + * // period. + * DashDownloader dashDownloader = new DashDownloader( + * manifestUrl, constructorHelper, new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); + * // Perform the download. * dashDownloader.download(); * // Access downloaded data using CacheDataSource * CacheDataSource cacheDataSource = @@ -58,27 +60,12 @@ import java.util.List; */ public final class DashDownloader extends SegmentDownloader { - /** - * @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) - */ - public DashDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); - } - - @Override - public RepresentationKey[] getAllRepresentationKeys() throws IOException { - ArrayList keys = new ArrayList<>(); - DashManifest manifest = getManifest(); - for (int periodIndex = 0; periodIndex < manifest.getPeriodCount(); periodIndex++) { - List adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; - for (int adaptationIndex = 0; adaptationIndex < adaptationSets.size(); adaptationIndex++) { - int representationsCount = adaptationSets.get(adaptationIndex).representations.size(); - for (int i = 0; i < representationsCount; i++) { - keys.add(new RepresentationKey(periodIndex, adaptationIndex, i)); - } - } - } - return keys.toArray(new RepresentationKey[keys.size()]); + /** @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper, Object[]) */ + public DashDownloader( + Uri manifestUri, + DownloaderConstructorHelper constructorHelper, + @Nullable RepresentationKey[] trackKeys) { + super(manifestUri, constructorHelper, trackKeys); } @Override @@ -87,40 +74,36 @@ public final class DashDownloader extends SegmentDownloader, Boolean> getSegments( - DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors) + protected List getSegments( + DataSource dataSource, DashManifest manifest, boolean allowIncompleteList) throws InterruptedException, IOException { ArrayList segments = new ArrayList<>(); - boolean segmentListComplete = true; 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++) { - if (!addSegmentsForAdaptationSet( + addSegmentsForAdaptationSet( dataSource, adaptationSets.get(j), periodStartUs, periodDurationUs, - allowIndexLoadErrors, - segments)) { - segmentListComplete = false; - } + allowIncompleteList, + segments); } } - return Pair., Boolean>create(segments, segmentListComplete); + return segments; } - private static boolean addSegmentsForAdaptationSet( + private static void addSegmentsForAdaptationSet( DataSource dataSource, AdaptationSet adaptationSet, long periodStartUs, long periodDurationUs, - boolean allowIndexLoadErrors, + boolean allowIncompleteList, ArrayList out) throws IOException, InterruptedException { - boolean segmentListComplete = true; for (int i = 0; i < adaptationSet.representations.size(); i++) { Representation representation = adaptationSet.representations.get(i); DashSegmentIndex index; @@ -131,11 +114,11 @@ public final class DashDownloader extends SegmentDownloader @Override protected HlsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) { - HlsDownloader downloader = new HlsDownloader(manifestUri, constructorHelper); - downloader.selectRepresentations(keys); - return downloader; + return new HlsDownloader(manifestUri, constructorHelper, keys); } @Override 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 45bbe11a22..a46ca91997 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 @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.hls.offline; import android.net.Uri; -import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.SegmentDownloader; @@ -35,29 +34,13 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -/** - * Helper class to download HLS streams. - * - *

A subset of renditions can be downloaded by selecting them using {@link - * #selectRepresentations(Object[])}. - */ +/** A downloader for HLS streams. */ public final class HlsDownloader extends SegmentDownloader { - /** - * @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) - */ - public HlsDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); - } - - @Override - public RenditionKey[] getAllRepresentationKeys() throws IOException { - ArrayList renditionKeys = new ArrayList<>(); - HlsMasterPlaylist manifest = getManifest(); - extractUrls(manifest.variants, renditionKeys); - extractUrls(manifest.audios, renditionKeys); - extractUrls(manifest.subtitles, renditionKeys); - return renditionKeys.toArray(new RenditionKey[renditionKeys.size()]); + /** @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper, Object[]) */ + public HlsDownloader( + Uri manifestUri, DownloaderConstructorHelper constructorHelper, RenditionKey[] trackKeys) { + super(manifestUri, constructorHelper, trackKeys); } @Override @@ -71,8 +54,8 @@ public final class HlsDownloader extends SegmentDownloader, Boolean> getSegments( - DataSource dataSource, HlsMasterPlaylist manifest, boolean allowIndexLoadErrors) + protected List getSegments( + DataSource dataSource, HlsMasterPlaylist manifest, boolean allowIncompleteList) throws IOException { HashSet encryptionKeyUris = new HashSet<>(); ArrayList renditionUrls = new ArrayList<>(); @@ -81,17 +64,15 @@ public final class HlsDownloader extends SegmentDownloader segments = new ArrayList<>(); - boolean segmentListComplete = true; for (HlsUrl renditionUrl : renditionUrls) { HlsMediaPlaylist mediaPlaylist = null; Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url); try { mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri); } catch (IOException e) { - if (!allowIndexLoadErrors) { + if (!allowIncompleteList) { throw e; } - segmentListComplete = false; } segments.add(new Segment(mediaPlaylist != null ? mediaPlaylist.startTimeUs : Long.MIN_VALUE, new DataSpec(uri))); @@ -111,7 +92,7 @@ public final class HlsDownloader extends SegmentDownloader, Boolean>create(segments, segmentListComplete); + return segments; } private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException { @@ -139,10 +120,4 @@ public final class HlsDownloader extends SegmentDownloader hlsUrls, ArrayList renditionKeys) { - for (int i = 0; i < hlsUrls.size(); i++) { - renditionKeys.add(new RenditionKey(hlsUrls.get(i).url)); - } - } - } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java index 29eb7c2516..f9fe859eda 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java @@ -33,8 +33,8 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa import static com.google.common.truth.Truth.assertThat; import android.net.Uri; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; -import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.RenditionKey; import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSource.Factory; @@ -56,7 +56,6 @@ public class HlsDownloaderTest { private SimpleCache cache; private File tempFolder; private FakeDataSet fakeDataSet; - private HlsDownloader hlsDownloader; @Before public void setUp() throws Exception { @@ -74,7 +73,6 @@ public class HlsDownloaderTest { .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13) .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14) .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15); - hlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI); } @After @@ -82,56 +80,19 @@ public class HlsDownloaderTest { Util.recursiveDelete(tempFolder); } - @Test - public void testDownloadManifest() throws Exception { - HlsMasterPlaylist manifest = hlsDownloader.getManifest(); - - assertThat(manifest).isNotNull(); - assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI); - } - - @Test - public void testSelectRepresentationsClearsPreviousSelection() throws Exception { - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_2_URI)); - hlsDownloader.download(); - - assertCachedData( - cache, - fakeDataSet, - MASTER_PLAYLIST_URI, - MEDIA_PLAYLIST_2_URI, - MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", - MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", - MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts"); - } - @Test public void testCounterMethods() throws Exception { - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - hlsDownloader.download(); + HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI)); + downloader.download(); - assertThat(hlsDownloader.getDownloadedBytes()) - .isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12); - } - - @Test - public void testInitStatus() throws Exception { - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - hlsDownloader.download(); - - HlsDownloader newHlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI); - newHlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - newHlsDownloader.init(); - - assertThat(newHlsDownloader.getDownloadedBytes()) + assertThat(downloader.getDownloadedBytes()) .isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12); } @Test public void testDownloadRepresentation() throws Exception { - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - hlsDownloader.download(); + HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI)); + downloader.download(); assertCachedData( cache, @@ -145,8 +106,9 @@ public class HlsDownloaderTest { @Test public void testDownloadMultipleRepresentations() throws Exception { - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI)); - hlsDownloader.download(); + HlsDownloader downloader = + getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI)); + downloader.download(); assertCachedData(cache, fakeDataSet); } @@ -163,36 +125,28 @@ public class HlsDownloaderTest { .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence0.ts", 13) .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence1.ts", 14) .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15); - hlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI); - // hlsDownloader.selectRepresentations() isn't called - hlsDownloader.download(); - assertCachedData(cache, fakeDataSet); - hlsDownloader.remove(); + HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, null); + downloader.download(); - // select something random - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - // clear selection - hlsDownloader.selectRepresentations(getKeys()); - hlsDownloader.download(); assertCachedData(cache, fakeDataSet); - hlsDownloader.remove(); } @Test - public void testRemoveAll() throws Exception { - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI)); - hlsDownloader.download(); - hlsDownloader.remove(); + public void testRemove() throws Exception { + HlsDownloader downloader = + getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI)); + downloader.download(); + downloader.remove(); assertCacheEmpty(cache); } @Test public void testDownloadMediaPlaylist() throws Exception { - hlsDownloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI); - hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); - hlsDownloader.download(); + HlsDownloader downloader = + getHlsDownloader(MEDIA_PLAYLIST_1_URI, getKeys(MEDIA_PLAYLIST_1_URI)); + downloader.download(); assertCachedData( cache, @@ -213,17 +167,17 @@ public class HlsDownloaderTest { .setRandomData("fileSequence0.ts", 10) .setRandomData("fileSequence1.ts", 11) .setRandomData("fileSequence2.ts", 12); - hlsDownloader = getHlsDownloader(ENC_MEDIA_PLAYLIST_URI); - hlsDownloader.selectRepresentations(getKeys(ENC_MEDIA_PLAYLIST_URI)); - hlsDownloader.download(); + HlsDownloader downloader = + getHlsDownloader(ENC_MEDIA_PLAYLIST_URI, getKeys(ENC_MEDIA_PLAYLIST_URI)); + downloader.download(); assertCachedData(cache, fakeDataSet); } - private HlsDownloader getHlsDownloader(String mediaPlaylistUri) { + private HlsDownloader getHlsDownloader(String mediaPlaylistUri, @Nullable RenditionKey[] keys) { Factory factory = new Factory(null).setFakeDataSet(fakeDataSet); return new HlsDownloader( - Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory)); + Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory), keys); } private static RenditionKey[] getKeys(String... urls) { diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java index 299f8c115f..9dc3f69767 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadAction.java @@ -60,9 +60,7 @@ public final class SsDownloadAction extends SegmentDownloadAction { @Override protected SsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) { - SsDownloader downloader = new SsDownloader(manifestUri, constructorHelper); - downloader.selectRepresentations(keys); - return downloader; + return new SsDownloader(manifestUri, constructorHelper, keys); } @Override 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 9a6f52f103..c23c50e296 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 @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline; import android.net.Uri; -import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.SegmentDownloader; @@ -33,7 +32,7 @@ import java.util.ArrayList; import java.util.List; /** - * Helper class to download SmoothStreaming streams. + * A downloader for SmoothStreaming streams. * *

Example usage: * @@ -42,9 +41,10 @@ import java.util.List; * DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null); * DownloaderConstructorHelper constructorHelper = * new DownloaderConstructorHelper(cache, factory); - * SsDownloader ssDownloader = new SsDownloader(manifestUrl, constructorHelper); - * // Select the first track of the first stream element - * ssDownloader.selectRepresentations(new TrackKey[] {new TrackKey(0, 0)}); + * // Create a downloader for the first track of the first stream element. + * SsDownloader ssDownloader = new SsDownloader( + * manifestUrl, constructorHelper, new TrackKey[] {new TrackKey(0, 0)}); + * // Perform the download. * ssDownloader.download(); * // Access downloaded data using CacheDataSource * CacheDataSource cacheDataSource = @@ -53,24 +53,10 @@ import java.util.List; */ public final class SsDownloader extends SegmentDownloader { - /** - * @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) - */ - public SsDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(SsUtil.fixManifestUri(manifestUri), constructorHelper); - } - - @Override - public TrackKey[] getAllRepresentationKeys() throws IOException { - ArrayList keys = new ArrayList<>(); - SsManifest manifest = getManifest(); - for (int i = 0; i < manifest.streamElements.length; i++) { - StreamElement streamElement = manifest.streamElements[i]; - for (int j = 0; j < streamElement.formats.length; j++) { - keys.add(new TrackKey(i, j)); - } - } - return keys.toArray(new TrackKey[keys.size()]); + /** @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper, Object[]) */ + public SsDownloader( + Uri manifestUri, DownloaderConstructorHelper constructorHelper, TrackKey[] trackKeys) { + super(SsUtil.fixManifestUri(manifestUri), constructorHelper, trackKeys); } @Override @@ -82,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader } @Override - protected Pair, Boolean> getSegments( - DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors) throws IOException { + protected List getSegments( + DataSource dataSource, SsManifest manifest, boolean allowIncompleteList) throws IOException { ArrayList segments = new ArrayList<>(); for (StreamElement streamElement : manifest.streamElements) { for (int i = 0; i < streamElement.formats.length; i++) { @@ -95,7 +81,7 @@ public final class SsDownloader extends SegmentDownloader } } } - return Pair., Boolean>create(segments, true); + return segments; } } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java index b85991f34f..5bf962a3b4 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java @@ -15,12 +15,12 @@ */ package com.google.android.exoplayer2.playbacktests.gts; -import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.net.Uri; import android.test.ActivityInstrumentationTestCase2; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; +import com.google.android.exoplayer2.source.dash.DashUtil; 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.Representation; @@ -35,8 +35,6 @@ import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; import java.io.File; -import java.io.IOException; -import java.io.InterruptedIOException; import java.util.ArrayList; import java.util.List; @@ -47,9 +45,13 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2 keys = new ArrayList<>(); - for (int pIndex = 0; pIndex < dashManifest.getPeriodCount(); pIndex++) { - List adaptationSets = dashManifest.getPeriod(pIndex).adaptationSets; - for (int aIndex = 0; aIndex < adaptationSets.size(); aIndex++) { - AdaptationSet adaptationSet = adaptationSets.get(aIndex); - List representations = adaptationSet.representations; - for (int rIndex = 0; rIndex < representations.size(); rIndex++) { - String id = representations.get(rIndex).format.id; - if (DashTestData.AAC_AUDIO_REPRESENTATION_ID.equals(id) - || DashTestData.H264_CDD_FIXED.equals(id)) { - keys.add(new RepresentationKey(pIndex, aIndex, rIndex)); - } + DashManifest dashManifest = + DashUtil.loadManifest(httpDataSourceFactory.createDataSource(), MANIFEST_URI); + ArrayList keys = new ArrayList<>(); + for (int pIndex = 0; pIndex < dashManifest.getPeriodCount(); pIndex++) { + List adaptationSets = dashManifest.getPeriod(pIndex).adaptationSets; + for (int aIndex = 0; aIndex < adaptationSets.size(); aIndex++) { + AdaptationSet adaptationSet = adaptationSets.get(aIndex); + List representations = adaptationSet.representations; + for (int rIndex = 0; rIndex < representations.size(); rIndex++) { + String id = representations.get(rIndex).format.id; + if (DashTestData.AAC_AUDIO_REPRESENTATION_ID.equals(id) + || DashTestData.H264_CDD_FIXED.equals(id)) { + keys.add(new RepresentationKey(pIndex, aIndex, rIndex)); } } - dashDownloader.selectRepresentations(keys.toArray(new RepresentationKey[keys.size()])); - dashDownloader.download(); } - } catch (InterruptedException e) { - // do nothing - } catch (IOException e) { - Throwable exception = e; - while (!(exception instanceof InterruptedIOException)) { - if (exception == null) { - throw e; - } - exception = exception.getCause(); - } - // else do nothing } - return dashDownloader; - } - - private DashDownloader createDashDownloader() { DownloaderConstructorHelper constructorHelper = - new DownloaderConstructorHelper(cache, new DefaultHttpDataSourceFactory("ExoPlayer", null)); - return new DashDownloader(Uri.parse(DashTestData.H264_MANIFEST), constructorHelper); - } - - private CacheDataSourceFactory newOfflineCacheDataSourceFactory() { - return new CacheDataSourceFactory(cache, DummyDataSource.FACTORY, - CacheDataSource.FLAG_BLOCK_ON_CACHE); + new DownloaderConstructorHelper(cache, httpDataSourceFactory); + return new DashDownloader( + MANIFEST_URI, constructorHelper, keys.toArray(new RepresentationKey[keys.size()])); } }