Commit 175a0100 by olly Committed by Oliver Woodman

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
parent d4f75963
Showing with 319 additions and 618 deletions
...@@ -29,28 +29,34 @@ import android.widget.ArrayAdapter; ...@@ -29,28 +29,34 @@ import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.exoplayer2.C; 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.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadService; 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.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.Representation;
import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; 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.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.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.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.manifest.TrackKey;
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction; 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.DefaultTrackNameProvider;
import com.google.android.exoplayer2.ui.TrackNameProvider; 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.ParcelableArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** An activity for downloading media. */ /** An activity for downloading media. */
...@@ -87,23 +93,22 @@ public class DownloadActivity extends Activity { ...@@ -87,23 +93,22 @@ public class DownloadActivity extends Activity {
representationList.setAdapter(arrayAdapter); representationList.setAdapter(arrayAdapter);
DemoApplication application = (DemoApplication) getApplication(); DemoApplication application = (DemoApplication) getApplication();
DownloaderConstructorHelper constructorHelper = DataSource.Factory manifestDataSourceFactory =
new DownloaderConstructorHelper( application.buildDataSourceFactory(/* listener= */ null);
application.getDownloadCache(), application.buildHttpDataSourceFactory(null));
String extension = playerIntent.getStringExtra(EXTENSION_EXTRA); String extension = playerIntent.getStringExtra(EXTENSION_EXTRA);
int type = Util.inferContentType(sampleUri, extension); int type = Util.inferContentType(sampleUri, extension);
switch (type) { switch (type) {
case C.TYPE_DASH: case C.TYPE_DASH:
downloadUtilMethods = new DashDownloadUtilMethods(sampleUri, constructorHelper); downloadUtilMethods = new DashDownloadUtilMethods(sampleUri, manifestDataSourceFactory);
break; break;
case C.TYPE_SS: case C.TYPE_SS:
downloadUtilMethods = new SsDownloadUtilMethods(sampleUri, constructorHelper); downloadUtilMethods = new SsDownloadUtilMethods(sampleUri, manifestDataSourceFactory);
break; break;
case C.TYPE_HLS: case C.TYPE_HLS:
downloadUtilMethods = new HlsDownloadUtilMethods(sampleUri, constructorHelper); downloadUtilMethods = new HlsDownloadUtilMethods(sampleUri, manifestDataSourceFactory);
break; break;
case C.TYPE_OTHER: case C.TYPE_OTHER:
downloadUtilMethods = new ProgressiveDownloadUtilMethods(sampleUri, constructorHelper); downloadUtilMethods = new ProgressiveDownloadUtilMethods(sampleUri);
break; break;
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
...@@ -148,13 +153,17 @@ public class DownloadActivity extends Activity { ...@@ -148,13 +153,17 @@ public class DownloadActivity extends Activity {
DownloadService.addDownloadAction( DownloadService.addDownloadAction(
this, this,
DemoDownloadService.class, DemoDownloadService.class,
downloadUtilMethods.getDownloadAction(sampleName, representationKeys)); downloadUtilMethods.getDownloadAction(
/* isRemoveAction= */ false, sampleName, representationKeys));
} }
} }
private void removeDownload() { private void removeDownload() {
DownloadService.addDownloadAction( 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++) { for (int i = 0; i < representationList.getChildCount(); i++) {
representationList.setItemChecked(i, false); representationList.setItemChecked(i, false);
} }
...@@ -199,18 +208,15 @@ public class DownloadActivity extends Activity { ...@@ -199,18 +208,15 @@ public class DownloadActivity extends Activity {
public final Parcelable key; public final Parcelable key;
public final String title; 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.key = key;
this.title = title; this.title = title;
this.percentDownloaded =
(int) (percentDownloaded == C.PERCENTAGE_UNSET ? 0 : percentDownloaded);
} }
@Override @Override
public String toString() { public String toString() {
return title + " (" + percentDownloaded + "%)"; return title;
} }
} }
...@@ -220,7 +226,7 @@ public class DownloadActivity extends Activity { ...@@ -220,7 +226,7 @@ public class DownloadActivity extends Activity {
@Override @Override
protected List<RepresentationItem> doInBackground(Void... ignore) { protected List<RepresentationItem> doInBackground(Void... ignore) {
try { try {
return downloadUtilMethods.getRepresentationItems(trackNameProvider); return downloadUtilMethods.loadRepresentationItems(trackNameProvider);
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
return null; return null;
} }
...@@ -244,173 +250,152 @@ public class DownloadActivity extends Activity { ...@@ -244,173 +250,152 @@ public class DownloadActivity extends Activity {
private abstract static class DownloadUtilMethods { private abstract static class DownloadUtilMethods {
protected final Uri manifestUri; protected final Uri manifestUri;
protected final DownloaderConstructorHelper constructorHelper;
public DownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { public DownloadUtilMethods(Uri manifestUri) {
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.constructorHelper = constructorHelper;
} }
public abstract List<RepresentationItem> getRepresentationItems( public abstract List<RepresentationItem> loadRepresentationItems(
TrackNameProvider trackNameProvider) throws IOException, InterruptedException; TrackNameProvider trackNameProvider) throws IOException, InterruptedException;
public abstract DownloadAction getDownloadAction( public abstract DownloadAction getDownloadAction(
String sampleName, ArrayList<Object> representationKeys); boolean isRemoveAction, String sampleName, List<Object> representationKeys);
public abstract DownloadAction getRemoveAction();
} }
private static final class DashDownloadUtilMethods extends DownloadUtilMethods { private static final class DashDownloadUtilMethods extends DownloadUtilMethods {
public DashDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { private final DataSource.Factory manifestDataSourceFactory;
super(manifestUri, constructorHelper);
public DashDownloadUtilMethods(Uri manifestUri, DataSource.Factory manifestDataSourceFactory) {
super(manifestUri);
this.manifestDataSourceFactory = manifestDataSourceFactory;
} }
@Override @Override
public List<RepresentationItem> getRepresentationItems(TrackNameProvider trackNameProvider) public List<RepresentationItem> loadRepresentationItems(TrackNameProvider trackNameProvider)
throws IOException, InterruptedException { throws IOException, InterruptedException {
DashDownloader downloader = new DashDownloader(manifestUri, constructorHelper); DataSource dataSource = manifestDataSourceFactory.createDataSource();
DashManifest manifest =
ParsingLoadable.load(dataSource, new DashManifestParser(), manifestUri);
ArrayList<RepresentationItem> items = new ArrayList<>(); ArrayList<RepresentationItem> items = new ArrayList<>();
for (RepresentationKey key : downloader.getAllRepresentationKeys()) { for (int periodIndex = 0; periodIndex < manifest.getPeriodCount(); periodIndex++) {
downloader.selectRepresentations(new RepresentationKey[] {key}); List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
try { for (int adaptationIndex = 0; adaptationIndex < adaptationSets.size(); adaptationIndex++) {
downloader.init(); List<Representation> representations =
} catch (IOException e) { adaptationSets.get(adaptationIndex).representations;
continue; 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; return items;
} }
@Override @Override
public DownloadAction getDownloadAction( public DownloadAction getDownloadAction(
String sampleName, ArrayList<Object> representationKeys) { boolean isRemoveAction, String sampleName, List<Object> representationKeys) {
RepresentationKey[] keys = RepresentationKey[] keys =
representationKeys.toArray(new RepresentationKey[representationKeys.size()]); 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 { private static final class HlsDownloadUtilMethods extends DownloadUtilMethods {
public HlsDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { private final DataSource.Factory manifestDataSourceFactory;
super(manifestUri, constructorHelper);
public HlsDownloadUtilMethods(Uri manifestUri, DataSource.Factory manifestDataSourceFactory) {
super(manifestUri);
this.manifestDataSourceFactory = manifestDataSourceFactory;
} }
@Override @Override
public List<RepresentationItem> getRepresentationItems(TrackNameProvider trackNameProvider) public List<RepresentationItem> loadRepresentationItems(TrackNameProvider trackNameProvider)
throws IOException, InterruptedException { throws IOException, InterruptedException {
HlsDownloader downloader = new HlsDownloader(manifestUri, constructorHelper); DataSource dataSource = manifestDataSourceFactory.createDataSource();
HlsPlaylist<?> playlist =
ParsingLoadable.load(dataSource, new HlsPlaylistParser(), manifestUri);
ArrayList<RepresentationItem> items = new ArrayList<>(); ArrayList<RepresentationItem> items = new ArrayList<>();
for (RenditionKey key : downloader.getAllRepresentationKeys()) { if (playlist instanceof HlsMediaPlaylist) {
downloader.selectRepresentations(new RenditionKey[] {key}); items.add(new RepresentationItem(null, "Stream"));
try { } else {
downloader.init(); HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
} catch (IOException e) { ArrayList<HlsMasterPlaylist.HlsUrl> hlsUrls = new ArrayList<>();
continue; 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; return items;
} }
@Override @Override
public DownloadAction getDownloadAction( public DownloadAction getDownloadAction(
String sampleName, ArrayList<Object> representationKeys) { boolean isRemoveAction, String sampleName, List<Object> representationKeys) {
RenditionKey[] keys = representationKeys.toArray(new RenditionKey[representationKeys.size()]); RenditionKey[] keys = representationKeys.toArray(new RenditionKey[representationKeys.size()]);
return new HlsDownloadAction(/* isRemoveAction= */ false, sampleName, manifestUri, keys); return new HlsDownloadAction(isRemoveAction, sampleName, manifestUri, keys);
}
@Override
public DownloadAction getRemoveAction() {
return new HlsDownloadAction(/* isRemoveAction= */ true, /* data= */ null, manifestUri);
} }
} }
private static final class SsDownloadUtilMethods extends DownloadUtilMethods { private static final class SsDownloadUtilMethods extends DownloadUtilMethods {
public SsDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { private final DataSource.Factory manifestDataSourceFactory;
super(manifestUri, constructorHelper);
public SsDownloadUtilMethods(Uri manifestUri, DataSource.Factory manifestDataSourceFactory) {
super(manifestUri);
this.manifestDataSourceFactory = manifestDataSourceFactory;
} }
@Override @Override
public List<RepresentationItem> getRepresentationItems(TrackNameProvider trackNameProvider) public List<RepresentationItem> loadRepresentationItems(TrackNameProvider trackNameProvider)
throws IOException, InterruptedException { throws IOException, InterruptedException {
SsDownloader downloader = new SsDownloader(manifestUri, constructorHelper); DataSource dataSource = manifestDataSourceFactory.createDataSource();
SsManifest manifest = ParsingLoadable.load(dataSource, new SsManifestParser(), manifestUri);
ArrayList<RepresentationItem> items = new ArrayList<>(); ArrayList<RepresentationItem> items = new ArrayList<>();
for (TrackKey key : downloader.getAllRepresentationKeys()) { for (int i = 0; i < manifest.streamElements.length; i++) {
downloader.selectRepresentations(new TrackKey[] {key}); SsManifest.StreamElement streamElement = manifest.streamElements[i];
try { for (int j = 0; j < streamElement.formats.length; j++) {
downloader.init(); TrackKey key = new TrackKey(i, j);
} catch (IOException e) { String trackName = trackNameProvider.getTrackName(streamElement.formats[j]);
continue; 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; return items;
} }
@Override @Override
public DownloadAction getDownloadAction( public DownloadAction getDownloadAction(
String sampleName, ArrayList<Object> representationKeys) { boolean isRemoveAction, String sampleName, List<Object> representationKeys) {
TrackKey[] keys = representationKeys.toArray(new TrackKey[representationKeys.size()]); TrackKey[] keys = representationKeys.toArray(new TrackKey[representationKeys.size()]);
return new SsDownloadAction(/* isRemoveAction= */ false, sampleName, manifestUri, keys); return new SsDownloadAction(isRemoveAction, sampleName, manifestUri, keys);
}
@Override
public DownloadAction getRemoveAction() {
return new SsDownloadAction(/* isRemoveAction= */ true, /* data= */ null, manifestUri);
} }
} }
private static final class ProgressiveDownloadUtilMethods extends DownloadUtilMethods { private static final class ProgressiveDownloadUtilMethods extends DownloadUtilMethods {
public ProgressiveDownloadUtilMethods( public ProgressiveDownloadUtilMethods(Uri manifestUri) {
Uri manifestUri, DownloaderConstructorHelper constructorHelper) { super(manifestUri);
super(manifestUri, constructorHelper);
} }
@Override @Override
public List<RepresentationItem> getRepresentationItems(TrackNameProvider trackNameProvider) { public List<RepresentationItem> loadRepresentationItems(TrackNameProvider trackNameProvider) {
ProgressiveDownloader downloader = return Collections.singletonList(new RepresentationItem(null, "Stream"));
new ProgressiveDownloader(manifestUri, null, constructorHelper);
ArrayList<RepresentationItem> items = new ArrayList<>();
{
downloader.init();
items.add(new RepresentationItem(null, "Stream", downloader.getDownloadPercentage()));
}
return items;
} }
@Override @Override
public DownloadAction getDownloadAction( public DownloadAction getDownloadAction(
String sampleName, ArrayList<Object> representationKeys) { boolean isRemoveAction, String sampleName, List<Object> representationKeys) {
return new ProgressiveDownloadAction(
/* isRemoveAction= */ false, /* data= */ null, manifestUri, /* customCacheKey= */ null);
}
@Override
public DownloadAction getRemoveAction() {
return new ProgressiveDownloadAction( return new ProgressiveDownloadAction(
/* isRemoveAction= */ true, /* data= */ null, manifestUri, /* customCacheKey= */ null); isRemoveAction, sampleName, manifestUri, /* customCacheKey= */ null);
} }
} }
} }
...@@ -24,15 +24,6 @@ import java.io.IOException; ...@@ -24,15 +24,6 @@ import java.io.IOException;
public interface Downloader { 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. * Downloads the media.
* *
* @throws DownloadException Thrown if the media cannot be downloaded. * @throws DownloadException Thrown if the media cannot be downloaded.
...@@ -41,13 +32,6 @@ public interface Downloader { ...@@ -41,13 +32,6 @@ public interface Downloader {
*/ */
void download() throws InterruptedException, IOException; 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. */ /** Returns the total number of downloaded bytes. */
long getDownloadedBytes(); long getDownloadedBytes();
...@@ -56,4 +40,11 @@ public interface Downloader { ...@@ -56,4 +40,11 @@ public interface Downloader {
* available. * available.
*/ */
float getDownloadPercentage(); float getDownloadPercentage();
/**
* Removes the media.
*
* @throws InterruptedException Thrown if the thread was interrupted.
*/
void remove() throws InterruptedException;
} }
...@@ -54,28 +54,24 @@ public final class ProgressiveDownloader implements Downloader { ...@@ -54,28 +54,24 @@ public final class ProgressiveDownloader implements Downloader {
} }
@Override @Override
public void init() {
CacheUtil.getCached(dataSpec, cache, cachingCounters);
}
@Override
public void download() throws InterruptedException, IOException { public void download() throws InterruptedException, IOException {
priorityTaskManager.add(C.PRIORITY_DOWNLOAD); priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
try { try {
byte[] buffer = new byte[BUFFER_SIZE_BYTES]; CacheUtil.cache(
CacheUtil.cache(dataSpec, cache, dataSource, buffer, priorityTaskManager, C.PRIORITY_DOWNLOAD, dataSpec,
cachingCounters, true); cache,
dataSource,
new byte[BUFFER_SIZE_BYTES],
priorityTaskManager,
C.PRIORITY_DOWNLOAD,
cachingCounters,
/* enableEOFException= */ true);
} finally { } finally {
priorityTaskManager.remove(C.PRIORITY_DOWNLOAD); priorityTaskManager.remove(C.PRIORITY_DOWNLOAD);
} }
} }
@Override @Override
public void remove() {
CacheUtil.remove(cache, CacheUtil.getKey(dataSpec));
}
@Override
public long getDownloadedBytes() { public long getDownloadedBytes() {
return cachingCounters.totalCachedBytes(); return cachingCounters.totalCachedBytes();
} }
...@@ -88,4 +84,8 @@ public final class ProgressiveDownloader implements Downloader { ...@@ -88,4 +84,8 @@ public final class ProgressiveDownloader implements Downloader {
: ((cachingCounters.totalCachedBytes() * 100f) / contentLength); : ((cachingCounters.totalCachedBytes() * 100f) / contentLength);
} }
@Override
public void remove() {
CacheUtil.remove(cache, CacheUtil.getKey(dataSpec));
}
} }
...@@ -17,7 +17,7 @@ package com.google.android.exoplayer2.offline; ...@@ -17,7 +17,7 @@ package com.google.android.exoplayer2.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; 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.C;
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;
...@@ -35,7 +35,7 @@ import java.util.List; ...@@ -35,7 +35,7 @@ import java.util.List;
* Base class for multi segment stream downloaders. * Base class for multi segment stream downloaders.
* *
* @param <M> The type of the manifest object. * @param <M> The type of the manifest object.
* @param <K> The type of the representation key object. * @param <K> The type of the track key object.
*/ */
public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
implements Downloader { implements Downloader {
...@@ -70,7 +70,6 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> ...@@ -70,7 +70,6 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
private final CacheDataSource offlineDataSource; private final CacheDataSource offlineDataSource;
private final ArrayList<K> keys; private final ArrayList<K> keys;
private M manifest;
private volatile int totalSegments; private volatile int totalSegments;
private volatile int downloadedSegments; private volatile int downloadedSegments;
private volatile long downloadedBytes; private volatile long downloadedBytes;
...@@ -78,84 +77,57 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> ...@@ -78,84 +77,57 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
/** /**
* @param manifestUri The {@link Uri} of the manifest to be downloaded. * @param manifestUri The {@link Uri} of the manifest to be downloaded.
* @param constructorHelper a {@link DownloaderConstructorHelper} instance. * @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.manifestUri = manifestUri;
this.cache = constructorHelper.getCache(); this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.buildCacheDataSource(false); this.dataSource = constructorHelper.buildCacheDataSource(false);
this.offlineDataSource = constructorHelper.buildCacheDataSource(true); this.offlineDataSource = constructorHelper.buildCacheDataSource(true);
this.priorityTaskManager = constructorHelper.getPriorityTaskManager(); this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
keys = new ArrayList<>(); keys = new ArrayList<>();
resetCounters(); if (trackKeys != null) {
} Collections.addAll(this.keys, trackKeys);
/**
* 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.
*
* @throws IOException Thrown when there is an error downloading.
* @throws InterruptedException If the thread has been interrupted.
*/
@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;
} }
totalSegments = C.LENGTH_UNSET;
} }
/** /**
* Downloads the content for the selected representations in sync or resumes a previously stopped * Downloads the selected tracks in the media. If multiple tracks are selected, they are
* download. * downloaded in sync with one another.
* *
* @throws IOException Thrown when there is an error downloading. * @throws IOException Thrown when there is an error downloading.
* @throws InterruptedException If the thread has been interrupted. * @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 @Override
public final synchronized void download() throws IOException, InterruptedException { public final void download() throws IOException, InterruptedException {
priorityTaskManager.add(C.PRIORITY_DOWNLOAD); priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
try { try {
getManifestIfNeeded(false); List<Segment> segments = initDownload();
List<Segment> segments = initStatus(false);
Collections.sort(segments); Collections.sort(segments);
byte[] buffer = new byte[BUFFER_SIZE_BYTES]; byte[] buffer = new byte[BUFFER_SIZE_BYTES];
CachingCounters cachingCounters = new CachingCounters(); CachingCounters cachingCounters = new CachingCounters();
for (int i = 0; i < segments.size(); i++) { for (int i = 0; i < segments.size(); i++) {
CacheUtil.cache(segments.get(i).dataSpec, cache, dataSource, buffer, try {
priorityTaskManager, C.PRIORITY_DOWNLOAD, cachingCounters, true); CacheUtil.cache(
downloadedBytes += cachingCounters.newlyCachedBytes; segments.get(i).dataSpec,
downloadedSegments++; cache,
dataSource,
buffer,
priorityTaskManager,
C.PRIORITY_DOWNLOAD,
cachingCounters,
true);
downloadedSegments++;
} finally {
downloadedBytes += cachingCounters.newlyCachedBytes;
}
} }
} finally { } finally {
priorityTaskManager.remove(C.PRIORITY_DOWNLOAD); priorityTaskManager.remove(C.PRIORITY_DOWNLOAD);
...@@ -168,7 +140,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> ...@@ -168,7 +140,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
} }
@Override @Override
public float getDownloadPercentage() { public final float getDownloadPercentage() {
// Take local snapshot of the volatile fields // Take local snapshot of the volatile fields
int totalSegments = this.totalSegments; int totalSegments = this.totalSegments;
int downloadedSegments = this.downloadedSegments; int downloadedSegments = this.downloadedSegments;
...@@ -181,29 +153,21 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> ...@@ -181,29 +153,21 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
@Override @Override
public final void remove() throws InterruptedException { public final void remove() throws InterruptedException {
try { try {
getManifestIfNeeded(true); M manifest = getManifest(offlineDataSource, manifestUri);
} catch (IOException e) { List<Segment> segments = getSegments(offlineDataSource, manifest, true);
// Either the manifest file isn't available offline, or it's not parsable. Continue anyway to for (int i = 0; i < segments.size(); i++) {
// reset the counters and attempt to remove the manifest file. removeUri(segments.get(i).dataSpec.uri);
}
resetCounters();
if (manifest != null) {
List<Segment> 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; } catch (IOException e) {
// Ignore exceptions when removing.
} finally {
// Always attempt to remove the manifest.
removeUri(manifestUri);
} }
remove(manifestUri);
} }
// Internal methods.
/** /**
* Loads and parses the manifest. * Loads and parses the manifest.
* *
...@@ -219,44 +183,27 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> ...@@ -219,44 +183,27 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
* *
* @param dataSource The {@link DataSource} through which to load any required data. * @param dataSource The {@link DataSource} through which to load any required data.
* @param manifest The manifest containing the segments. * @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 * segments from being listed. If true then a partial segment list will be returned. If false
* an {@link IOException} will be thrown. * an {@link IOException} will be thrown.
* @throws InterruptedException Thrown if the thread was interrupted. * @throws InterruptedException Thrown if the thread was interrupted.
* @throws IOException Thrown if {@code allowPartialIndex} is false and a load error occurs, or if * @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. * 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 * @return The list of downloadable {@link Segment}s.
* complete.
*/ */
protected abstract Pair<List<Segment>, Boolean> getSegments( protected abstract List<Segment> getSegments(
DataSource dataSource, M manifest, boolean allowIncompleteIndex) DataSource dataSource, M manifest, boolean allowIncompleteList)
throws InterruptedException, IOException; throws InterruptedException, IOException;
private void resetCounters() { /** Initializes the download, returning a list of {@link Segment}s that need to be downloaded. */
totalSegments = C.LENGTH_UNSET; private List<Segment> initDownload() throws IOException, InterruptedException {
downloadedSegments = 0; M manifest = getManifest(dataSource, manifestUri);
downloadedBytes = 0; if (!keys.isEmpty()) {
} manifest = manifest.copy(keys);
}
private void remove(Uri uri) { List<Segment> segments = getSegments(dataSource, manifest, /* allowIncompleteList= */ false);
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<Segment> initStatus(boolean offline)
throws IOException, InterruptedException {
DataSource dataSource = getDataSource(offline);
M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys);
Pair<List<Segment>, Boolean> result = getSegments(dataSource, filteredManifest, offline);
List<Segment> segments = result.first;
boolean isSegmentListComplete = result.second;
CachingCounters cachingCounters = new CachingCounters(); CachingCounters cachingCounters = new CachingCounters();
totalSegments = isSegmentListComplete ? segments.size() : C.LENGTH_UNSET; totalSegments = segments.size();
downloadedSegments = 0; downloadedSegments = 0;
downloadedBytes = 0; downloadedBytes = 0;
for (int i = segments.size() - 1; i >= 0; i--) { for (int i = segments.size() - 1; i >= 0; i--) {
...@@ -272,15 +219,8 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K> ...@@ -272,15 +219,8 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
return segments; return segments;
} }
private M getManifestIfNeeded(boolean offline) throws IOException { private void removeUri(Uri uri) {
if (manifest == null) { CacheUtil.remove(cache, CacheUtil.generateKey(uri));
manifest = getManifest(getDataSource(offline), manifestUri);
}
return manifest;
}
private DataSource getDataSource(boolean offline) {
return offline ? offlineDataSource : dataSource;
} }
} }
...@@ -49,6 +49,22 @@ public final class ParsingLoadable<T> implements Loadable { ...@@ -49,6 +49,22 @@ public final class ParsingLoadable<T> 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> T load(DataSource dataSource, Parser<? extends T> parser, Uri uri)
throws IOException {
ParsingLoadable<T> 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. * The {@link DataSpec} that defines the data to be loaded.
*/ */
public final DataSpec dataSpec; public final DataSpec dataSpec;
......
...@@ -663,11 +663,6 @@ public class DownloadManagerTest { ...@@ -663,11 +663,6 @@ public class DownloadManagerTest {
} }
@Override @Override
public void init() throws InterruptedException, IOException {
// do nothing.
}
@Override
public void download() throws InterruptedException, IOException { public void download() throws InterruptedException, IOException {
assertThat(isRemoveAction).isFalse(); assertThat(isRemoveAction).isFalse();
started.countDown(); started.countDown();
......
...@@ -53,10 +53,7 @@ public final class DashUtil { ...@@ -53,10 +53,7 @@ public final class DashUtil {
*/ */
public static DashManifest loadManifest(DataSource dataSource, Uri uri) public static DashManifest loadManifest(DataSource dataSource, Uri uri)
throws IOException { throws IOException {
ParsingLoadable<DashManifest> loadable = return ParsingLoadable.load(dataSource, new DashManifestParser(), uri);
new ParsingLoadable<>(dataSource, uri, C.DATA_TYPE_MANIFEST, new DashManifestParser());
loadable.load();
return loadable.getResult();
} }
/** /**
......
...@@ -60,9 +60,7 @@ public final class DashDownloadAction extends SegmentDownloadAction<Representati ...@@ -60,9 +60,7 @@ public final class DashDownloadAction extends SegmentDownloadAction<Representati
@Override @Override
protected DashDownloader createDownloader(DownloaderConstructorHelper constructorHelper) { protected DashDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
DashDownloader downloader = new DashDownloader(manifestUri, constructorHelper); return new DashDownloader(manifestUri, constructorHelper, keys);
downloader.selectRepresentations(keys);
return downloader;
} }
@Override @Override
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.source.dash.offline; package com.google.android.exoplayer2.source.dash.offline;
import android.net.Uri; import android.net.Uri;
import android.util.Pair; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.offline.DownloadException; import com.google.android.exoplayer2.offline.DownloadException;
...@@ -38,7 +38,7 @@ import java.util.ArrayList; ...@@ -38,7 +38,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Helper class to download DASH streams. * A downloader for DASH streams.
* *
* <p>Example usage: * <p>Example usage:
* *
...@@ -47,9 +47,11 @@ import java.util.List; ...@@ -47,9 +47,11 @@ import java.util.List;
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null); * DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
* DownloaderConstructorHelper constructorHelper = * DownloaderConstructorHelper constructorHelper =
* new DownloaderConstructorHelper(cache, factory); * new DownloaderConstructorHelper(cache, factory);
* DashDownloader dashDownloader = new DashDownloader(manifestUrl, constructorHelper); * // Create a downloader for the first representation of the first adaptation set of the first
* // Select the first representation of the first adaptation set of the first period * // period.
* dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); * DashDownloader dashDownloader = new DashDownloader(
* manifestUrl, constructorHelper, new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
* // Perform the download.
* dashDownloader.download(); * dashDownloader.download();
* // Access downloaded data using CacheDataSource * // Access downloaded data using CacheDataSource
* CacheDataSource cacheDataSource = * CacheDataSource cacheDataSource =
...@@ -58,27 +60,12 @@ import java.util.List; ...@@ -58,27 +60,12 @@ import java.util.List;
*/ */
public final class DashDownloader extends SegmentDownloader<DashManifest, RepresentationKey> { public final class DashDownloader extends SegmentDownloader<DashManifest, RepresentationKey> {
/** /** @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper, Object[]) */
* @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) public DashDownloader(
*/ Uri manifestUri,
public DashDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { DownloaderConstructorHelper constructorHelper,
super(manifestUri, constructorHelper); @Nullable RepresentationKey[] trackKeys) {
} super(manifestUri, constructorHelper, trackKeys);
@Override
public RepresentationKey[] getAllRepresentationKeys() throws IOException {
ArrayList<RepresentationKey> keys = new ArrayList<>();
DashManifest manifest = getManifest();
for (int periodIndex = 0; periodIndex < manifest.getPeriodCount(); periodIndex++) {
List<AdaptationSet> 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()]);
} }
@Override @Override
...@@ -87,40 +74,36 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres ...@@ -87,40 +74,36 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
} }
@Override @Override
protected Pair<List<Segment>, Boolean> getSegments( protected List<Segment> getSegments(
DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors) DataSource dataSource, DashManifest manifest, boolean allowIncompleteList)
throws InterruptedException, IOException { throws InterruptedException, IOException {
ArrayList<Segment> segments = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>();
boolean segmentListComplete = true;
for (int i = 0; i < manifest.getPeriodCount(); i++) { for (int i = 0; i < manifest.getPeriodCount(); i++) {
Period period = manifest.getPeriod(i); Period period = manifest.getPeriod(i);
long periodStartUs = C.msToUs(period.startMs); long periodStartUs = C.msToUs(period.startMs);
long periodDurationUs = manifest.getPeriodDurationUs(i); long periodDurationUs = manifest.getPeriodDurationUs(i);
List<AdaptationSet> adaptationSets = period.adaptationSets; List<AdaptationSet> adaptationSets = period.adaptationSets;
for (int j = 0; j < adaptationSets.size(); j++) { for (int j = 0; j < adaptationSets.size(); j++) {
if (!addSegmentsForAdaptationSet( addSegmentsForAdaptationSet(
dataSource, dataSource,
adaptationSets.get(j), adaptationSets.get(j),
periodStartUs, periodStartUs,
periodDurationUs, periodDurationUs,
allowIndexLoadErrors, allowIncompleteList,
segments)) { segments);
segmentListComplete = false;
}
} }
} }
return Pair.<List<Segment>, Boolean>create(segments, segmentListComplete); return segments;
} }
private static boolean addSegmentsForAdaptationSet( private static void addSegmentsForAdaptationSet(
DataSource dataSource, DataSource dataSource,
AdaptationSet adaptationSet, AdaptationSet adaptationSet,
long periodStartUs, long periodStartUs,
long periodDurationUs, long periodDurationUs,
boolean allowIndexLoadErrors, boolean allowIncompleteList,
ArrayList<Segment> out) ArrayList<Segment> out)
throws IOException, InterruptedException { throws IOException, InterruptedException {
boolean segmentListComplete = true;
for (int i = 0; i < adaptationSet.representations.size(); i++) { for (int i = 0; i < adaptationSet.representations.size(); i++) {
Representation representation = adaptationSet.representations.get(i); Representation representation = adaptationSet.representations.get(i);
DashSegmentIndex index; DashSegmentIndex index;
...@@ -131,11 +114,11 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres ...@@ -131,11 +114,11 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
throw new DownloadException("Missing segment index"); throw new DownloadException("Missing segment index");
} }
} catch (IOException e) { } catch (IOException e) {
if (!allowIndexLoadErrors) { if (!allowIncompleteList) {
throw e; throw e;
} }
// Loading failed, but load errors are allowed. Advance to the next representation. // Loading failed, but generating an incomplete segment list is allowed. Advance to the next
segmentListComplete = false; // representation.
continue; continue;
} }
...@@ -159,8 +142,6 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres ...@@ -159,8 +142,6 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
addSegment(periodStartUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j), out); addSegment(periodStartUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j), out);
} }
} }
return segmentListComplete;
} }
private static void addSegment( private static void addSegment(
......
...@@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.source.dash.offline.DashDownloadTest ...@@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.source.dash.offline.DashDownloadTest
import static com.google.android.exoplayer2.source.dash.offline.DashDownloadTestData.TEST_MPD_URI; import static com.google.android.exoplayer2.source.dash.offline.DashDownloadTestData.TEST_MPD_URI;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertDataCached;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -28,7 +27,6 @@ import static org.mockito.Mockito.when; ...@@ -28,7 +27,6 @@ import static org.mockito.Mockito.when;
import com.google.android.exoplayer2.offline.DownloadException; import com.google.android.exoplayer2.offline.DownloadException;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
...@@ -40,7 +38,6 @@ import com.google.android.exoplayer2.upstream.cache.SimpleCache; ...@@ -40,7 +38,6 @@ import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -69,47 +66,6 @@ public class DashDownloaderTest { ...@@ -69,47 +66,6 @@ public class DashDownloaderTest {
} }
@Test @Test
public void testGetManifest() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet().setData(TEST_MPD_URI, TEST_MPD);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
DashManifest manifest = dashDownloader.getManifest();
assertThat(manifest).isNotNull();
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadManifestFailure() throws Exception {
byte[] testMpdFirstPart = Arrays.copyOf(TEST_MPD, 10);
byte[] testMpdSecondPart = Arrays.copyOfRange(TEST_MPD, 10, TEST_MPD.length);
FakeDataSet fakeDataSet =
new FakeDataSet()
.newData(TEST_MPD_URI)
.appendReadData(testMpdFirstPart)
.appendReadError(new IOException())
.appendReadData(testMpdSecondPart)
.endData();
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
// fails on the first try
try {
dashDownloader.getManifest();
fail();
} catch (IOException e) {
// ignore
}
DataSpec dataSpec = new DataSpec(TEST_MPD_URI, 0, testMpdFirstPart.length, null);
assertDataCached(cache, dataSpec, testMpdFirstPart);
// on the second try it downloads the rest of the data
DashManifest manifest = dashDownloader.getManifest();
assertThat(manifest).isNotNull();
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadRepresentation() throws Exception { public void testDownloadRepresentation() throws Exception {
FakeDataSet fakeDataSet = FakeDataSet fakeDataSet =
new FakeDataSet() new FakeDataSet()
...@@ -118,11 +74,9 @@ public class DashDownloaderTest { ...@@ -118,11 +74,9 @@ public class DashDownloaderTest {
.setRandomData("audio_segment_1", 4) .setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5) .setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6); .setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new RepresentationKey(0, 0, 0));
dashDownloader.download(); dashDownloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
} }
...@@ -139,11 +93,9 @@ public class DashDownloaderTest { ...@@ -139,11 +93,9 @@ public class DashDownloaderTest {
.endData() .endData()
.setRandomData("audio_segment_2", 5) .setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6); .setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new RepresentationKey(0, 0, 0));
dashDownloader.download(); dashDownloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
} }
...@@ -159,12 +111,11 @@ public class DashDownloaderTest { ...@@ -159,12 +111,11 @@ public class DashDownloaderTest {
.setRandomData("text_segment_1", 1) .setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2) .setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3); .setRandomData("text_segment_3", 3);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations( DashDownloader dashDownloader =
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)}); getDashDownloader(
fakeDataSet, new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0));
dashDownloader.download(); dashDownloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
} }
...@@ -183,20 +134,10 @@ public class DashDownloaderTest { ...@@ -183,20 +134,10 @@ public class DashDownloaderTest {
.setRandomData("period_2_segment_1", 1) .setRandomData("period_2_segment_1", 1)
.setRandomData("period_2_segment_2", 2) .setRandomData("period_2_segment_2", 2)
.setRandomData("period_2_segment_3", 3); .setRandomData("period_2_segment_3", 3);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
// dashDownloader.selectRepresentations() isn't called DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.download();
assertCachedData(cache, fakeDataSet);
dashDownloader.remove();
// select something random
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
// clear selection
dashDownloader.selectRepresentations(new RepresentationKey[0]);
dashDownloader.download(); dashDownloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
dashDownloader.remove();
} }
@Test @Test
...@@ -214,11 +155,9 @@ public class DashDownloaderTest { ...@@ -214,11 +155,9 @@ public class DashDownloaderTest {
FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet); FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet);
Factory factory = mock(Factory.class); Factory factory = mock(Factory.class);
when(factory.createDataSource()).thenReturn(fakeDataSource); when(factory.createDataSource()).thenReturn(fakeDataSource);
DashDownloader dashDownloader =
new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
dashDownloader.selectRepresentations( DashDownloader dashDownloader =
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)}); getDashDownloader(factory, new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0));
dashDownloader.download(); dashDownloader.download();
DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs(); DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs();
...@@ -248,11 +187,9 @@ public class DashDownloaderTest { ...@@ -248,11 +187,9 @@ public class DashDownloaderTest {
FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet); FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet);
Factory factory = mock(Factory.class); Factory factory = mock(Factory.class);
when(factory.createDataSource()).thenReturn(fakeDataSource); when(factory.createDataSource()).thenReturn(fakeDataSource);
DashDownloader dashDownloader =
new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
dashDownloader.selectRepresentations( DashDownloader dashDownloader =
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(1, 0, 0)}); getDashDownloader(factory, new RepresentationKey(0, 0, 0), new RepresentationKey(1, 0, 0));
dashDownloader.download(); dashDownloader.download();
DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs(); DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs();
...@@ -280,18 +217,15 @@ public class DashDownloaderTest { ...@@ -280,18 +217,15 @@ public class DashDownloaderTest {
.appendReadData(TestUtil.buildTestData(3)) .appendReadData(TestUtil.buildTestData(3))
.endData() .endData()
.setRandomData("audio_segment_3", 6); .setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new RepresentationKey(0, 0, 0));
// downloadRepresentations fails on the first try
try { try {
dashDownloader.download(); dashDownloader.download();
fail(); fail();
} catch (IOException e) { } catch (IOException e) {
// ignore // Expected.
} }
dashDownloader.download(); dashDownloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
} }
...@@ -308,22 +242,16 @@ public class DashDownloaderTest { ...@@ -308,22 +242,16 @@ public class DashDownloaderTest {
.appendReadData(TestUtil.buildTestData(3)) .appendReadData(TestUtil.buildTestData(3))
.endData() .endData()
.setRandomData("audio_segment_3", 6); .setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new RepresentationKey(0, 0, 0));
dashDownloader.init();
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0); assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
// downloadRepresentations fails after downloading init data, segment 1 and 2 bytes in segment 2
try { try {
dashDownloader.download(); dashDownloader.download();
fail(); fail();
} catch (IOException e) { } catch (IOException e) {
// ignore // Failure expected after downloading init data, segment 1 and 2 bytes in segment 2.
} }
dashDownloader.init();
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(10 + 4 + 2); assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(10 + 4 + 2);
dashDownloader.download(); dashDownloader.download();
...@@ -331,7 +259,7 @@ public class DashDownloaderTest { ...@@ -331,7 +259,7 @@ public class DashDownloaderTest {
} }
@Test @Test
public void testRemoveAll() throws Exception { public void testRemove() throws Exception {
FakeDataSet fakeDataSet = FakeDataSet fakeDataSet =
new FakeDataSet() new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD) .setData(TEST_MPD_URI, TEST_MPD)
...@@ -342,13 +270,12 @@ public class DashDownloaderTest { ...@@ -342,13 +270,12 @@ public class DashDownloaderTest {
.setRandomData("text_segment_1", 1) .setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2) .setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3); .setRandomData("text_segment_3", 3);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)});
dashDownloader.download();
DashDownloader dashDownloader =
getDashDownloader(
fakeDataSet, new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0));
dashDownloader.download();
dashDownloader.remove(); dashDownloader.remove();
assertCacheEmpty(cache); assertCacheEmpty(cache);
} }
...@@ -358,43 +285,24 @@ public class DashDownloaderTest { ...@@ -358,43 +285,24 @@ public class DashDownloaderTest {
new FakeDataSet() new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD_NO_INDEX) .setData(TEST_MPD_URI, TEST_MPD_NO_INDEX)
.setRandomData("test_segment_1", 4); .setRandomData("test_segment_1", 4);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new RepresentationKey(0, 0, 0));
dashDownloader.init();
try { try {
dashDownloader.download(); dashDownloader.download();
fail(); fail();
} catch (DownloadException e) { } catch (DownloadException e) {
// expected exception. // Expected.
} }
dashDownloader.remove(); dashDownloader.remove();
assertCacheEmpty(cache); assertCacheEmpty(cache);
} }
@Test private DashDownloader getDashDownloader(FakeDataSet fakeDataSet, RepresentationKey... keys) {
public void testSelectRepresentationsClearsPreviousSelection() throws Exception { return getDashDownloader(new Factory(null).setFakeDataSet(fakeDataSet), keys);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)});
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
dashDownloader.download();
assertCachedData(cache, fakeDataSet);
} }
private DashDownloader getDashDownloader(FakeDataSet fakeDataSet) { private DashDownloader getDashDownloader(Factory factory, RepresentationKey... keys) {
Factory factory = new Factory(null).setFakeDataSet(fakeDataSet); return new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory), keys);
return new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
} }
} }
...@@ -60,9 +60,7 @@ public final class HlsDownloadAction extends SegmentDownloadAction<RenditionKey> ...@@ -60,9 +60,7 @@ public final class HlsDownloadAction extends SegmentDownloadAction<RenditionKey>
@Override @Override
protected HlsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) { protected HlsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
HlsDownloader downloader = new HlsDownloader(manifestUri, constructorHelper); return new HlsDownloader(manifestUri, constructorHelper, keys);
downloader.selectRepresentations(keys);
return downloader;
} }
@Override @Override
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.hls.offline; package com.google.android.exoplayer2.source.hls.offline;
import android.net.Uri; import android.net.Uri;
import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.offline.SegmentDownloader;
...@@ -35,29 +34,13 @@ import java.util.ArrayList; ...@@ -35,29 +34,13 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
/** /** A downloader for HLS streams. */
* Helper class to download HLS streams.
*
* <p>A subset of renditions can be downloaded by selecting them using {@link
* #selectRepresentations(Object[])}.
*/
public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, RenditionKey> { public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, RenditionKey> {
/** /** @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper, Object[]) */
* @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) public HlsDownloader(
*/ Uri manifestUri, DownloaderConstructorHelper constructorHelper, RenditionKey[] trackKeys) {
public HlsDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { super(manifestUri, constructorHelper, trackKeys);
super(manifestUri, constructorHelper);
}
@Override
public RenditionKey[] getAllRepresentationKeys() throws IOException {
ArrayList<RenditionKey> 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()]);
} }
@Override @Override
...@@ -71,8 +54,8 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re ...@@ -71,8 +54,8 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
} }
@Override @Override
protected Pair<List<Segment>, Boolean> getSegments( protected List<Segment> getSegments(
DataSource dataSource, HlsMasterPlaylist manifest, boolean allowIndexLoadErrors) DataSource dataSource, HlsMasterPlaylist manifest, boolean allowIncompleteList)
throws IOException { throws IOException {
HashSet<Uri> encryptionKeyUris = new HashSet<>(); HashSet<Uri> encryptionKeyUris = new HashSet<>();
ArrayList<HlsUrl> renditionUrls = new ArrayList<>(); ArrayList<HlsUrl> renditionUrls = new ArrayList<>();
...@@ -81,17 +64,15 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re ...@@ -81,17 +64,15 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
renditionUrls.addAll(manifest.subtitles); renditionUrls.addAll(manifest.subtitles);
ArrayList<Segment> segments = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>();
boolean segmentListComplete = true;
for (HlsUrl renditionUrl : renditionUrls) { for (HlsUrl renditionUrl : renditionUrls) {
HlsMediaPlaylist mediaPlaylist = null; HlsMediaPlaylist mediaPlaylist = null;
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url); Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url);
try { try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri); mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri);
} catch (IOException e) { } catch (IOException e) {
if (!allowIndexLoadErrors) { if (!allowIncompleteList) {
throw e; throw e;
} }
segmentListComplete = false;
} }
segments.add(new Segment(mediaPlaylist != null ? mediaPlaylist.startTimeUs : Long.MIN_VALUE, segments.add(new Segment(mediaPlaylist != null ? mediaPlaylist.startTimeUs : Long.MIN_VALUE,
new DataSpec(uri))); new DataSpec(uri)));
...@@ -111,7 +92,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re ...@@ -111,7 +92,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
addSegment(segments, mediaPlaylist, segment, encryptionKeyUris); addSegment(segments, mediaPlaylist, segment, encryptionKeyUris);
} }
} }
return Pair.<List<Segment>, Boolean>create(segments, segmentListComplete); return segments;
} }
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException { private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException {
...@@ -139,10 +120,4 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re ...@@ -139,10 +120,4 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null))); new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null)));
} }
private static void extractUrls(List<HlsUrl> hlsUrls, ArrayList<RenditionKey> renditionKeys) {
for (int i = 0; i < hlsUrls.size(); i++) {
renditionKeys.add(new RenditionKey(hlsUrls.get(i).url));
}
}
} }
...@@ -33,8 +33,8 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa ...@@ -33,8 +33,8 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; 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.source.hls.playlist.RenditionKey;
import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource.Factory; import com.google.android.exoplayer2.testutil.FakeDataSource.Factory;
...@@ -56,7 +56,6 @@ public class HlsDownloaderTest { ...@@ -56,7 +56,6 @@ public class HlsDownloaderTest {
private SimpleCache cache; private SimpleCache cache;
private File tempFolder; private File tempFolder;
private FakeDataSet fakeDataSet; private FakeDataSet fakeDataSet;
private HlsDownloader hlsDownloader;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
...@@ -74,7 +73,6 @@ public class HlsDownloaderTest { ...@@ -74,7 +73,6 @@ public class HlsDownloaderTest {
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13) .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14) .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15); .setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15);
hlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI);
} }
@After @After
...@@ -83,55 +81,18 @@ public class HlsDownloaderTest { ...@@ -83,55 +81,18 @@ public class HlsDownloaderTest {
} }
@Test @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 { public void testCounterMethods() throws Exception {
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(); downloader.download();
assertThat(hlsDownloader.getDownloadedBytes()) assertThat(downloader.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())
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12); .isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
} }
@Test @Test
public void testDownloadRepresentation() throws Exception { public void testDownloadRepresentation() throws Exception {
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(); downloader.download();
assertCachedData( assertCachedData(
cache, cache,
...@@ -145,8 +106,9 @@ public class HlsDownloaderTest { ...@@ -145,8 +106,9 @@ public class HlsDownloaderTest {
@Test @Test
public void testDownloadMultipleRepresentations() throws Exception { public void testDownloadMultipleRepresentations() throws Exception {
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI)); HlsDownloader downloader =
hlsDownloader.download(); getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI));
downloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
} }
...@@ -163,36 +125,28 @@ public class HlsDownloaderTest { ...@@ -163,36 +125,28 @@ public class HlsDownloaderTest {
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence0.ts", 13) .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence0.ts", 13)
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence1.ts", 14) .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence1.ts", 14)
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15); .setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15);
hlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI);
// hlsDownloader.selectRepresentations() isn't called HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, null);
hlsDownloader.download(); downloader.download();
assertCachedData(cache, fakeDataSet);
hlsDownloader.remove();
// select something random
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
// clear selection
hlsDownloader.selectRepresentations(getKeys());
hlsDownloader.download();
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
hlsDownloader.remove();
} }
@Test @Test
public void testRemoveAll() throws Exception { public void testRemove() throws Exception {
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI)); HlsDownloader downloader =
hlsDownloader.download(); getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI));
hlsDownloader.remove(); downloader.download();
downloader.remove();
assertCacheEmpty(cache); assertCacheEmpty(cache);
} }
@Test @Test
public void testDownloadMediaPlaylist() throws Exception { public void testDownloadMediaPlaylist() throws Exception {
hlsDownloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI); HlsDownloader downloader =
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI)); getHlsDownloader(MEDIA_PLAYLIST_1_URI, getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(); downloader.download();
assertCachedData( assertCachedData(
cache, cache,
...@@ -213,17 +167,17 @@ public class HlsDownloaderTest { ...@@ -213,17 +167,17 @@ public class HlsDownloaderTest {
.setRandomData("fileSequence0.ts", 10) .setRandomData("fileSequence0.ts", 10)
.setRandomData("fileSequence1.ts", 11) .setRandomData("fileSequence1.ts", 11)
.setRandomData("fileSequence2.ts", 12); .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); assertCachedData(cache, fakeDataSet);
} }
private HlsDownloader getHlsDownloader(String mediaPlaylistUri) { private HlsDownloader getHlsDownloader(String mediaPlaylistUri, @Nullable RenditionKey[] keys) {
Factory factory = new Factory(null).setFakeDataSet(fakeDataSet); Factory factory = new Factory(null).setFakeDataSet(fakeDataSet);
return new HlsDownloader( return new HlsDownloader(
Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory)); Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory), keys);
} }
private static RenditionKey[] getKeys(String... urls) { private static RenditionKey[] getKeys(String... urls) {
......
...@@ -60,9 +60,7 @@ public final class SsDownloadAction extends SegmentDownloadAction<TrackKey> { ...@@ -60,9 +60,7 @@ public final class SsDownloadAction extends SegmentDownloadAction<TrackKey> {
@Override @Override
protected SsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) { protected SsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
SsDownloader downloader = new SsDownloader(manifestUri, constructorHelper); return new SsDownloader(manifestUri, constructorHelper, keys);
downloader.selectRepresentations(keys);
return downloader;
} }
@Override @Override
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming.offline; package com.google.android.exoplayer2.source.smoothstreaming.offline;
import android.net.Uri; import android.net.Uri;
import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.offline.SegmentDownloader;
...@@ -33,7 +32,7 @@ import java.util.ArrayList; ...@@ -33,7 +32,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Helper class to download SmoothStreaming streams. * A downloader for SmoothStreaming streams.
* *
* <p>Example usage: * <p>Example usage:
* *
...@@ -42,9 +41,10 @@ import java.util.List; ...@@ -42,9 +41,10 @@ import java.util.List;
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null); * DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
* DownloaderConstructorHelper constructorHelper = * DownloaderConstructorHelper constructorHelper =
* new DownloaderConstructorHelper(cache, factory); * new DownloaderConstructorHelper(cache, factory);
* SsDownloader ssDownloader = new SsDownloader(manifestUrl, constructorHelper); * // Create a downloader for the first track of the first stream element.
* // Select the first track of the first stream element * SsDownloader ssDownloader = new SsDownloader(
* ssDownloader.selectRepresentations(new TrackKey[] {new TrackKey(0, 0)}); * manifestUrl, constructorHelper, new TrackKey[] {new TrackKey(0, 0)});
* // Perform the download.
* ssDownloader.download(); * ssDownloader.download();
* // Access downloaded data using CacheDataSource * // Access downloaded data using CacheDataSource
* CacheDataSource cacheDataSource = * CacheDataSource cacheDataSource =
...@@ -53,24 +53,10 @@ import java.util.List; ...@@ -53,24 +53,10 @@ import java.util.List;
*/ */
public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey> { public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey> {
/** /** @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper, Object[]) */
* @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) public SsDownloader(
*/ Uri manifestUri, DownloaderConstructorHelper constructorHelper, TrackKey[] trackKeys) {
public SsDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { super(SsUtil.fixManifestUri(manifestUri), constructorHelper, trackKeys);
super(SsUtil.fixManifestUri(manifestUri), constructorHelper);
}
@Override
public TrackKey[] getAllRepresentationKeys() throws IOException {
ArrayList<TrackKey> 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()]);
} }
@Override @Override
...@@ -82,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey> ...@@ -82,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey>
} }
@Override @Override
protected Pair<List<Segment>, Boolean> getSegments( protected List<Segment> getSegments(
DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors) throws IOException { DataSource dataSource, SsManifest manifest, boolean allowIncompleteList) throws IOException {
ArrayList<Segment> segments = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>();
for (StreamElement streamElement : manifest.streamElements) { for (StreamElement streamElement : manifest.streamElements) {
for (int i = 0; i < streamElement.formats.length; i++) { for (int i = 0; i < streamElement.formats.length; i++) {
...@@ -95,7 +81,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey> ...@@ -95,7 +81,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey>
} }
} }
} }
return Pair.<List<Segment>, Boolean>create(segments, true); return segments;
} }
} }
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
*/ */
package com.google.android.exoplayer2.playbacktests.gts; package com.google.android.exoplayer2.playbacktests.gts;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import android.net.Uri; import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2; import android.test.ActivityInstrumentationTestCase2;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; 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.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.Representation; import com.google.android.exoplayer2.source.dash.manifest.Representation;
...@@ -35,8 +35,6 @@ import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; ...@@ -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.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -47,9 +45,13 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos ...@@ -47,9 +45,13 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos
private static final String TAG = "DashDownloadTest"; private static final String TAG = "DashDownloadTest";
private static final Uri MANIFEST_URI = Uri.parse(DashTestData.H264_MANIFEST);
private DashTestRunner testRunner; private DashTestRunner testRunner;
private File tempFolder; private File tempFolder;
private SimpleCache cache; private SimpleCache cache;
private DefaultHttpDataSourceFactory httpDataSourceFactory;
private CacheDataSourceFactory offlineDataSourceFactory;
public DashDownloadTest() { public DashDownloadTest() {
super(HostActivity.class); super(HostActivity.class);
...@@ -66,6 +68,10 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos ...@@ -66,6 +68,10 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos
DashTestData.H264_CDD_FIXED); DashTestData.H264_CDD_FIXED);
tempFolder = Util.createTempDirectory(getActivity(), "ExoPlayerTest"); tempFolder = Util.createTempDirectory(getActivity(), "ExoPlayerTest");
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
httpDataSourceFactory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
offlineDataSourceFactory =
new CacheDataSourceFactory(
cache, DummyDataSource.FACTORY, CacheDataSource.FLAG_BLOCK_ON_CACHE);
} }
@Override @Override
...@@ -83,17 +89,13 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos ...@@ -83,17 +89,13 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos
return; // Pass. return; // Pass.
} }
// Download manifest only
createDashDownloader().getManifest();
long manifestLength = cache.getCacheSpace();
// Download representations
DashDownloader dashDownloader = downloadContent(); DashDownloader dashDownloader = downloadContent();
assertThat(dashDownloader.getDownloadedBytes()) dashDownloader.download();
.isEqualTo(cache.getCacheSpace() - manifestLength);
testRunner.setStreamName("test_h264_fixed_download"). testRunner
setDataSourceFactory(newOfflineCacheDataSourceFactory()).run(); .setStreamName("test_h264_fixed_download")
.setDataSourceFactory(offlineDataSourceFactory)
.run();
dashDownloader.remove(); dashDownloader.remove();
...@@ -102,50 +104,27 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos ...@@ -102,50 +104,27 @@ public final class DashDownloadTest extends ActivityInstrumentationTestCase2<Hos
} }
private DashDownloader downloadContent() throws Exception { private DashDownloader downloadContent() throws Exception {
DashDownloader dashDownloader = createDashDownloader(); DashManifest dashManifest =
DashManifest dashManifest = dashDownloader.getManifest(); DashUtil.loadManifest(httpDataSourceFactory.createDataSource(), MANIFEST_URI);
try { ArrayList<RepresentationKey> keys = new ArrayList<>();
ArrayList<RepresentationKey> keys = new ArrayList<>(); for (int pIndex = 0; pIndex < dashManifest.getPeriodCount(); pIndex++) {
for (int pIndex = 0; pIndex < dashManifest.getPeriodCount(); pIndex++) { List<AdaptationSet> adaptationSets = dashManifest.getPeriod(pIndex).adaptationSets;
List<AdaptationSet> adaptationSets = dashManifest.getPeriod(pIndex).adaptationSets; for (int aIndex = 0; aIndex < adaptationSets.size(); aIndex++) {
for (int aIndex = 0; aIndex < adaptationSets.size(); aIndex++) { AdaptationSet adaptationSet = adaptationSets.get(aIndex);
AdaptationSet adaptationSet = adaptationSets.get(aIndex); List<Representation> representations = adaptationSet.representations;
List<Representation> representations = adaptationSet.representations; for (int rIndex = 0; rIndex < representations.size(); rIndex++) {
for (int rIndex = 0; rIndex < representations.size(); rIndex++) { String id = representations.get(rIndex).format.id;
String id = representations.get(rIndex).format.id; if (DashTestData.AAC_AUDIO_REPRESENTATION_ID.equals(id)
if (DashTestData.AAC_AUDIO_REPRESENTATION_ID.equals(id) || DashTestData.H264_CDD_FIXED.equals(id)) {
|| DashTestData.H264_CDD_FIXED.equals(id)) { keys.add(new RepresentationKey(pIndex, aIndex, rIndex));
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 = DownloaderConstructorHelper constructorHelper =
new DownloaderConstructorHelper(cache, new DefaultHttpDataSourceFactory("ExoPlayer", null)); new DownloaderConstructorHelper(cache, httpDataSourceFactory);
return new DashDownloader(Uri.parse(DashTestData.H264_MANIFEST), constructorHelper); return new DashDownloader(
} MANIFEST_URI, constructorHelper, keys.toArray(new RepresentationKey[keys.size()]));
private CacheDataSourceFactory newOfflineCacheDataSourceFactory() {
return new CacheDataSourceFactory(cache, DummyDataSource.FACTORY,
CacheDataSource.FLAG_BLOCK_ON_CACHE);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment