Commit 0a8ae742 by tonihei Committed by Oliver Woodman

Update DownloadHelper to use MediaSource and MediaPeriod directly.

This requires to prepare the media source and the periods in a small helper similar
to the metadata retriever. It also gets rid of the need to have abstract protected
methods to load the manifest, to extract the track groups and to convert to stream keys,
as this can now be handled by the media period.

PiperOrigin-RevId: 231385590
parent 32b40502
...@@ -210,7 +210,7 @@ public class DownloadTracker implements DownloadManager.Listener { ...@@ -210,7 +210,7 @@ public class DownloadTracker implements DownloadManager.Listener {
DownloadService.startWithAction(context, DemoDownloadService.class, action, false); DownloadService.startWithAction(context, DemoDownloadService.class, action, false);
} }
private DownloadHelper<?> getDownloadHelper( private DownloadHelper getDownloadHelper(
Uri uri, String extension, RenderersFactory renderersFactory) { Uri uri, String extension, RenderersFactory renderersFactory) {
int type = Util.inferContentType(uri, extension); int type = Util.inferContentType(uri, extension);
switch (type) { switch (type) {
...@@ -231,10 +231,11 @@ public class DownloadTracker implements DownloadManager.Listener { ...@@ -231,10 +231,11 @@ public class DownloadTracker implements DownloadManager.Listener {
private final class StartDownloadDialogHelper private final class StartDownloadDialogHelper
implements DownloadHelper.Callback, implements DownloadHelper.Callback,
DialogInterface.OnClickListener, DialogInterface.OnClickListener,
DialogInterface.OnDismissListener,
View.OnClickListener, View.OnClickListener,
TrackSelectionView.DialogCallback { TrackSelectionView.DialogCallback {
private final DownloadHelper<?> downloadHelper; private final DownloadHelper downloadHelper;
private final String name; private final String name;
private final LayoutInflater dialogInflater; private final LayoutInflater dialogInflater;
private final AlertDialog dialog; private final AlertDialog dialog;
...@@ -244,20 +245,21 @@ public class DownloadTracker implements DownloadManager.Listener { ...@@ -244,20 +245,21 @@ public class DownloadTracker implements DownloadManager.Listener {
private DefaultTrackSelector.Parameters parameters; private DefaultTrackSelector.Parameters parameters;
private StartDownloadDialogHelper( private StartDownloadDialogHelper(
Activity activity, DownloadHelper<?> downloadHelper, String name) { Activity activity, DownloadHelper downloadHelper, String name) {
this.downloadHelper = downloadHelper; this.downloadHelper = downloadHelper;
this.name = name; this.name = name;
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setTitle(R.string.download_preparing) .setTitle(R.string.download_preparing)
.setPositiveButton(android.R.string.ok, this) .setPositiveButton(android.R.string.ok, /* listener= */ this)
.setNegativeButton(android.R.string.cancel, null); .setNegativeButton(android.R.string.cancel, /* listener= */ null);
// Inflate with the builder's context to ensure the correct style is used. // Inflate with the builder's context to ensure the correct style is used.
dialogInflater = LayoutInflater.from(builder.getContext()); dialogInflater = LayoutInflater.from(builder.getContext());
selectionList = (LinearLayout) dialogInflater.inflate(R.layout.start_download_dialog, null); selectionList = (LinearLayout) dialogInflater.inflate(R.layout.start_download_dialog, null);
builder.setView(selectionList); builder.setView(selectionList);
dialog = builder.create(); dialog = builder.create();
dialog.setOnDismissListener(/* listener= */ this);
dialog.show(); dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
...@@ -268,19 +270,17 @@ public class DownloadTracker implements DownloadManager.Listener { ...@@ -268,19 +270,17 @@ public class DownloadTracker implements DownloadManager.Listener {
// DownloadHelper.Callback implementation. // DownloadHelper.Callback implementation.
@Override @Override
public void onPrepared(DownloadHelper<?> helper) { public void onPrepared(DownloadHelper helper) {
if (helper.getPeriodCount() < 1) { if (helper.getPeriodCount() > 0) {
onPrepareError(downloadHelper, new IOException("Content is empty.")); mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
return; updateSelectionList();
} }
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
updateSelectionList();
dialog.setTitle(R.string.exo_download_description); dialog.setTitle(R.string.exo_download_description);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
} }
@Override @Override
public void onPrepareError(DownloadHelper<?> helper, IOException e) { public void onPrepareError(DownloadHelper helper, IOException e) {
Toast.makeText( Toast.makeText(
context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG) context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
.show(); .show();
...@@ -326,6 +326,13 @@ public class DownloadTracker implements DownloadManager.Listener { ...@@ -326,6 +326,13 @@ public class DownloadTracker implements DownloadManager.Listener {
startDownload(downloadAction); startDownload(downloadAction);
} }
// DialogInterface.OnDismissListener implementation.
@Override
public void onDismiss(DialogInterface dialog) {
downloadHelper.release();
}
// Internal methods. // Internal methods.
private void updateSelectionList() { private void updateSelectionList() {
......
...@@ -17,7 +17,9 @@ package com.google.android.exoplayer2.offline; ...@@ -17,7 +17,9 @@ package com.google.android.exoplayer2.offline;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -27,6 +29,8 @@ import com.google.android.exoplayer2.RenderersFactory; ...@@ -27,6 +29,8 @@ import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; 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.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
...@@ -36,7 +40,9 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Paramet ...@@ -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.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; 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.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -58,19 +64,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -58,19 +64,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* <p>A typical usage of DownloadHelper follows these steps: * <p>A typical usage of DownloadHelper follows these steps:
* *
* <ol> * <ol>
* <li>Construct the download helper with information about the {@link RenderersFactory renderers} * <li>Construct the download helper with the {@link MediaSource}, information about the {@link
* and {@link DefaultTrackSelector.Parameters parameters} for track selection. * RenderersFactory renderers} and {@link DefaultTrackSelector.Parameters parameters} for
* track selection.
* <li>Prepare the helper using {@link #prepare(Callback)} and wait for the callback. * <li>Prepare the helper using {@link #prepare(Callback)} and wait for the callback.
* <li>Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link * <li>Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link
* #getTrackSelections(int, int)}, and make adjustments using {@link * #getTrackSelections(int, int)}, and make adjustments using {@link
* #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, Parameters)} and {@link * #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, Parameters)} and {@link
* #addTrackSelection(int, Parameters)}. * #addTrackSelection(int, Parameters)}.
* <li>Create download actions for the selected track using {@link #getDownloadAction(byte[])}. * <li>Create a download action for the selected track using {@link #getDownloadAction(byte[])}.
* <li>Release the helper using {@link #release()}.
* </ol> * </ol>
*
* @param <T> The manifest type.
*/ */
public abstract class DownloadHelper<T> { public abstract class DownloadHelper {
/** /**
* The default parameters used for track selection for downloading. This default selects the * The default parameters used for track selection for downloading. This default selects the
...@@ -87,7 +93,7 @@ public abstract class DownloadHelper<T> { ...@@ -87,7 +93,7 @@ public abstract class DownloadHelper<T> {
* *
* @param helper The reporting {@link DownloadHelper}. * @param helper The reporting {@link DownloadHelper}.
*/ */
void onPrepared(DownloadHelper<?> helper); void onPrepared(DownloadHelper helper);
/** /**
* Called when preparation fails. * Called when preparation fails.
...@@ -95,18 +101,21 @@ public abstract class DownloadHelper<T> { ...@@ -95,18 +101,21 @@ public abstract class DownloadHelper<T> {
* @param helper The reporting {@link DownloadHelper}. * @param helper The reporting {@link DownloadHelper}.
* @param e The error. * @param e The error.
*/ */
void onPrepareError(DownloadHelper<?> helper, IOException e); void onPrepareError(DownloadHelper helper, IOException e);
} }
private final String downloadType; private final String downloadType;
private final Uri uri; private final Uri uri;
@Nullable private final String cacheKey; @Nullable private final String cacheKey;
@Nullable private final MediaSource mediaSource;
private final DefaultTrackSelector trackSelector; private final DefaultTrackSelector trackSelector;
private final RendererCapabilities[] rendererCapabilities; private final RendererCapabilities[] rendererCapabilities;
private final SparseIntArray scratchSet; private final SparseIntArray scratchSet;
private int currentTrackSelectionPeriodIndex; private boolean isPreparedWithMedia;
@Nullable private T manifest; private @MonotonicNonNull Callback callback;
private @MonotonicNonNull Handler callbackHandler;
private @MonotonicNonNull MediaPreparer mediaPreparer;
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays; private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos; private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
private List<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer; private List<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
...@@ -118,10 +127,12 @@ public abstract class DownloadHelper<T> { ...@@ -118,10 +127,12 @@ public abstract class DownloadHelper<T> {
* @param downloadType A download type. This value will be used as {@link DownloadAction#type}. * @param downloadType A download type. This value will be used as {@link DownloadAction#type}.
* @param uri A {@link Uri}. * @param uri A {@link Uri}.
* @param cacheKey An optional cache key. * @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 * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
* downloading. * downloading.
* @param renderersFactory The {@link RenderersFactory} creating the renderers for which tracks * @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 * @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers created by
* {@code renderersFactory}. * {@code renderersFactory}.
*/ */
...@@ -129,14 +140,19 @@ public abstract class DownloadHelper<T> { ...@@ -129,14 +140,19 @@ public abstract class DownloadHelper<T> {
String downloadType, String downloadType,
Uri uri, Uri uri,
@Nullable String cacheKey, @Nullable String cacheKey,
@Nullable MediaSource mediaSource,
DefaultTrackSelector.Parameters trackSelectorParameters, DefaultTrackSelector.Parameters trackSelectorParameters,
RenderersFactory renderersFactory, @Nullable RenderersFactory renderersFactory,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
this.downloadType = downloadType; this.downloadType = downloadType;
this.uri = uri; this.uri = uri;
this.cacheKey = cacheKey; this.cacheKey = cacheKey;
this.mediaSource = mediaSource;
this.trackSelector = new DefaultTrackSelector(new DownloadTrackSelection.Factory()); 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(); this.scratchSet = new SparseIntArray();
trackSelector.setParameters(trackSelectorParameters); trackSelector.setParameters(trackSelectorParameters);
trackSelector.init(/* listener= */ () -> {}, new DummyBandwidthMeter()); trackSelector.init(/* listener= */ () -> {}, new DummyBandwidthMeter());
...@@ -148,35 +164,38 @@ public abstract class DownloadHelper<T> { ...@@ -148,35 +164,38 @@ public abstract class DownloadHelper<T> {
* @param callback A callback to be notified when preparation completes or fails. The callback * @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 * 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. * 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) { 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 Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
new Thread( if (mediaSource != null) {
() -> { mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
try { } else {
manifest = loadManifest(uri); callbackHandler.post(() -> callback.onPrepared(this));
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();
} }
/** Returns the manifest. Must not be called until after preparation completes. */ /** Releases the helper and all resources it is holding. */
public final T getManifest() { public final void release() {
Assertions.checkNotNull(manifest); if (mediaPreparer != null) {
return manifest; 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<T> { ...@@ -184,7 +203,10 @@ public abstract class DownloadHelper<T> {
* preparation completes. * preparation completes.
*/ */
public final int getPeriodCount() { public final int getPeriodCount() {
Assertions.checkNotNull(trackGroupArrays); if (mediaSource == null) {
return 0;
}
assertPreparedWithMedia();
return trackGroupArrays.length; return trackGroupArrays.length;
} }
...@@ -199,7 +221,7 @@ public abstract class DownloadHelper<T> { ...@@ -199,7 +221,7 @@ public abstract class DownloadHelper<T> {
* content. * content.
*/ */
public final TrackGroupArray getTrackGroups(int periodIndex) { public final TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(trackGroupArrays); assertPreparedWithMedia();
return trackGroupArrays[periodIndex]; return trackGroupArrays[periodIndex];
} }
...@@ -211,7 +233,7 @@ public abstract class DownloadHelper<T> { ...@@ -211,7 +233,7 @@ public abstract class DownloadHelper<T> {
* @return The {@link MappedTrackInfo} for the period. * @return The {@link MappedTrackInfo} for the period.
*/ */
public final MappedTrackInfo getMappedTrackInfo(int periodIndex) { public final MappedTrackInfo getMappedTrackInfo(int periodIndex) {
Assertions.checkNotNull(mappedTrackInfos); assertPreparedWithMedia();
return mappedTrackInfos[periodIndex]; return mappedTrackInfos[periodIndex];
} }
...@@ -224,7 +246,7 @@ public abstract class DownloadHelper<T> { ...@@ -224,7 +246,7 @@ public abstract class DownloadHelper<T> {
* @return A list of selected {@link TrackSelection track selections}. * @return A list of selected {@link TrackSelection track selections}.
*/ */
public final List<TrackSelection> getTrackSelections(int periodIndex, int rendererIndex) { public final List<TrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
Assertions.checkNotNull(immutableTrackSelectionsByPeriodAndRenderer); assertPreparedWithMedia();
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex]; return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
} }
...@@ -235,7 +257,7 @@ public abstract class DownloadHelper<T> { ...@@ -235,7 +257,7 @@ public abstract class DownloadHelper<T> {
* @param periodIndex The period index for which track selections are cleared. * @param periodIndex The period index for which track selections are cleared.
*/ */
public final void clearTrackSelections(int periodIndex) { public final void clearTrackSelections(int periodIndex) {
Assertions.checkNotNull(trackSelectionsByPeriodAndRenderer); assertPreparedWithMedia();
for (int i = 0; i < rendererCapabilities.length; i++) { for (int i = 0; i < rendererCapabilities.length; i++) {
trackSelectionsByPeriodAndRenderer[periodIndex][i].clear(); trackSelectionsByPeriodAndRenderer[periodIndex][i].clear();
} }
...@@ -265,8 +287,7 @@ public abstract class DownloadHelper<T> { ...@@ -265,8 +287,7 @@ public abstract class DownloadHelper<T> {
*/ */
public final void addTrackSelection( public final void addTrackSelection(
int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) { int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
Assertions.checkNotNull(trackGroupArrays); assertPreparedWithMedia();
Assertions.checkNotNull(trackSelectionsByPeriodAndRenderer);
trackSelector.setParameters(trackSelectorParameters); trackSelector.setParameters(trackSelectorParameters);
runTrackSelection(periodIndex); runTrackSelection(periodIndex);
} }
...@@ -279,26 +300,21 @@ public abstract class DownloadHelper<T> { ...@@ -279,26 +300,21 @@ public abstract class DownloadHelper<T> {
* @return The built {@link DownloadAction}. * @return The built {@link DownloadAction}.
*/ */
public final DownloadAction getDownloadAction(@Nullable byte[] data) { public final DownloadAction getDownloadAction(@Nullable byte[] data) {
Assertions.checkNotNull(trackSelectionsByPeriodAndRenderer); if (mediaSource == null) {
Assertions.checkNotNull(trackGroupArrays); return DownloadAction.createDownloadAction(
downloadType, uri, /* keys= */ Collections.emptyList(), cacheKey, data);
}
assertPreparedWithMedia();
List<StreamKey> streamKeys = new ArrayList<>(); List<StreamKey> streamKeys = new ArrayList<>();
List<TrackSelection> allSelections = new ArrayList<>();
int periodCount = trackSelectionsByPeriodAndRenderer.length; int periodCount = trackSelectionsByPeriodAndRenderer.length;
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) { for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
allSelections.clear();
int rendererCount = trackSelectionsByPeriodAndRenderer[periodIndex].length; int rendererCount = trackSelectionsByPeriodAndRenderer[periodIndex].length;
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) { for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
List<TrackSelection> trackSelectionList = allSelections.addAll(trackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex]);
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));
}
}
} }
streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
} }
return DownloadAction.createDownloadAction(downloadType, uri, streamKeys, cacheKey, data); return DownloadAction.createDownloadAction(downloadType, uri, streamKeys, cacheKey, data);
} }
...@@ -312,36 +328,14 @@ public abstract class DownloadHelper<T> { ...@@ -312,36 +328,14 @@ public abstract class DownloadHelper<T> {
return DownloadAction.createRemoveAction(downloadType, uri, cacheKey); return DownloadAction.createRemoveAction(downloadType, uri, cacheKey);
} }
/** // Initialization of array of Lists.
* 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);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@EnsuresNonNull("trackSelectionsByPeriodAndRenderer") private void onMediaPrepared() {
private void initializeTrackSelectionLists(int periodCount, int rendererCount) { Assertions.checkNotNull(mediaPreparer);
Assertions.checkNotNull(mediaPreparer.mediaPeriods);
Assertions.checkNotNull(mediaPreparer.timeline);
int periodCount = mediaPreparer.mediaPeriods.length;
int rendererCount = rendererCapabilities.length;
trackSelectionsByPeriodAndRenderer = trackSelectionsByPeriodAndRenderer =
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount]; (List<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
immutableTrackSelectionsByPeriodAndRenderer = immutableTrackSelectionsByPeriodAndRenderer =
...@@ -353,6 +347,49 @@ public abstract class DownloadHelper<T> { ...@@ -353,6 +347,49 @@ public abstract class DownloadHelper<T> {
Collections.unmodifiableList(trackSelectionsByPeriodAndRenderer[i][j]); 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<T> { ...@@ -361,26 +398,27 @@ public abstract class DownloadHelper<T> {
*/ */
// Intentional reference comparison of track group instances. // Intentional reference comparison of track group instances.
@SuppressWarnings("ReferenceEquality") @SuppressWarnings("ReferenceEquality")
@RequiresNonNull({"trackGroupArrays", "trackSelectionsByPeriodAndRenderer"}) @RequiresNonNull({
"trackGroupArrays",
"trackSelectionsByPeriodAndRenderer",
"mediaPreparer",
"mediaPreparer.timeline"
})
private TrackSelectorResult runTrackSelection(int periodIndex) { 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 { try {
TrackSelectorResult trackSelectorResult = TrackSelectorResult trackSelectorResult =
trackSelector.selectTracks( trackSelector.selectTracks(
rendererCapabilities, rendererCapabilities,
trackGroupArrays[periodIndex], trackGroupArrays[periodIndex],
dummyMediaPeriodId, new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
dummyTimeline); mediaPreparer.timeline);
for (int i = 0; i < trackSelectorResult.length; i++) { for (int i = 0; i < trackSelectorResult.length; i++) {
TrackSelection newSelection = trackSelectorResult.selections.get(i); TrackSelection newSelection = trackSelectorResult.selections.get(i);
if (newSelection == null) { if (newSelection == null) {
continue; continue;
} }
List<TrackSelection> existingSelectionList = List<TrackSelection> existingSelectionList =
trackSelectionsByPeriodAndRenderer[currentTrackSelectionPeriodIndex][i]; trackSelectionsByPeriodAndRenderer[periodIndex][i];
boolean mergedWithExistingSelection = false; boolean mergedWithExistingSelection = false;
for (int j = 0; j < existingSelectionList.size(); j++) { for (int j = 0; j < existingSelectionList.size(); j++) {
TrackSelection existingSelection = existingSelectionList.get(j); TrackSelection existingSelection = existingSelectionList.get(j);
...@@ -414,6 +452,113 @@ public abstract class DownloadHelper<T> { ...@@ -414,6 +452,113 @@ public abstract class DownloadHelper<T> {
} }
} }
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 DownloadTrackSelection extends BaseTrackSelection {
private static final class Factory implements TrackSelection.Factory { private static final class Factory implements TrackSelection.Factory {
......
...@@ -17,11 +17,9 @@ package com.google.android.exoplayer2.offline; ...@@ -17,11 +17,9 @@ package com.google.android.exoplayer2.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.source.TrackGroupArray;
/** A {@link DownloadHelper} for progressive streams. */ /** A {@link DownloadHelper} for progressive streams. */
public final class ProgressiveDownloadHelper extends DownloadHelper<Void> { public final class ProgressiveDownloadHelper extends DownloadHelper {
/** /**
* Creates download helper for progressive streams. * Creates download helper for progressive streams.
...@@ -43,24 +41,9 @@ public final class ProgressiveDownloadHelper extends DownloadHelper<Void> { ...@@ -43,24 +41,9 @@ public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
DownloadAction.TYPE_PROGRESSIVE, DownloadAction.TYPE_PROGRESSIVE,
uri, uri,
cacheKey, cacheKey,
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS, /* mediaSource= */ null,
(handler, videoListener, audioListener, metadata, text, drm) -> new Renderer[0], /* trackSelectorParameters= */ DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
/* renderersFactory= */ null,
/* drmSessionManager= */ 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);
}
} }
...@@ -22,17 +22,28 @@ import com.google.android.exoplayer2.C; ...@@ -22,17 +22,28 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory; 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.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.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.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;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection; 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.ConditionVariable;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
...@@ -40,15 +51,19 @@ import org.junit.Before; ...@@ -40,15 +51,19 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowLooper;
/** Unit tests for {@link DownloadHelper}. */ /** Unit tests for {@link DownloadHelper}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class DownloadHelperTest { public class DownloadHelperTest {
private static final String TEST_DOWNLOAD_TYPE = "downloadType"; private static final String TEST_DOWNLOAD_TYPE = "downloadType";
private static final String TEST_CACHE_KEY = "cacheKey"; 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_LOW = createVideoFormat(/* bitrate= */ 200_000);
private static final Format VIDEO_FORMAT_HIGH = createVideoFormat(/* bitrate= */ 800_000); private static final Format VIDEO_FORMAT_HIGH = createVideoFormat(/* bitrate= */ 800_000);
...@@ -98,7 +113,7 @@ public class DownloadHelperTest { ...@@ -98,7 +113,7 @@ public class DownloadHelperTest {
public void getManifest_returnsManifest() throws Exception { public void getManifest_returnsManifest() throws Exception {
prepareDownloadHelper(downloadHelper); prepareDownloadHelper(downloadHelper);
ManifestType manifest = downloadHelper.getManifest(); Object manifest = downloadHelper.getManifest();
assertThat(manifest).isEqualTo(TEST_MANIFEST); assertThat(manifest).isEqualTo(TEST_MANIFEST);
} }
...@@ -337,12 +352,12 @@ public class DownloadHelperTest { ...@@ -337,12 +352,12 @@ public class DownloadHelperTest {
downloadHelper.prepare( downloadHelper.prepare(
new Callback() { new Callback() {
@Override @Override
public void onPrepared(DownloadHelper<?> helper) { public void onPrepared(DownloadHelper helper) {
preparedCondition.open(); preparedCondition.open();
} }
@Override @Override
public void onPrepareError(DownloadHelper<?> helper, IOException e) { public void onPrepareError(DownloadHelper helper, IOException e) {
prepareException.set(e); prepareException.set(e);
preparedCondition.open(); preparedCondition.open();
} }
...@@ -411,35 +426,52 @@ public class DownloadHelperTest { ...@@ -411,35 +426,52 @@ public class DownloadHelperTest {
assertThat(selectedTracksInGroup).isEqualTo(tracks); assertThat(selectedTracksInGroup).isEqualTo(tracks);
} }
private static final class ManifestType {} private static final class FakeDownloadHelper extends DownloadHelper {
private static final class FakeDownloadHelper extends DownloadHelper<ManifestType> {
public FakeDownloadHelper(Uri testUri, RenderersFactory renderersFactory) { public FakeDownloadHelper(Uri testUri, RenderersFactory renderersFactory) {
super( super(
TEST_DOWNLOAD_TYPE, TEST_DOWNLOAD_TYPE,
testUri, testUri,
TEST_CACHE_KEY, TEST_CACHE_KEY,
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS, DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
renderersFactory, renderersFactory,
/* drmSessionManager= */ null); /* drmSessionManager= */ null);
} }
}
@Override private static final class TestMediaSource extends FakeMediaSource {
protected ManifestType loadManifest(Uri uri) throws IOException {
return TEST_MANIFEST; public TestMediaSource() {
super(TEST_TIMELINE, TEST_MANIFEST);
} }
@Override @Override
protected TrackGroupArray[] getTrackGroupArrays(ManifestType manifest) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
assertThat(manifest).isEqualTo(TEST_MANIFEST); int periodIndex = TEST_TIMELINE.getIndexOfPeriod(id.periodUid);
return TRACK_GROUP_ARRAYS; return new FakeMediaPeriod(
TRACK_GROUP_ARRAYS[periodIndex],
new EventDispatcher()
.withParameters(/* windowIndex= */ 0, id, /* mediaTimeOffsetMs= */ 0)) {
@Override
public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
List<StreamKey> 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 @Override
protected StreamKey toStreamKey( public void releasePeriod(MediaPeriod mediaPeriod) {
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) { // Do nothing.
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
} }
} }
} }
...@@ -17,30 +17,17 @@ package com.google.android.exoplayer2.source.dash.offline; ...@@ -17,30 +17,17 @@ package com.google.android.exoplayer2.source.dash.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; 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.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper; import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.dash.DashMediaSource;
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.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource; 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. */ /** A {@link DownloadHelper} for DASH streams. */
public final class DashDownloadHelper extends DownloadHelper<DashManifest> { public final class DashDownloadHelper extends DownloadHelper {
private final DataSource.Factory manifestDataSourceFactory;
/** /**
* Creates a DASH download helper. * Creates a DASH download helper.
...@@ -85,42 +72,9 @@ public final class DashDownloadHelper extends DownloadHelper<DashManifest> { ...@@ -85,42 +72,9 @@ public final class DashDownloadHelper extends DownloadHelper<DashManifest> {
DownloadAction.TYPE_DASH, DownloadAction.TYPE_DASH,
uri, uri,
/* cacheKey= */ null, /* cacheKey= */ null,
new DashMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri),
trackSelectorParameters, trackSelectorParameters,
renderersFactory, renderersFactory,
drmSessionManager); 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<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);
}
trackGroupArrays[periodIndex] = new TrackGroupArray(trackGroups);
}
return trackGroupArrays;
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
} }
} }
...@@ -17,34 +17,17 @@ package com.google.android.exoplayer2.source.hls.offline; ...@@ -17,34 +17,17 @@ package com.google.android.exoplayer2.source.hls.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; 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.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper; import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
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.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource; 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. */ /** A {@link DownloadHelper} for HLS streams. */
public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> { public final class HlsDownloadHelper extends DownloadHelper {
private final DataSource.Factory manifestDataSourceFactory;
private int[] renditionGroups;
/** /**
* Creates a HLS download helper. * Creates a HLS download helper.
...@@ -89,56 +72,11 @@ public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> { ...@@ -89,56 +72,11 @@ public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> {
DownloadAction.TYPE_HLS, DownloadAction.TYPE_HLS,
uri, uri,
/* cacheKey= */ null, /* cacheKey= */ null,
new HlsMediaSource.Factory(manifestDataSourceFactory)
.setAllowChunklessPreparation(true)
.createMediaSource(uri),
trackSelectorParameters, trackSelectorParameters,
renderersFactory, renderersFactory,
drmSessionManager); 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<HlsMasterPlaylist.HlsUrl> hlsUrls) {
Format[] formats = new Format[hlsUrls.size()];
for (int i = 0; i < hlsUrls.size(); i++) {
formats[i] = hlsUrls.get(i).format;
}
return formats;
} }
} }
...@@ -17,27 +17,17 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline; ...@@ -17,27 +17,17 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper; import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
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.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException;
/** A {@link DownloadHelper} for SmoothStreaming streams. */ /** A {@link DownloadHelper} for SmoothStreaming streams. */
public final class SsDownloadHelper extends DownloadHelper<SsManifest> { public final class SsDownloadHelper extends DownloadHelper {
private final DataSource.Factory manifestDataSourceFactory;
/** /**
* Creates a SmoothStreaming download helper. * Creates a SmoothStreaming download helper.
...@@ -82,32 +72,9 @@ public final class SsDownloadHelper extends DownloadHelper<SsManifest> { ...@@ -82,32 +72,9 @@ public final class SsDownloadHelper extends DownloadHelper<SsManifest> {
DownloadAction.TYPE_SS, DownloadAction.TYPE_SS,
uri, uri,
/* cacheKey= */ null, /* cacheKey= */ null,
new SsMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri),
trackSelectorParameters, trackSelectorParameters,
renderersFactory, renderersFactory,
drmSessionManager); 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);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment