diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 905619c6f0..044bd8cc8a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -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 The manifest type. + */ +public abstract class DownloadHelper { /** 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 trackKeys); + public final DownloadAction getDownloadAction(@Nullable byte[] data, List 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 toStreamKeys(List trackKeys); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java index 6c1ceafd93..70587694c4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java @@ -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 { 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 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 toStreamKeys(List trackKeys) { + return Collections.emptyList(); } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java index 9c6a24d1b2..f4e43f4641 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java @@ -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 { - 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 adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; - TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()]; - for (int i = 0; i < trackGroups.length; i++) { - List 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 adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; + TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()]; + for (int i = 0; i < trackGroups.length; i++) { + List 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 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 toStreamKeys(List trackKeys) { + protected List toStreamKeys(List trackKeys) { List streamKeys = new ArrayList<>(trackKeys.size()); for (int i = 0; i < trackKeys.size(); i++) { TrackKey trackKey = trackKeys.get(i); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java index d4cbd8b638..c6ebe8e294 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java @@ -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 { - 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 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 toStreamKeys(List trackKeys) { + List 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 hlsUrls) { @@ -121,13 +99,4 @@ public final class HlsDownloadHelper extends DownloadHelper { } return formats; } - - private static List toStreamKeys(List trackKeys, int[] groups) { - List 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; - } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java index d7083400cd..154ac30ac6 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java @@ -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 { - 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 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 toStreamKeys(List trackKeys) { + protected List toStreamKeys(List trackKeys) { List representationKeys = new ArrayList<>(trackKeys.size()); for (int i = 0; i < trackKeys.size(); i++) { TrackKey trackKey = trackKeys.get(i);