mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
d4f75963c4
commit
175a0100d0
16 changed files with 320 additions and 619 deletions
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
@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 {
|
||||||
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(
|
return new ProgressiveDownloadAction(
|
||||||
/* isRemoveAction= */ false, /* data= */ null, manifestUri, /* customCacheKey= */ null);
|
isRemoveAction, sampleName, manifestUri, /* customCacheKey= */ null);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DownloadAction getRemoveAction() {
|
|
||||||
return new ProgressiveDownloadAction(
|
|
||||||
/* isRemoveAction= */ true, /* data= */ null, manifestUri, /* customCacheKey= */ null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,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.
|
||||||
*
|
*
|
||||||
|
|
@ -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 {
|
||||||
* available.
|
* available.
|
||||||
*/
|
*/
|
||||||
float getDownloadPercentage();
|
float getDownloadPercentage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the media.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException Thrown if the thread was interrupted.
|
||||||
|
*/
|
||||||
|
void remove() throws InterruptedException;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,28 +53,24 @@ public final class ProgressiveDownloader implements Downloader {
|
||||||
cachingCounters = new CachingCounters();
|
cachingCounters = new CachingCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() {
|
|
||||||
CacheUtil.getCached(dataSpec, cache, cachingCounters);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@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
|
|
||||||
public void remove() {
|
|
||||||
CacheUtil.remove(cache, CacheUtil.getKey(dataSpec));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getDownloadedBytes() {
|
public long getDownloadedBytes() {
|
||||||
return cachingCounters.totalCachedBytes();
|
return cachingCounters.totalCachedBytes();
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
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;
|
||||||
* 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>
|
||||||
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>
|
||||||
/**
|
/**
|
||||||
* @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);
|
||||||
|
}
|
||||||
|
totalSegments = C.LENGTH_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the manifest. Downloads and parses it if necessary.
|
* Downloads the selected tracks in the media. If multiple tracks are selected, they are
|
||||||
*
|
* downloaded in sync with one another.
|
||||||
* @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 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 void init() throws IOException, InterruptedException {
|
public final void download() 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 {
|
|
||||||
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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@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>
|
||||||
@Override
|
@Override
|
||||||
public final void remove() throws InterruptedException {
|
public final void remove() throws InterruptedException {
|
||||||
try {
|
try {
|
||||||
getManifestIfNeeded(true);
|
M manifest = getManifest(offlineDataSource, manifestUri);
|
||||||
|
List<Segment> segments = getSegments(offlineDataSource, manifest, true);
|
||||||
|
for (int i = 0; i < segments.size(); i++) {
|
||||||
|
removeUri(segments.get(i).dataSpec.uri);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Either the manifest file isn't available offline, or it's not parsable. Continue anyway to
|
// Ignore exceptions when removing.
|
||||||
// reset the counters and attempt to remove the manifest file.
|
} finally {
|
||||||
|
// Always attempt to remove the manifest.
|
||||||
|
removeUri(manifestUri);
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
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>
|
||||||
*
|
*
|
||||||
* @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>
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -662,11 +662,6 @@ public class DownloadManagerTest {
|
||||||
this.blocker = new com.google.android.exoplayer2.util.ConditionVariable();
|
this.blocker = new com.google.android.exoplayer2.util.ConditionVariable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws InterruptedException, IOException {
|
|
||||||
// do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void download() throws InterruptedException, IOException {
|
public void download() throws InterruptedException, IOException {
|
||||||
assertThat(isRemoveAction).isFalse();
|
assertThat(isRemoveAction).isFalse();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
@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 @@
|
||||||
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;
|
||||||
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;
|
||||||
* 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;
|
||||||
*/
|
*/
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
||||||
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
|
||||||
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
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
|
|
@ -68,47 +65,6 @@ public class DashDownloaderTest {
|
||||||
Util.recursiveDelete(tempFolder);
|
Util.recursiveDelete(tempFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
public void testDownloadRepresentation() throws Exception {
|
public void testDownloadRepresentation() throws Exception {
|
||||||
FakeDataSet fakeDataSet =
|
FakeDataSet fakeDataSet =
|
||||||
|
|
@ -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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 dashDownloader = getDashDownloader(fakeDataSet);
|
||||||
|
|
||||||
// dashDownloader.selectRepresentations() isn't called
|
|
||||||
dashDownloader.download();
|
dashDownloader.download();
|
||||||
assertCachedData(cache, fakeDataSet);
|
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();
|
|
||||||
assertCachedData(cache, fakeDataSet);
|
|
||||||
dashDownloader.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
.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 {
|
||||||
.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 dashDownloader = getDashDownloader(fakeDataSet, new RepresentationKey(0, 0, 0));
|
||||||
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
|
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
|
||||||
|
|
||||||
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
|
|
||||||
dashDownloader.init();
|
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
.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();
|
||||||
|
|
||||||
dashDownloader.remove();
|
dashDownloader.remove();
|
||||||
|
|
||||||
assertCacheEmpty(cache);
|
assertCacheEmpty(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
@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 @@
|
||||||
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;
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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 {
|
||||||
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 {
|
||||||
.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
|
||||||
|
|
@ -82,56 +80,19 @@ public class HlsDownloaderTest {
|
||||||
Util.recursiveDelete(tempFolder);
|
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
|
@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 {
|
||||||
|
|
||||||
@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 {
|
||||||
.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 {
|
||||||
.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> {
|
||||||
|
|
||||||
@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 @@
|
||||||
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;
|
||||||
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;
|
||||||
* 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;
|
||||||
*/
|
*/
|
||||||
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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair.<List<Segment>, Boolean>create(segments, true);
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue