Converge DownloadHelper implementations.

Moving most of the logic to the base DownloaderHelper helps to implement track
selection for downloading in a single place instead of multiple places.

PiperOrigin-RevId: 223964869
This commit is contained in:
tonihei 2018-12-04 14:16:20 +00:00 committed by Oliver Woodman
parent 8de149eb78
commit 8a566fb330
5 changed files with 126 additions and 171 deletions

View file

@ -15,15 +15,21 @@
*/
package com.google.android.exoplayer2.offline;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.List;
/** A helper for initializing and removing downloads. */
public abstract class DownloadHelper {
/**
* A helper for initializing and removing downloads.
*
* @param <T> The manifest type.
*/
public abstract class DownloadHelper<T> {
/** A callback to be notified when the {@link DownloadHelper} is prepared. */
public interface Callback {
@ -44,6 +50,26 @@ public abstract class DownloadHelper {
void onPrepareError(DownloadHelper helper, IOException e);
}
private final String downloadType;
private final Uri uri;
@Nullable private final String cacheKey;
@Nullable private T manifest;
@Nullable private TrackGroupArray[] trackGroupArrays;
/**
* Create download helper.
*
* @param downloadType A download type. This value will be used as {@link DownloadAction#type}.
* @param uri A {@link Uri}.
* @param cacheKey An optional cache key.
*/
public DownloadHelper(String downloadType, Uri uri, @Nullable String cacheKey) {
this.downloadType = downloadType;
this.uri = uri;
this.cacheKey = cacheKey;
}
/**
* Initializes the helper for starting a download.
*
@ -51,14 +77,15 @@ public abstract class DownloadHelper {
* will be invoked on the calling thread unless that thread does not have an associated {@link
* Looper}, in which case it will be called on the application's main thread.
*/
public void prepare(final Callback callback) {
public final void prepare(final Callback callback) {
final Handler handler =
new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
new Thread() {
@Override
public void run() {
try {
prepareInternal();
manifest = loadManifest(uri);
trackGroupArrays = getTrackGroupArrays(manifest);
handler.post(() -> callback.onPrepared(DownloadHelper.this));
} catch (final IOException e) {
handler.post(() -> callback.onPrepareError(DownloadHelper.this, e));
@ -67,18 +94,20 @@ public abstract class DownloadHelper {
}.start();
}
/**
* Called on a background thread during preparation.
*
* @throws IOException If preparation fails.
*/
protected abstract void prepareInternal() throws IOException;
/** Returns the manifest. Must not be called until after preparation completes. */
public final T getManifest() {
Assertions.checkNotNull(manifest);
return manifest;
}
/**
* Returns the number of periods for which media is available. Must not be called until after
* preparation completes.
*/
public abstract int getPeriodCount();
public final int getPeriodCount() {
Assertions.checkNotNull(trackGroupArrays);
return trackGroupArrays.length;
}
/**
* Returns the track groups for the given period. Must not be called until after preparation
@ -88,7 +117,10 @@ public abstract class DownloadHelper {
* @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream
* content.
*/
public abstract TrackGroupArray getTrackGroups(int periodIndex);
public final TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(trackGroupArrays);
return trackGroupArrays[periodIndex];
}
/**
* Builds a {@link DownloadAction} for downloading the specified tracks. Must not be called until
@ -98,12 +130,41 @@ public abstract class DownloadHelper {
* @param trackKeys The selected tracks. If empty, all streams will be downloaded.
* @return The built {@link DownloadAction}.
*/
public abstract DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys);
public final DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
downloadType, uri, toStreamKeys(trackKeys), cacheKey, data);
}
/**
* Builds a {@link DownloadAction} for removing the media. May be called in any state.
*
* @return The built {@link DownloadAction}.
*/
public abstract DownloadAction getRemoveAction();
public final DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(downloadType, uri, cacheKey);
}
/**
* Loads the manifest. This method is called on a background thread.
*
* @param uri The manifest uri.
* @throws IOException If loading fails.
*/
protected abstract T loadManifest(Uri uri) throws IOException;
/**
* Returns the track group arrays for each period in the manifest.
*
* @param manifest The manifest.
* @return An array of {@link TrackGroupArray}s. One for each period in the manifest.
*/
protected abstract TrackGroupArray[] getTrackGroupArrays(T manifest);
/**
* Converts a list of {@link TrackKey track keys} to {@link StreamKey stream keys}.
*
* @param trackKeys A list of track keys.
* @return A corresponding list of stream keys.
*/
protected abstract List<StreamKey> toStreamKeys(List<TrackKey> trackKeys);
}

View file

@ -22,47 +22,28 @@ import java.util.Collections;
import java.util.List;
/** A {@link DownloadHelper} for progressive streams. */
public final class ProgressiveDownloadHelper extends DownloadHelper {
private final Uri uri;
private final @Nullable String customCacheKey;
public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
public ProgressiveDownloadHelper(Uri uri) {
this(uri, null);
}
public ProgressiveDownloadHelper(Uri uri, @Nullable String customCacheKey) {
this.uri = uri;
this.customCacheKey = customCacheKey;
super(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey);
}
@Override
protected void prepareInternal() {
// Do nothing.
protected Void loadManifest(Uri uri) {
return null;
}
@Override
public int getPeriodCount() {
return 1;
protected TrackGroupArray[] getTrackGroupArrays(Void manifest) {
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
return TrackGroupArray.EMPTY;
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri,
/* keys= */ Collections.emptyList(),
customCacheKey,
data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey);
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
return Collections.emptyList();
}
}

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.dash.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.DownloadAction;
@ -31,74 +30,49 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for DASH streams. */
public final class DashDownloadHelper extends DownloadHelper {
public final class DashDownloadHelper extends DownloadHelper<DashManifest> {
private final Uri uri;
private final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull DashManifest manifest;
public DashDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri;
super(DownloadAction.TYPE_DASH, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected void prepareInternal() throws IOException {
protected DashManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
manifest =
ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
/** Returns the DASH manifest. Must not be called until after preparation completes. */
public DashManifest getManifest() {
Assertions.checkNotNull(manifest);
return manifest;
return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(manifest);
return manifest.getPeriodCount();
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(manifest);
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()];
for (int i = 0; i < trackGroups.length; i++) {
List<Representation> representations = adaptationSets.get(i).representations;
Format[] formats = new Format[representations.size()];
int representationsCount = representations.size();
for (int j = 0; j < representationsCount; j++) {
formats[j] = representations.get(j).format;
public TrackGroupArray[] getTrackGroupArrays(DashManifest manifest) {
int periodCount = manifest.getPeriodCount();
TrackGroupArray[] trackGroupArrays = new TrackGroupArray[periodCount];
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()];
for (int i = 0; i < trackGroups.length; i++) {
List<Representation> representations = adaptationSets.get(i).representations;
Format[] formats = new Format[representations.size()];
int representationsCount = representations.size();
for (int j = 0; j < representationsCount; j++) {
formats[j] = representations.get(j).format;
}
trackGroups[i] = new TrackGroup(formats);
}
trackGroups[i] = new TrackGroup(formats);
trackGroupArrays[periodIndex] = new TrackGroupArray(trackGroups);
}
return new TrackGroupArray(trackGroups);
return trackGroupArrays;
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_DASH, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null);
}
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
List<StreamKey> streamKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.hls.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.DownloadAction;
@ -36,46 +35,31 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for HLS streams. */
public final class HlsDownloadHelper extends DownloadHelper {
public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> {
private final Uri uri;
private final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull HlsPlaylist playlist;
private int[] renditionGroups;
public HlsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri;
super(DownloadAction.TYPE_HLS, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected void prepareInternal() throws IOException {
protected HlsPlaylist loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
playlist = ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
}
/** Returns the HLS playlist. Must not be called until after preparation completes. */
public HlsPlaylist getPlaylist() {
Assertions.checkNotNull(playlist);
return playlist;
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(playlist);
return 1;
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
protected TrackGroupArray[] getTrackGroupArrays(HlsPlaylist playlist) {
Assertions.checkNotNull(playlist);
if (playlist instanceof HlsMediaPlaylist) {
renditionGroups = new int[0];
return TrackGroupArray.EMPTY;
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
// TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction.
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
@ -94,24 +78,18 @@ public final class HlsDownloadHelper extends DownloadHelper {
renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE;
trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles));
}
return new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex));
return new TrackGroupArray[] {new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex))};
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
Assertions.checkNotNull(renditionGroups);
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_HLS,
uri,
toStreamKeys(trackKeys, renditionGroups),
/* customCacheKey= */ null,
data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_HLS, uri, /* customCacheKey= */ null);
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);
representationKeys.add(
new StreamKey(renditionGroups[trackKey.groupIndex], trackKey.trackIndex));
}
return representationKeys;
}
private static Format[] toFormats(List<HlsMasterPlaylist.HlsUrl> hlsUrls) {
@ -121,13 +99,4 @@ public final class HlsDownloadHelper extends DownloadHelper {
}
return formats;
}
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys, int[] groups) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);
representationKeys.add(new StreamKey(groups[trackKey.groupIndex], trackKey.trackIndex));
}
return representationKeys;
}
}

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
@ -28,67 +27,38 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for SmoothStreaming streams. */
public final class SsDownloadHelper extends DownloadHelper {
public final class SsDownloadHelper extends DownloadHelper<SsManifest> {
private final Uri uri;
private final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull SsManifest manifest;
public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri;
super(DownloadAction.TYPE_SS, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected void prepareInternal() throws IOException {
protected SsManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
manifest = ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
/** Returns the SmoothStreaming manifest. Must not be called until after preparation completes. */
public SsManifest getManifest() {
Assertions.checkNotNull(manifest);
return manifest;
return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(manifest);
return 1;
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(manifest);
protected TrackGroupArray[] getTrackGroupArrays(SsManifest manifest) {
SsManifest.StreamElement[] streamElements = manifest.streamElements;
TrackGroup[] trackGroups = new TrackGroup[streamElements.length];
for (int i = 0; i < streamElements.length; i++) {
trackGroups[i] = new TrackGroup(streamElements[i].formats);
}
return new TrackGroupArray(trackGroups);
return new TrackGroupArray[] {new TrackGroupArray(trackGroups)};
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_SS, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_SS, uri, /* customCacheKey= */ null);
}
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);