diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
index 559bbcef0f..b0e56b9f92 100644
--- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
@@ -210,7 +210,7 @@ public class DownloadTracker implements DownloadManager.Listener {
DownloadService.startWithAction(context, DemoDownloadService.class, action, false);
}
- private DownloadHelper> getDownloadHelper(
+ private DownloadHelper getDownloadHelper(
Uri uri, String extension, RenderersFactory renderersFactory) {
int type = Util.inferContentType(uri, extension);
switch (type) {
@@ -231,10 +231,11 @@ public class DownloadTracker implements DownloadManager.Listener {
private final class StartDownloadDialogHelper
implements DownloadHelper.Callback,
DialogInterface.OnClickListener,
+ DialogInterface.OnDismissListener,
View.OnClickListener,
TrackSelectionView.DialogCallback {
- private final DownloadHelper> downloadHelper;
+ private final DownloadHelper downloadHelper;
private final String name;
private final LayoutInflater dialogInflater;
private final AlertDialog dialog;
@@ -244,20 +245,21 @@ public class DownloadTracker implements DownloadManager.Listener {
private DefaultTrackSelector.Parameters parameters;
private StartDownloadDialogHelper(
- Activity activity, DownloadHelper> downloadHelper, String name) {
+ Activity activity, DownloadHelper downloadHelper, String name) {
this.downloadHelper = downloadHelper;
this.name = name;
AlertDialog.Builder builder =
new AlertDialog.Builder(activity)
.setTitle(R.string.download_preparing)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, null);
+ .setPositiveButton(android.R.string.ok, /* listener= */ this)
+ .setNegativeButton(android.R.string.cancel, /* listener= */ null);
// Inflate with the builder's context to ensure the correct style is used.
dialogInflater = LayoutInflater.from(builder.getContext());
selectionList = (LinearLayout) dialogInflater.inflate(R.layout.start_download_dialog, null);
builder.setView(selectionList);
dialog = builder.create();
+ dialog.setOnDismissListener(/* listener= */ this);
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
@@ -268,19 +270,17 @@ public class DownloadTracker implements DownloadManager.Listener {
// DownloadHelper.Callback implementation.
@Override
- public void onPrepared(DownloadHelper> helper) {
- if (helper.getPeriodCount() < 1) {
- onPrepareError(downloadHelper, new IOException("Content is empty."));
- return;
+ public void onPrepared(DownloadHelper helper) {
+ if (helper.getPeriodCount() > 0) {
+ mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
+ updateSelectionList();
}
- mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
- updateSelectionList();
dialog.setTitle(R.string.exo_download_description);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
}
@Override
- public void onPrepareError(DownloadHelper> helper, IOException e) {
+ public void onPrepareError(DownloadHelper helper, IOException e) {
Toast.makeText(
context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
.show();
@@ -326,6 +326,13 @@ public class DownloadTracker implements DownloadManager.Listener {
startDownload(downloadAction);
}
+ // DialogInterface.OnDismissListener implementation.
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ downloadHelper.release();
+ }
+
// Internal methods.
private void updateSelectionList() {
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 e799aff4b2..0cd8081708 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
@@ -17,7 +17,9 @@ package com.google.android.exoplayer2.offline;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Message;
import android.support.annotation.Nullable;
import android.util.SparseIntArray;
import com.google.android.exoplayer2.C;
@@ -27,6 +29,8 @@ import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
+import com.google.android.exoplayer2.source.MediaPeriod;
+import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
@@ -36,7 +40,9 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Paramet
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
+import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
+import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
@@ -58,19 +64,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
*
A typical usage of DownloadHelper follows these steps:
*
*
- * - Construct the download helper with information about the {@link RenderersFactory renderers}
- * and {@link DefaultTrackSelector.Parameters parameters} for track selection.
+ *
- Construct the download helper with the {@link MediaSource}, information about the {@link
+ * RenderersFactory renderers} and {@link DefaultTrackSelector.Parameters parameters} for
+ * track selection.
*
- Prepare the helper using {@link #prepare(Callback)} and wait for the callback.
*
- Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link
* #getTrackSelections(int, int)}, and make adjustments using {@link
* #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, Parameters)} and {@link
* #addTrackSelection(int, Parameters)}.
- *
- Create download actions for the selected track using {@link #getDownloadAction(byte[])}.
+ *
- Create a download action for the selected track using {@link #getDownloadAction(byte[])}.
+ *
- Release the helper using {@link #release()}.
*
- *
- * @param The manifest type.
*/
-public abstract class DownloadHelper {
+public abstract class DownloadHelper {
/**
* The default parameters used for track selection for downloading. This default selects the
@@ -87,7 +93,7 @@ public abstract class DownloadHelper {
*
* @param helper The reporting {@link DownloadHelper}.
*/
- void onPrepared(DownloadHelper> helper);
+ void onPrepared(DownloadHelper helper);
/**
* Called when preparation fails.
@@ -95,18 +101,21 @@ public abstract class DownloadHelper {
* @param helper The reporting {@link DownloadHelper}.
* @param e The error.
*/
- void onPrepareError(DownloadHelper> helper, IOException e);
+ void onPrepareError(DownloadHelper helper, IOException e);
}
private final String downloadType;
private final Uri uri;
@Nullable private final String cacheKey;
+ @Nullable private final MediaSource mediaSource;
private final DefaultTrackSelector trackSelector;
private final RendererCapabilities[] rendererCapabilities;
private final SparseIntArray scratchSet;
- private int currentTrackSelectionPeriodIndex;
- @Nullable private T manifest;
+ private boolean isPreparedWithMedia;
+ private @MonotonicNonNull Callback callback;
+ private @MonotonicNonNull Handler callbackHandler;
+ private @MonotonicNonNull MediaPreparer mediaPreparer;
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
private List @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
@@ -118,10 +127,12 @@ public abstract class DownloadHelper {
* @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.
+ * @param mediaSource A {@link MediaSource} for which tracks are selected, or null if no track
+ * selection needs to be made.
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
* downloading.
* @param renderersFactory The {@link RenderersFactory} creating the renderers for which tracks
- * are selected.
+ * are selected, or null if no track selection needs to be made.
* @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers created by
* {@code renderersFactory}.
*/
@@ -129,14 +140,19 @@ public abstract class DownloadHelper {
String downloadType,
Uri uri,
@Nullable String cacheKey,
+ @Nullable MediaSource mediaSource,
DefaultTrackSelector.Parameters trackSelectorParameters,
- RenderersFactory renderersFactory,
+ @Nullable RenderersFactory renderersFactory,
@Nullable DrmSessionManager drmSessionManager) {
this.downloadType = downloadType;
this.uri = uri;
this.cacheKey = cacheKey;
+ this.mediaSource = mediaSource;
this.trackSelector = new DefaultTrackSelector(new DownloadTrackSelection.Factory());
- this.rendererCapabilities = Util.getRendererCapabilities(renderersFactory, drmSessionManager);
+ this.rendererCapabilities =
+ renderersFactory == null
+ ? new RendererCapabilities[0]
+ : Util.getRendererCapabilities(renderersFactory, drmSessionManager);
this.scratchSet = new SparseIntArray();
trackSelector.setParameters(trackSelectorParameters);
trackSelector.init(/* listener= */ () -> {}, new DummyBandwidthMeter());
@@ -148,35 +164,38 @@ public abstract class DownloadHelper {
* @param callback A callback to be notified when preparation completes or fails. The callback
* 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.
+ * @throws IllegalStateException If the download helper has already been prepared.
*/
public final void prepare(Callback callback) {
- Handler handler =
+ Assertions.checkState(this.callback == null);
+ this.callback = callback;
+ callbackHandler =
new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
- new Thread(
- () -> {
- try {
- manifest = loadManifest(uri);
- trackGroupArrays = getTrackGroupArrays(manifest);
- initializeTrackSelectionLists(trackGroupArrays.length, rendererCapabilities.length);
- mappedTrackInfos = new MappedTrackInfo[trackGroupArrays.length];
- for (int i = 0; i < trackGroupArrays.length; i++) {
- TrackSelectorResult trackSelectorResult = runTrackSelection(/* periodIndex= */ i);
- trackSelector.onSelectionActivated(trackSelectorResult.info);
- mappedTrackInfos[i] =
- Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
- }
- handler.post(() -> callback.onPrepared(DownloadHelper.this));
- } catch (final IOException e) {
- handler.post(() -> callback.onPrepareError(DownloadHelper.this, e));
- }
- })
- .start();
+ if (mediaSource != null) {
+ mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
+ } else {
+ callbackHandler.post(() -> callback.onPrepared(this));
+ }
}
- /** Returns the manifest. Must not be called until after preparation completes. */
- public final T getManifest() {
- Assertions.checkNotNull(manifest);
- return manifest;
+ /** Releases the helper and all resources it is holding. */
+ public final void release() {
+ if (mediaPreparer != null) {
+ mediaPreparer.release();
+ }
+ }
+
+ /**
+ * Returns the manifest, or null if no manifest is loaded. Must not be called until after
+ * preparation completes.
+ */
+ @Nullable
+ public final Object getManifest() {
+ if (mediaSource == null) {
+ return null;
+ }
+ assertPreparedWithMedia();
+ return mediaPreparer.manifest;
}
/**
@@ -184,7 +203,10 @@ public abstract class DownloadHelper {
* preparation completes.
*/
public final int getPeriodCount() {
- Assertions.checkNotNull(trackGroupArrays);
+ if (mediaSource == null) {
+ return 0;
+ }
+ assertPreparedWithMedia();
return trackGroupArrays.length;
}
@@ -199,7 +221,7 @@ public abstract class DownloadHelper {
* content.
*/
public final TrackGroupArray getTrackGroups(int periodIndex) {
- Assertions.checkNotNull(trackGroupArrays);
+ assertPreparedWithMedia();
return trackGroupArrays[periodIndex];
}
@@ -211,7 +233,7 @@ public abstract class DownloadHelper {
* @return The {@link MappedTrackInfo} for the period.
*/
public final MappedTrackInfo getMappedTrackInfo(int periodIndex) {
- Assertions.checkNotNull(mappedTrackInfos);
+ assertPreparedWithMedia();
return mappedTrackInfos[periodIndex];
}
@@ -224,7 +246,7 @@ public abstract class DownloadHelper {
* @return A list of selected {@link TrackSelection track selections}.
*/
public final List getTrackSelections(int periodIndex, int rendererIndex) {
- Assertions.checkNotNull(immutableTrackSelectionsByPeriodAndRenderer);
+ assertPreparedWithMedia();
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
}
@@ -235,7 +257,7 @@ public abstract class DownloadHelper {
* @param periodIndex The period index for which track selections are cleared.
*/
public final void clearTrackSelections(int periodIndex) {
- Assertions.checkNotNull(trackSelectionsByPeriodAndRenderer);
+ assertPreparedWithMedia();
for (int i = 0; i < rendererCapabilities.length; i++) {
trackSelectionsByPeriodAndRenderer[periodIndex][i].clear();
}
@@ -265,8 +287,7 @@ public abstract class DownloadHelper {
*/
public final void addTrackSelection(
int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
- Assertions.checkNotNull(trackGroupArrays);
- Assertions.checkNotNull(trackSelectionsByPeriodAndRenderer);
+ assertPreparedWithMedia();
trackSelector.setParameters(trackSelectorParameters);
runTrackSelection(periodIndex);
}
@@ -279,26 +300,21 @@ public abstract class DownloadHelper {
* @return The built {@link DownloadAction}.
*/
public final DownloadAction getDownloadAction(@Nullable byte[] data) {
- Assertions.checkNotNull(trackSelectionsByPeriodAndRenderer);
- Assertions.checkNotNull(trackGroupArrays);
+ if (mediaSource == null) {
+ return DownloadAction.createDownloadAction(
+ downloadType, uri, /* keys= */ Collections.emptyList(), cacheKey, data);
+ }
+ assertPreparedWithMedia();
List streamKeys = new ArrayList<>();
+ List allSelections = new ArrayList<>();
int periodCount = trackSelectionsByPeriodAndRenderer.length;
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
+ allSelections.clear();
int rendererCount = trackSelectionsByPeriodAndRenderer[periodIndex].length;
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
- List trackSelectionList =
- trackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
- for (int selectionIndex = 0; selectionIndex < trackSelectionList.size(); selectionIndex++) {
- TrackSelection trackSelection = trackSelectionList.get(selectionIndex);
- int trackGroupIndex =
- trackGroupArrays[periodIndex].indexOf(trackSelection.getTrackGroup());
- int trackCount = trackSelection.length();
- for (int trackListIndex = 0; trackListIndex < trackCount; trackListIndex++) {
- int trackIndex = trackSelection.getIndexInTrackGroup(trackListIndex);
- streamKeys.add(toStreamKey(periodIndex, trackGroupIndex, trackIndex));
- }
- }
+ allSelections.addAll(trackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex]);
}
+ streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
}
return DownloadAction.createDownloadAction(downloadType, uri, streamKeys, cacheKey, data);
}
@@ -312,36 +328,14 @@ public abstract class DownloadHelper {
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 track of a track group of a period to the corresponding {@link StreamKey}.
- *
- * @param periodIndex The index of the containing period.
- * @param trackGroupIndex The index of the containing track group within the period.
- * @param trackIndexInTrackGroup The index of the track within the track group.
- * @return The corresponding {@link StreamKey}.
- */
- protected abstract StreamKey toStreamKey(
- int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup);
-
+ // Initialization of array of Lists.
@SuppressWarnings("unchecked")
- @EnsuresNonNull("trackSelectionsByPeriodAndRenderer")
- private void initializeTrackSelectionLists(int periodCount, int rendererCount) {
+ private void onMediaPrepared() {
+ Assertions.checkNotNull(mediaPreparer);
+ Assertions.checkNotNull(mediaPreparer.mediaPeriods);
+ Assertions.checkNotNull(mediaPreparer.timeline);
+ int periodCount = mediaPreparer.mediaPeriods.length;
+ int rendererCount = rendererCapabilities.length;
trackSelectionsByPeriodAndRenderer =
(List[][]) new List>[periodCount][rendererCount];
immutableTrackSelectionsByPeriodAndRenderer =
@@ -353,6 +347,49 @@ public abstract class DownloadHelper {
Collections.unmodifiableList(trackSelectionsByPeriodAndRenderer[i][j]);
}
}
+ trackGroupArrays = new TrackGroupArray[periodCount];
+ mappedTrackInfos = new MappedTrackInfo[periodCount];
+ for (int i = 0; i < periodCount; i++) {
+ trackGroupArrays[i] = mediaPreparer.mediaPeriods[i].getTrackGroups();
+ TrackSelectorResult trackSelectorResult = runTrackSelection(/* periodIndex= */ i);
+ trackSelector.onSelectionActivated(trackSelectorResult.info);
+ mappedTrackInfos[i] = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
+ }
+ setPreparedWithMedia();
+ Assertions.checkNotNull(callbackHandler)
+ .post(() -> Assertions.checkNotNull(callback).onPrepared(this));
+ }
+
+ private void onMediaPreparationFailed(IOException error) {
+ Assertions.checkNotNull(callbackHandler)
+ .post(() -> Assertions.checkNotNull(callback).onPrepareError(this, error));
+ }
+
+ @RequiresNonNull({
+ "trackGroupArrays",
+ "mappedTrackInfos",
+ "trackSelectionsByPeriodAndRenderer",
+ "immutableTrackSelectionsByPeriodAndRenderer",
+ "mediaPreparer",
+ "mediaPreparer.timeline",
+ "mediaPreparer.mediaPeriods"
+ })
+ private void setPreparedWithMedia() {
+ isPreparedWithMedia = true;
+ }
+
+ @EnsuresNonNull({
+ "trackGroupArrays",
+ "mappedTrackInfos",
+ "trackSelectionsByPeriodAndRenderer",
+ "immutableTrackSelectionsByPeriodAndRenderer",
+ "mediaPreparer",
+ "mediaPreparer.timeline",
+ "mediaPreparer.mediaPeriods"
+ })
+ @SuppressWarnings("nullness:contracts.postcondition.not.satisfied")
+ private void assertPreparedWithMedia() {
+ Assertions.checkState(isPreparedWithMedia);
}
/**
@@ -361,26 +398,27 @@ public abstract class DownloadHelper {
*/
// Intentional reference comparison of track group instances.
@SuppressWarnings("ReferenceEquality")
- @RequiresNonNull({"trackGroupArrays", "trackSelectionsByPeriodAndRenderer"})
+ @RequiresNonNull({
+ "trackGroupArrays",
+ "trackSelectionsByPeriodAndRenderer",
+ "mediaPreparer",
+ "mediaPreparer.timeline"
+ })
private TrackSelectorResult runTrackSelection(int periodIndex) {
- // TODO: Use actual timeline and media period id.
- MediaPeriodId dummyMediaPeriodId = new MediaPeriodId(new Object());
- Timeline dummyTimeline = Timeline.EMPTY;
- currentTrackSelectionPeriodIndex = periodIndex;
try {
TrackSelectorResult trackSelectorResult =
trackSelector.selectTracks(
rendererCapabilities,
trackGroupArrays[periodIndex],
- dummyMediaPeriodId,
- dummyTimeline);
+ new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
+ mediaPreparer.timeline);
for (int i = 0; i < trackSelectorResult.length; i++) {
TrackSelection newSelection = trackSelectorResult.selections.get(i);
if (newSelection == null) {
continue;
}
List existingSelectionList =
- trackSelectionsByPeriodAndRenderer[currentTrackSelectionPeriodIndex][i];
+ trackSelectionsByPeriodAndRenderer[periodIndex][i];
boolean mergedWithExistingSelection = false;
for (int j = 0; j < existingSelectionList.size(); j++) {
TrackSelection existingSelection = existingSelectionList.get(j);
@@ -414,6 +452,113 @@ public abstract class DownloadHelper {
}
}
+ private static final class MediaPreparer
+ implements MediaSource.SourceInfoRefreshListener, MediaPeriod.Callback, Handler.Callback {
+
+ private static final int MESSAGE_PREPARE_SOURCE = 0;
+ private static final int MESSAGE_CHECK_FOR_FAILURE = 1;
+
+ private final MediaSource mediaSource;
+ private final DownloadHelper downloadHelper;
+ private final Allocator allocator;
+ private final HandlerThread mediaSourceThread;
+ private final Handler mediaSourceHandler;
+
+ @Nullable public Object manifest;
+ public @MonotonicNonNull Timeline timeline;
+ public MediaPeriod @MonotonicNonNull [] mediaPeriods;
+
+ private int pendingPreparations;
+
+ public MediaPreparer(MediaSource mediaSource, DownloadHelper downloadHelper) {
+ this.mediaSource = mediaSource;
+ this.downloadHelper = downloadHelper;
+ allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
+ mediaSourceThread = new HandlerThread("DownloadHelper");
+ mediaSourceThread.start();
+ mediaSourceHandler = Util.createHandler(mediaSourceThread.getLooper(), /* callback= */ this);
+ mediaSourceHandler.sendEmptyMessage(MESSAGE_PREPARE_SOURCE);
+ }
+
+ public void release() {
+ if (mediaPeriods != null) {
+ for (MediaPeriod mediaPeriod : mediaPeriods) {
+ mediaSource.releasePeriod(mediaPeriod);
+ }
+ }
+ mediaSource.releaseSource(this);
+ mediaSourceThread.quit();
+ }
+
+ // Handler.Callback
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_PREPARE_SOURCE:
+ mediaSource.prepareSource(/* listener= */ this, /* mediaTransferListener= */ null);
+ mediaSourceHandler.sendEmptyMessage(MESSAGE_CHECK_FOR_FAILURE);
+ return true;
+ case MESSAGE_CHECK_FOR_FAILURE:
+ try {
+ if (mediaPeriods == null) {
+ mediaSource.maybeThrowSourceInfoRefreshError();
+ } else {
+ for (MediaPeriod mediaPeriod : mediaPeriods) {
+ mediaPeriod.maybeThrowPrepareError();
+ }
+ }
+ mediaSourceHandler.sendEmptyMessageDelayed(
+ MESSAGE_CHECK_FOR_FAILURE, /* delayMillis= */ 100);
+ } catch (IOException e) {
+ downloadHelper.onMediaPreparationFailed(e);
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // MediaSource.SourceInfoRefreshListener implementation.
+
+ @Override
+ public void onSourceInfoRefreshed(
+ MediaSource source, Timeline timeline, @Nullable Object manifest) {
+ if (this.timeline != null) {
+ // Ignore dynamic updates.
+ return;
+ }
+ this.timeline = timeline;
+ this.manifest = manifest;
+ mediaPeriods = new MediaPeriod[timeline.getPeriodCount()];
+ pendingPreparations = mediaPeriods.length;
+ for (int i = 0; i < mediaPeriods.length; i++) {
+ mediaPeriods[i] =
+ mediaSource.createPeriod(
+ new MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ i)),
+ allocator,
+ /* startPositionUs= */ 0);
+ mediaPeriods[i].prepare(/* callback= */ this, /* positionUs= */ 0);
+ }
+ }
+
+ // MediaPeriod.Callback implementation.
+
+ @Override
+ public void onPrepared(MediaPeriod mediaPeriod) {
+ pendingPreparations--;
+ if (pendingPreparations == 0) {
+ mediaSourceHandler.removeMessages(MESSAGE_CHECK_FOR_FAILURE);
+ downloadHelper.onMediaPrepared();
+ }
+ }
+
+ @Override
+ public void onContinueLoadingRequested(MediaPeriod source) {
+ // Ignore.
+ }
+ }
+
private static final class DownloadTrackSelection extends BaseTrackSelection {
private static final class Factory implements TrackSelection.Factory {
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 2ec14368ca..1850eaebf2 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
@@ -17,11 +17,9 @@ package com.google.android.exoplayer2.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
-import com.google.android.exoplayer2.Renderer;
-import com.google.android.exoplayer2.source.TrackGroupArray;
/** A {@link DownloadHelper} for progressive streams. */
-public final class ProgressiveDownloadHelper extends DownloadHelper {
+public final class ProgressiveDownloadHelper extends DownloadHelper {
/**
* Creates download helper for progressive streams.
@@ -43,24 +41,9 @@ public final class ProgressiveDownloadHelper extends DownloadHelper {
DownloadAction.TYPE_PROGRESSIVE,
uri,
cacheKey,
- DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
- (handler, videoListener, audioListener, metadata, text, drm) -> new Renderer[0],
+ /* mediaSource= */ null,
+ /* trackSelectorParameters= */ DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
+ /* renderersFactory= */ null,
/* drmSessionManager= */ null);
}
-
- @Override
- protected Void loadManifest(Uri uri) {
- return null;
- }
-
- @Override
- protected TrackGroupArray[] getTrackGroupArrays(Void manifest) {
- return new TrackGroupArray[] {TrackGroupArray.EMPTY};
- }
-
- @Override
- protected StreamKey toStreamKey(
- int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
- return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
- }
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java
index 5f287d8685..e6cca02140 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java
@@ -22,17 +22,28 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory;
+import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.offline.DownloadHelper.Callback;
+import com.google.android.exoplayer2.source.MediaPeriod;
+import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
+import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
+import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
+import com.google.android.exoplayer2.testutil.FakeTimeline;
+import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
+import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
+import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -40,15 +51,19 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
/** Unit tests for {@link DownloadHelper}. */
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class DownloadHelperTest {
private static final String TEST_DOWNLOAD_TYPE = "downloadType";
private static final String TEST_CACHE_KEY = "cacheKey";
- private static final ManifestType TEST_MANIFEST = new ManifestType();
+ private static final Timeline TEST_TIMELINE =
+ new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ new Object()));
+ private static final Object TEST_MANIFEST = new Object();
private static final Format VIDEO_FORMAT_LOW = createVideoFormat(/* bitrate= */ 200_000);
private static final Format VIDEO_FORMAT_HIGH = createVideoFormat(/* bitrate= */ 800_000);
@@ -98,7 +113,7 @@ public class DownloadHelperTest {
public void getManifest_returnsManifest() throws Exception {
prepareDownloadHelper(downloadHelper);
- ManifestType manifest = downloadHelper.getManifest();
+ Object manifest = downloadHelper.getManifest();
assertThat(manifest).isEqualTo(TEST_MANIFEST);
}
@@ -337,12 +352,12 @@ public class DownloadHelperTest {
downloadHelper.prepare(
new Callback() {
@Override
- public void onPrepared(DownloadHelper> helper) {
+ public void onPrepared(DownloadHelper helper) {
preparedCondition.open();
}
@Override
- public void onPrepareError(DownloadHelper> helper, IOException e) {
+ public void onPrepareError(DownloadHelper helper, IOException e) {
prepareException.set(e);
preparedCondition.open();
}
@@ -411,35 +426,52 @@ public class DownloadHelperTest {
assertThat(selectedTracksInGroup).isEqualTo(tracks);
}
- private static final class ManifestType {}
-
- private static final class FakeDownloadHelper extends DownloadHelper {
+ private static final class FakeDownloadHelper extends DownloadHelper {
public FakeDownloadHelper(Uri testUri, RenderersFactory renderersFactory) {
super(
TEST_DOWNLOAD_TYPE,
testUri,
TEST_CACHE_KEY,
+ new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
renderersFactory,
/* drmSessionManager= */ null);
}
+ }
- @Override
- protected ManifestType loadManifest(Uri uri) throws IOException {
- return TEST_MANIFEST;
+ private static final class TestMediaSource extends FakeMediaSource {
+
+ public TestMediaSource() {
+ super(TEST_TIMELINE, TEST_MANIFEST);
}
@Override
- protected TrackGroupArray[] getTrackGroupArrays(ManifestType manifest) {
- assertThat(manifest).isEqualTo(TEST_MANIFEST);
- return TRACK_GROUP_ARRAYS;
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
+ int periodIndex = TEST_TIMELINE.getIndexOfPeriod(id.periodUid);
+ return new FakeMediaPeriod(
+ TRACK_GROUP_ARRAYS[periodIndex],
+ new EventDispatcher()
+ .withParameters(/* windowIndex= */ 0, id, /* mediaTimeOffsetMs= */ 0)) {
+ @Override
+ public List getStreamKeys(List trackSelections) {
+ List result = new ArrayList<>();
+ for (TrackSelection trackSelection : trackSelections) {
+ int groupIndex =
+ TRACK_GROUP_ARRAYS[periodIndex].indexOf(trackSelection.getTrackGroup());
+ for (int i = 0; i < trackSelection.length(); i++) {
+ result.add(
+ new StreamKey(periodIndex, groupIndex, trackSelection.getIndexInTrackGroup(i)));
+ }
+ }
+ return result;
+ }
+ };
}
@Override
- protected StreamKey toStreamKey(
- int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
- return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
+ public void releasePeriod(MediaPeriod mediaPeriod) {
+ // Do nothing.
}
}
}
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 f86e47ed3d..b611cf0d5f 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
@@ -17,30 +17,17 @@ 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.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
-import com.google.android.exoplayer2.offline.StreamKey;
-import com.google.android.exoplayer2.source.TrackGroup;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-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.DashMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.ParsingLoadable;
-import java.io.IOException;
-import java.util.List;
/** A {@link DownloadHelper} for DASH streams. */
-public final class DashDownloadHelper extends DownloadHelper {
-
- private final DataSource.Factory manifestDataSourceFactory;
+public final class DashDownloadHelper extends DownloadHelper {
/**
* Creates a DASH download helper.
@@ -85,42 +72,9 @@ public final class DashDownloadHelper extends DownloadHelper {
DownloadAction.TYPE_DASH,
uri,
/* cacheKey= */ null,
+ new DashMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri),
trackSelectorParameters,
renderersFactory,
drmSessionManager);
- this.manifestDataSourceFactory = manifestDataSourceFactory;
- }
-
- @Override
- protected DashManifest loadManifest(Uri uri) throws IOException {
- DataSource dataSource = manifestDataSourceFactory.createDataSource();
- return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
- }
-
- @Override
- 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);
- }
- trackGroupArrays[periodIndex] = new TrackGroupArray(trackGroups);
- }
- return trackGroupArrays;
- }
-
- @Override
- protected StreamKey toStreamKey(
- int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
- return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
}
}
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 e0f55aa738..ee6bbe333a 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
@@ -17,34 +17,17 @@ 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.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
-import com.google.android.exoplayer2.offline.StreamKey;
-import com.google.android.exoplayer2.source.TrackGroup;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-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.HlsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
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.Arrays;
-import java.util.List;
/** A {@link DownloadHelper} for HLS streams. */
-public final class HlsDownloadHelper extends DownloadHelper {
-
- private final DataSource.Factory manifestDataSourceFactory;
-
- private int[] renditionGroups;
+public final class HlsDownloadHelper extends DownloadHelper {
/**
* Creates a HLS download helper.
@@ -89,56 +72,11 @@ public final class HlsDownloadHelper extends DownloadHelper {
DownloadAction.TYPE_HLS,
uri,
/* cacheKey= */ null,
+ new HlsMediaSource.Factory(manifestDataSourceFactory)
+ .setAllowChunklessPreparation(true)
+ .createMediaSource(uri),
trackSelectorParameters,
renderersFactory,
drmSessionManager);
- this.manifestDataSourceFactory = manifestDataSourceFactory;
- }
-
- @Override
- protected HlsPlaylist loadManifest(Uri uri) throws IOException {
- DataSource dataSource = manifestDataSourceFactory.createDataSource();
- return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
- }
-
- @Override
- protected TrackGroupArray[] getTrackGroupArrays(HlsPlaylist playlist) {
- Assertions.checkNotNull(playlist);
- if (playlist instanceof HlsMediaPlaylist) {
- renditionGroups = new int[0];
- return new TrackGroupArray[] {TrackGroupArray.EMPTY};
- }
- // TODO: Generate track groups as in playback. Reverse the mapping in toStreamKey.
- HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
- TrackGroup[] trackGroups = new TrackGroup[3];
- renditionGroups = new int[3];
- int trackGroupIndex = 0;
- if (!masterPlaylist.variants.isEmpty()) {
- renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_VARIANT;
- trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.variants));
- }
- if (!masterPlaylist.audios.isEmpty()) {
- renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_AUDIO;
- trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.audios));
- }
- if (!masterPlaylist.subtitles.isEmpty()) {
- renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE;
- trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles));
- }
- return new TrackGroupArray[] {new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex))};
- }
-
- @Override
- protected StreamKey toStreamKey(
- int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
- return new StreamKey(renditionGroups[trackGroupIndex], trackIndexInTrackGroup);
- }
-
- private static Format[] toFormats(List hlsUrls) {
- Format[] formats = new Format[hlsUrls.size()];
- for (int i = 0; i < hlsUrls.size(); i++) {
- formats[i] = hlsUrls.get(i).format;
- }
- return formats;
}
}
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 b17768f202..f76fb4ee90 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
@@ -17,27 +17,17 @@ 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.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
-import com.google.android.exoplayer2.offline.StreamKey;
-import com.google.android.exoplayer2.source.TrackGroup;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-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.SsUtil;
+import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.ParsingLoadable;
-import java.io.IOException;
/** A {@link DownloadHelper} for SmoothStreaming streams. */
-public final class SsDownloadHelper extends DownloadHelper {
-
- private final DataSource.Factory manifestDataSourceFactory;
+public final class SsDownloadHelper extends DownloadHelper {
/**
* Creates a SmoothStreaming download helper.
@@ -82,32 +72,9 @@ public final class SsDownloadHelper extends DownloadHelper {
DownloadAction.TYPE_SS,
uri,
/* cacheKey= */ null,
+ new SsMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri),
trackSelectorParameters,
renderersFactory,
drmSessionManager);
- this.manifestDataSourceFactory = manifestDataSourceFactory;
- }
-
- @Override
- protected SsManifest loadManifest(Uri uri) throws IOException {
- DataSource dataSource = manifestDataSourceFactory.createDataSource();
- Uri fixedUri = SsUtil.fixManifestUri(uri);
- return ParsingLoadable.load(dataSource, new SsManifestParser(), fixedUri, C.DATA_TYPE_MANIFEST);
- }
-
- @Override
- 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[] {new TrackGroupArray(trackGroups)};
- }
-
- @Override
- protected StreamKey toStreamKey(
- int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
- return new StreamKey(trackGroupIndex, trackIndexInTrackGroup);
}
}