mirror of
https://github.com/samsonjs/media.git
synced 2026-04-08 11:45:51 +00:00
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
This commit is contained in:
parent
32b40502fc
commit
0a8ae74217
7 changed files with 322 additions and 296 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
* <p>A typical usage of DownloadHelper follows these steps:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Construct the download helper with information about the {@link RenderersFactory renderers}
|
||||
* and {@link DefaultTrackSelector.Parameters parameters} for track selection.
|
||||
* <li>Construct the download helper with the {@link MediaSource}, information about the {@link
|
||||
* RenderersFactory renderers} and {@link DefaultTrackSelector.Parameters parameters} for
|
||||
* track selection.
|
||||
* <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
|
||||
* #getTrackSelections(int, int)}, and make adjustments using {@link
|
||||
* #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, Parameters)} and {@link
|
||||
* #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>
|
||||
*
|
||||
* @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
|
||||
|
|
@ -87,7 +93,7 @@ public abstract class DownloadHelper<T> {
|
|||
*
|
||||
* @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<T> {
|
|||
* @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<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
|
||||
|
|
@ -118,10 +127,12 @@ public abstract class DownloadHelper<T> {
|
|||
* @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<T> {
|
|||
String downloadType,
|
||||
Uri uri,
|
||||
@Nullable String cacheKey,
|
||||
@Nullable MediaSource mediaSource,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters,
|
||||
RenderersFactory renderersFactory,
|
||||
@Nullable RenderersFactory renderersFactory,
|
||||
@Nullable DrmSessionManager<FrameworkMediaCrypto> 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<T> {
|
|||
* @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<T> {
|
|||
* 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<T> {
|
|||
* content.
|
||||
*/
|
||||
public final TrackGroupArray getTrackGroups(int periodIndex) {
|
||||
Assertions.checkNotNull(trackGroupArrays);
|
||||
assertPreparedWithMedia();
|
||||
return trackGroupArrays[periodIndex];
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +233,7 @@ public abstract class DownloadHelper<T> {
|
|||
* @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<T> {
|
|||
* @return A list of selected {@link TrackSelection track selections}.
|
||||
*/
|
||||
public final List<TrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
|
||||
Assertions.checkNotNull(immutableTrackSelectionsByPeriodAndRenderer);
|
||||
assertPreparedWithMedia();
|
||||
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +257,7 @@ public abstract class DownloadHelper<T> {
|
|||
* @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<T> {
|
|||
*/
|
||||
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<T> {
|
|||
* @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<StreamKey> streamKeys = new ArrayList<>();
|
||||
List<TrackSelection> 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<TrackSelection> 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<T> {
|
|||
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<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
|
||||
immutableTrackSelectionsByPeriodAndRenderer =
|
||||
|
|
@ -353,6 +347,49 @@ public abstract class DownloadHelper<T> {
|
|||
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> {
|
|||
*/
|
||||
// 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<TrackSelection> 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<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 Factory implements TrackSelection.Factory {
|
||||
|
|
|
|||
|
|
@ -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<Void> {
|
||||
public final class ProgressiveDownloadHelper extends DownloadHelper {
|
||||
|
||||
/**
|
||||
* Creates download helper for progressive streams.
|
||||
|
|
@ -43,24 +41,9 @@ public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ManifestType> {
|
||||
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<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
|
||||
protected StreamKey toStreamKey(
|
||||
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
|
||||
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<DashManifest> {
|
||||
|
||||
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<DashManifest> {
|
|||
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<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;
|
|||
|
||||
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<HlsPlaylist> {
|
||||
|
||||
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<HlsPlaylist> {
|
|||
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<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;
|
|||
|
||||
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<SsManifest> {
|
||||
|
||||
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<SsManifest> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue