mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Enable DownloadHelper to create DownloadRequest with byteRange
This change only enable the partial support for progressive stream. For now, creating `DownloadRequest` for partial adaptive media will result in an `IllegalStateException`. PiperOrigin-RevId: 729100584
This commit is contained in:
parent
daf8f9ff58
commit
a5ffae17c3
@ -16,13 +16,18 @@
|
|||||||
package androidx.media3.exoplayer.offline;
|
package androidx.media3.exoplayer.offline;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -33,11 +38,14 @@ import androidx.media3.common.TrackSelectionOverride;
|
|||||||
import androidx.media3.common.TrackSelectionParameters;
|
import androidx.media3.common.TrackSelectionParameters;
|
||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.NullableType;
|
import androidx.media3.common.util.NullableType;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
|
import androidx.media3.datasource.cache.Cache;
|
||||||
|
import androidx.media3.datasource.cache.CacheDataSource;
|
||||||
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList;
|
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
import androidx.media3.exoplayer.LoadingInfo;
|
import androidx.media3.exoplayer.LoadingInfo;
|
||||||
@ -51,6 +59,7 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
|||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
||||||
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
|
||||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||||
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
||||||
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
||||||
@ -65,7 +74,11 @@ import androidx.media3.exoplayer.upstream.Allocator;
|
|||||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||||
import androidx.media3.extractor.ExtractorsFactory;
|
import androidx.media3.extractor.ExtractorsFactory;
|
||||||
|
import androidx.media3.extractor.SeekMap;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -118,6 +131,20 @@ public final class DownloadHelper {
|
|||||||
return DEFAULT_TRACK_SELECTOR_PARAMETERS;
|
return DEFAULT_TRACK_SELECTOR_PARAMETERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@Target(TYPE_USE)
|
||||||
|
@IntDef({
|
||||||
|
MODE_NOT_PREPARE,
|
||||||
|
MODE_PREPARE_PROGRESSIVE_SOURCE,
|
||||||
|
MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS
|
||||||
|
})
|
||||||
|
private @interface Mode {}
|
||||||
|
|
||||||
|
private static final int MODE_NOT_PREPARE = 0;
|
||||||
|
private static final int MODE_PREPARE_PROGRESSIVE_SOURCE = 1;
|
||||||
|
private static final int MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS = 2;
|
||||||
|
|
||||||
/** A callback to be notified when the {@link DownloadHelper} is prepared. */
|
/** A callback to be notified when the {@link DownloadHelper} is prepared. */
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
||||||
@ -158,6 +185,28 @@ public final class DownloadHelper {
|
|||||||
/* drmSessionManager= */ null);
|
/* drmSessionManager= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link DownloadHelper} for the given media item.
|
||||||
|
*
|
||||||
|
* @param context The context.
|
||||||
|
* @param mediaItem A {@link MediaItem}.
|
||||||
|
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
||||||
|
* streams or the {@link SeekMap} for progressive streams. In the latter case, this has to be
|
||||||
|
* a {@link CacheDataSource.Factory} for the {@link Cache} into which downloads will be
|
||||||
|
* written.
|
||||||
|
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
||||||
|
* SmoothStreaming media items.
|
||||||
|
*/
|
||||||
|
public static DownloadHelper forMediaItem(
|
||||||
|
Context context, MediaItem mediaItem, DataSource.Factory dataSourceFactory) {
|
||||||
|
return forMediaItem(
|
||||||
|
mediaItem,
|
||||||
|
getDefaultTrackSelectorParameters(context),
|
||||||
|
/* renderersFactory= */ null,
|
||||||
|
dataSourceFactory,
|
||||||
|
/* drmSessionManager= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link DownloadHelper} for the given media item.
|
* Creates a {@link DownloadHelper} for the given media item.
|
||||||
*
|
*
|
||||||
@ -166,9 +215,10 @@ public final class DownloadHelper {
|
|||||||
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
|
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
|
||||||
* selected.
|
* selected.
|
||||||
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
||||||
* streams. This argument is required for adaptive streams and ignored for progressive
|
* streams or the {@link SeekMap} for progressive streams. This argument is required for
|
||||||
* streams.
|
* adaptive streams or when requesting partial downloads for progressive streams. In the
|
||||||
* @return A {@link DownloadHelper}.
|
* latter case, this has to be a {@link CacheDataSource.Factory} for the {@link Cache} into
|
||||||
|
* which downloads will be written.
|
||||||
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
||||||
* SmoothStreaming media items.
|
* SmoothStreaming media items.
|
||||||
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
|
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
|
||||||
@ -195,9 +245,10 @@ public final class DownloadHelper {
|
|||||||
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
||||||
* downloading.
|
* downloading.
|
||||||
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
||||||
* streams. This argument is required for adaptive streams and ignored for progressive
|
* streams or the {@link SeekMap} for progressive streams. This argument is required for
|
||||||
* streams.
|
* adaptive streams or when requesting partial downloads for progressive streams. In the
|
||||||
* @return A {@link DownloadHelper}.
|
* latter case, this has to be a {@link CacheDataSource.Factory} for the {@link Cache} into
|
||||||
|
* which downloads will be written.
|
||||||
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
||||||
* SmoothStreaming media items.
|
* SmoothStreaming media items.
|
||||||
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
|
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
|
||||||
@ -224,11 +275,12 @@ public final class DownloadHelper {
|
|||||||
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
||||||
* downloading.
|
* downloading.
|
||||||
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
||||||
* streams. This argument is required for adaptive streams and ignored for progressive
|
* streams or the {@link SeekMap} for progressive streams. This argument is required for
|
||||||
* streams.
|
* adaptive streams or when requesting partial downloads for progressive streams. In the
|
||||||
|
* latter case, this has to be a {@link CacheDataSource.Factory} for the {@link Cache} into
|
||||||
|
* which downloads will be written.
|
||||||
* @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
|
* @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
|
||||||
* tracks can be selected.
|
* tracks can be selected.
|
||||||
* @return A {@link DownloadHelper}.
|
|
||||||
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
|
||||||
* SmoothStreaming media items.
|
* SmoothStreaming media items.
|
||||||
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
|
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
|
||||||
@ -243,7 +295,7 @@ public final class DownloadHelper {
|
|||||||
Assertions.checkArgument(isProgressive || dataSourceFactory != null);
|
Assertions.checkArgument(isProgressive || dataSourceFactory != null);
|
||||||
return new DownloadHelper(
|
return new DownloadHelper(
|
||||||
mediaItem,
|
mediaItem,
|
||||||
isProgressive
|
isProgressive && dataSourceFactory == null
|
||||||
? null
|
? null
|
||||||
: createMediaSourceInternal(
|
: createMediaSourceInternal(
|
||||||
mediaItem, castNonNull(dataSourceFactory), drmSessionManager),
|
mediaItem, castNonNull(dataSourceFactory), drmSessionManager),
|
||||||
@ -281,8 +333,11 @@ public final class DownloadHelper {
|
|||||||
downloadRequest.toMediaItem(), dataSourceFactory, drmSessionManager);
|
downloadRequest.toMediaItem(), dataSourceFactory, drmSessionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "DownloadHelper";
|
||||||
|
|
||||||
private final MediaItem.LocalConfiguration localConfiguration;
|
private final MediaItem.LocalConfiguration localConfiguration;
|
||||||
@Nullable private final MediaSource mediaSource;
|
@Nullable private final MediaSource mediaSource;
|
||||||
|
private final @Mode int mode;
|
||||||
private final DefaultTrackSelector trackSelector;
|
private final DefaultTrackSelector trackSelector;
|
||||||
private final RendererCapabilitiesList rendererCapabilities;
|
private final RendererCapabilitiesList rendererCapabilities;
|
||||||
private final SparseIntArray scratchSet;
|
private final SparseIntArray scratchSet;
|
||||||
@ -290,6 +345,7 @@ public final class DownloadHelper {
|
|||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
|
|
||||||
private boolean isPreparedWithMedia;
|
private boolean isPreparedWithMedia;
|
||||||
|
private boolean areTracksSelected;
|
||||||
private @MonotonicNonNull Callback callback;
|
private @MonotonicNonNull Callback callback;
|
||||||
private @MonotonicNonNull MediaPreparer mediaPreparer;
|
private @MonotonicNonNull MediaPreparer mediaPreparer;
|
||||||
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
|
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
|
||||||
@ -316,6 +372,12 @@ public final class DownloadHelper {
|
|||||||
RendererCapabilitiesList rendererCapabilities) {
|
RendererCapabilitiesList rendererCapabilities) {
|
||||||
this.localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
this.localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
|
this.mode =
|
||||||
|
(mediaSource == null)
|
||||||
|
? MODE_NOT_PREPARE
|
||||||
|
: (mediaSource instanceof ProgressiveMediaSource)
|
||||||
|
? MODE_PREPARE_PROGRESSIVE_SOURCE
|
||||||
|
: MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS;
|
||||||
this.trackSelector =
|
this.trackSelector =
|
||||||
new DefaultTrackSelector(trackSelectionParameters, new DownloadTrackSelection.Factory());
|
new DefaultTrackSelector(trackSelectionParameters, new DownloadTrackSelection.Factory());
|
||||||
this.rendererCapabilities = rendererCapabilities;
|
this.rendererCapabilities = rendererCapabilities;
|
||||||
@ -334,8 +396,8 @@ public final class DownloadHelper {
|
|||||||
public void prepare(Callback callback) {
|
public void prepare(Callback callback) {
|
||||||
Assertions.checkState(this.callback == null);
|
Assertions.checkState(this.callback == null);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
if (mediaSource != null) {
|
if (mode != MODE_NOT_PREPARE) {
|
||||||
mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
|
mediaPreparer = new MediaPreparer(checkNotNull(mediaSource), /* downloadHelper= */ this);
|
||||||
} else {
|
} else {
|
||||||
callbackHandler.post(() -> callback.onPrepared(this));
|
callbackHandler.post(() -> callback.onPrepared(this));
|
||||||
}
|
}
|
||||||
@ -374,7 +436,7 @@ public final class DownloadHelper {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithMedia();
|
||||||
return trackGroupArrays.length;
|
return mediaPreparer.mediaPeriods.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -386,7 +448,7 @@ public final class DownloadHelper {
|
|||||||
* content.
|
* content.
|
||||||
*/
|
*/
|
||||||
public Tracks getTracks(int periodIndex) {
|
public Tracks getTracks(int periodIndex) {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
return TrackSelectionUtil.buildTracks(
|
return TrackSelectionUtil.buildTracks(
|
||||||
mappedTrackInfos[periodIndex], immutableTrackSelectionsByPeriodAndRenderer[periodIndex]);
|
mappedTrackInfos[periodIndex], immutableTrackSelectionsByPeriodAndRenderer[periodIndex]);
|
||||||
}
|
}
|
||||||
@ -402,7 +464,7 @@ public final class DownloadHelper {
|
|||||||
* content.
|
* content.
|
||||||
*/
|
*/
|
||||||
public TrackGroupArray getTrackGroups(int periodIndex) {
|
public TrackGroupArray getTrackGroups(int periodIndex) {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
return trackGroupArrays[periodIndex];
|
return trackGroupArrays[periodIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +476,7 @@ public final class DownloadHelper {
|
|||||||
* @return The {@link MappedTrackInfo} for the period.
|
* @return The {@link MappedTrackInfo} for the period.
|
||||||
*/
|
*/
|
||||||
public MappedTrackInfo getMappedTrackInfo(int periodIndex) {
|
public MappedTrackInfo getMappedTrackInfo(int periodIndex) {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
return mappedTrackInfos[periodIndex];
|
return mappedTrackInfos[periodIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,7 +489,7 @@ public final class DownloadHelper {
|
|||||||
* @return A list of selected {@link ExoTrackSelection track selections}.
|
* @return A list of selected {@link ExoTrackSelection track selections}.
|
||||||
*/
|
*/
|
||||||
public List<ExoTrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
|
public List<ExoTrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
|
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,7 +500,7 @@ public final class DownloadHelper {
|
|||||||
* @param periodIndex The period index for which track selections are cleared.
|
* @param periodIndex The period index for which track selections are cleared.
|
||||||
*/
|
*/
|
||||||
public void clearTrackSelections(int periodIndex) {
|
public void clearTrackSelections(int periodIndex) {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
for (int i = 0; i < rendererCapabilities.size(); i++) {
|
for (int i = 0; i < rendererCapabilities.size(); i++) {
|
||||||
trackSelectionsByPeriodAndRenderer[periodIndex][i].clear();
|
trackSelectionsByPeriodAndRenderer[periodIndex][i].clear();
|
||||||
}
|
}
|
||||||
@ -455,7 +517,7 @@ public final class DownloadHelper {
|
|||||||
public void replaceTrackSelections(
|
public void replaceTrackSelections(
|
||||||
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
||||||
try {
|
try {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
clearTrackSelections(periodIndex);
|
clearTrackSelections(periodIndex);
|
||||||
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
|
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
|
||||||
} catch (ExoPlaybackException e) {
|
} catch (ExoPlaybackException e) {
|
||||||
@ -474,7 +536,7 @@ public final class DownloadHelper {
|
|||||||
public void addTrackSelection(
|
public void addTrackSelection(
|
||||||
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
||||||
try {
|
try {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
|
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
|
||||||
} catch (ExoPlaybackException e) {
|
} catch (ExoPlaybackException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
@ -491,7 +553,7 @@ public final class DownloadHelper {
|
|||||||
*/
|
*/
|
||||||
public void addAudioLanguagesToSelection(String... languages) {
|
public void addAudioLanguagesToSelection(String... languages) {
|
||||||
try {
|
try {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
|
|
||||||
TrackSelectionParameters.Builder parametersBuilder =
|
TrackSelectionParameters.Builder parametersBuilder =
|
||||||
DEFAULT_TRACK_SELECTOR_PARAMETERS.buildUpon();
|
DEFAULT_TRACK_SELECTOR_PARAMETERS.buildUpon();
|
||||||
@ -531,7 +593,7 @@ public final class DownloadHelper {
|
|||||||
public void addTextLanguagesToSelection(
|
public void addTextLanguagesToSelection(
|
||||||
boolean selectUndeterminedTextLanguage, String... languages) {
|
boolean selectUndeterminedTextLanguage, String... languages) {
|
||||||
try {
|
try {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
|
|
||||||
TrackSelectionParameters.Builder parametersBuilder =
|
TrackSelectionParameters.Builder parametersBuilder =
|
||||||
DEFAULT_TRACK_SELECTOR_PARAMETERS.buildUpon();
|
DEFAULT_TRACK_SELECTOR_PARAMETERS.buildUpon();
|
||||||
@ -576,7 +638,7 @@ public final class DownloadHelper {
|
|||||||
DefaultTrackSelector.Parameters trackSelectorParameters,
|
DefaultTrackSelector.Parameters trackSelectorParameters,
|
||||||
List<SelectionOverride> overrides) {
|
List<SelectionOverride> overrides) {
|
||||||
try {
|
try {
|
||||||
assertPreparedWithMedia();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
DefaultTrackSelector.Parameters.Builder builder = trackSelectorParameters.buildUpon();
|
DefaultTrackSelector.Parameters.Builder builder = trackSelectorParameters.buildUpon();
|
||||||
for (int i = 0; i < mappedTrackInfos[periodIndex].getRendererCount(); i++) {
|
for (int i = 0; i < mappedTrackInfos[periodIndex].getRendererCount(); i++) {
|
||||||
builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex);
|
builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex);
|
||||||
@ -601,21 +663,72 @@ public final class DownloadHelper {
|
|||||||
* after preparation completes. The uri of the {@link DownloadRequest} will be used as content id.
|
* after preparation completes. The uri of the {@link DownloadRequest} will be used as content id.
|
||||||
*
|
*
|
||||||
* @param data Application provided data to store in {@link DownloadRequest#data}.
|
* @param data Application provided data to store in {@link DownloadRequest#data}.
|
||||||
* @return The built {@link DownloadRequest}.
|
|
||||||
*/
|
*/
|
||||||
public DownloadRequest getDownloadRequest(@Nullable byte[] data) {
|
public DownloadRequest getDownloadRequest(@Nullable byte[] data) {
|
||||||
return getDownloadRequest(localConfiguration.uri.toString(), data);
|
return getDownloadRequest(localConfiguration.uri.toString(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link DownloadRequest} for downloading the selected tracks and time range. Must not
|
||||||
|
* be called until preparation completes.
|
||||||
|
*
|
||||||
|
* <p>This method is only supported for progressive streams.
|
||||||
|
*
|
||||||
|
* @param data Application provided data to store in {@link DownloadRequest#data}.
|
||||||
|
* @param startPositionMs The start position (in milliseconds) of the media that download should
|
||||||
|
* cover from, or {@link C#TIME_UNSET} if the download should cover from the default start
|
||||||
|
* position.
|
||||||
|
* @param durationMs The end position (in milliseconds) of the media that download should cover
|
||||||
|
* to, or {@link C#TIME_UNSET} if the download should cover to the end of the media. If the
|
||||||
|
* {@code endPositionMs} is larger than the duration of the media, then the download will
|
||||||
|
* cover to the end of the media.
|
||||||
|
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
|
||||||
|
*/
|
||||||
|
public DownloadRequest getDownloadRequest(
|
||||||
|
@Nullable byte[] data, long startPositionMs, long durationMs) {
|
||||||
|
return getDownloadRequest(localConfiguration.uri.toString(), data, startPositionMs, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@link DownloadRequest} for downloading the selected tracks. Must not be called until
|
* Builds a {@link DownloadRequest} for downloading the selected tracks. Must not be called until
|
||||||
* after preparation completes.
|
* after preparation completes.
|
||||||
*
|
*
|
||||||
* @param id The unique content id.
|
* @param id The unique content id.
|
||||||
* @param data Application provided data to store in {@link DownloadRequest#data}.
|
* @param data Application provided data to store in {@link DownloadRequest#data}.
|
||||||
* @return The built {@link DownloadRequest}.
|
|
||||||
*/
|
*/
|
||||||
public DownloadRequest getDownloadRequest(String id, @Nullable byte[] data) {
|
public DownloadRequest getDownloadRequest(String id, @Nullable byte[] data) {
|
||||||
|
return getDownloadRequestBuilder(id, data).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link DownloadRequest} for downloading the selected tracks and time range. Must not
|
||||||
|
* be called until preparation completes.
|
||||||
|
*
|
||||||
|
* <p>This method is only supported for progressive streams.
|
||||||
|
*
|
||||||
|
* @param id The unique content id.
|
||||||
|
* @param data Application provided data to store in {@link DownloadRequest#data}.
|
||||||
|
* @param startPositionMs The start position (in milliseconds) of the media that download should
|
||||||
|
* cover from, or {@link C#TIME_UNSET} if the download should cover from the default start
|
||||||
|
* position.
|
||||||
|
* @param durationMs The duration (in milliseconds) of the media that download should cover, or
|
||||||
|
* {@link C#TIME_UNSET} if the download should cover to the end of the media. If the end
|
||||||
|
* position resolved from {@code startPositionMs} and {@code durationMs} is beyond the
|
||||||
|
* duration of the media, then the download will just cover to the end of the media.
|
||||||
|
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
|
||||||
|
*/
|
||||||
|
public DownloadRequest getDownloadRequest(
|
||||||
|
String id, @Nullable byte[] data, long startPositionMs, long durationMs) {
|
||||||
|
checkState(
|
||||||
|
mode == MODE_PREPARE_PROGRESSIVE_SOURCE,
|
||||||
|
"Partial download is only supported for progressive streams");
|
||||||
|
DownloadRequest.Builder builder = getDownloadRequestBuilder(id, data);
|
||||||
|
assertPreparedWithProgressiveSource();
|
||||||
|
populateDownloadRequestBuilderWithDownloadRange(builder, startPositionMs, durationMs);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DownloadRequest.Builder getDownloadRequestBuilder(String id, @Nullable byte[] data) {
|
||||||
DownloadRequest.Builder requestBuilder =
|
DownloadRequest.Builder requestBuilder =
|
||||||
new DownloadRequest.Builder(id, localConfiguration.uri)
|
new DownloadRequest.Builder(id, localConfiguration.uri)
|
||||||
.setMimeType(localConfiguration.mimeType)
|
.setMimeType(localConfiguration.mimeType)
|
||||||
@ -625,10 +738,8 @@ public final class DownloadHelper {
|
|||||||
: null)
|
: null)
|
||||||
.setCustomCacheKey(localConfiguration.customCacheKey)
|
.setCustomCacheKey(localConfiguration.customCacheKey)
|
||||||
.setData(data);
|
.setData(data);
|
||||||
if (mediaSource == null) {
|
if (mode == MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS) {
|
||||||
return requestBuilder.build();
|
assertPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
}
|
|
||||||
assertPreparedWithMedia();
|
|
||||||
List<StreamKey> streamKeys = new ArrayList<>();
|
List<StreamKey> streamKeys = new ArrayList<>();
|
||||||
List<ExoTrackSelection> allSelections = new ArrayList<>();
|
List<ExoTrackSelection> allSelections = new ArrayList<>();
|
||||||
int periodCount = trackSelectionsByPeriodAndRenderer.length;
|
int periodCount = trackSelectionsByPeriodAndRenderer.length;
|
||||||
@ -640,7 +751,62 @@ public final class DownloadHelper {
|
|||||||
}
|
}
|
||||||
streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
|
streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
|
||||||
}
|
}
|
||||||
return requestBuilder.setStreamKeys(streamKeys).build();
|
requestBuilder.setStreamKeys(streamKeys);
|
||||||
|
}
|
||||||
|
return requestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateDownloadRequestBuilderWithDownloadRange(
|
||||||
|
DownloadRequest.Builder requestBuilder, long startPositionMs, long durationMs) {
|
||||||
|
assertPreparedWithProgressiveSource();
|
||||||
|
Timeline timeline = mediaPreparer.timeline;
|
||||||
|
if (mediaPreparer.mediaPeriods.length > 1) {
|
||||||
|
Log.w(TAG, "Partial download is only supported for single period.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeline.Window window = new Timeline.Window();
|
||||||
|
Timeline.Period period = new Timeline.Period();
|
||||||
|
long periodStartPositionUs =
|
||||||
|
timeline.getPeriodPositionUs(
|
||||||
|
window,
|
||||||
|
period,
|
||||||
|
/* windowIndex= */ 0,
|
||||||
|
/* windowPositionUs= */ Util.msToUs(startPositionMs))
|
||||||
|
.second;
|
||||||
|
|
||||||
|
long periodEndPositionUs = C.TIME_UNSET;
|
||||||
|
if (durationMs != C.TIME_UNSET) {
|
||||||
|
periodEndPositionUs = periodStartPositionUs + Util.msToUs(durationMs);
|
||||||
|
if (period.durationUs != C.TIME_UNSET) {
|
||||||
|
periodEndPositionUs = min(periodEndPositionUs, period.durationUs - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeekMap should be available for prepared progressive media.
|
||||||
|
SeekMap seekMap = mediaPreparer.seekMap;
|
||||||
|
if (seekMap.isSeekable()) {
|
||||||
|
long byteRangeStartPositionOffset =
|
||||||
|
seekMap.getSeekPoints(periodStartPositionUs).first.position;
|
||||||
|
long byteRangeLength = C.LENGTH_UNSET;
|
||||||
|
if (periodEndPositionUs != C.TIME_UNSET) {
|
||||||
|
long byteRangeEndPositionOffset =
|
||||||
|
seekMap.getSeekPoints(periodEndPositionUs).second.position;
|
||||||
|
// When the start and end positions are after the last seek point, they will both have only
|
||||||
|
// that one mapped seek point. Then we should download from that seek point to the end of
|
||||||
|
// the media, otherwise nothing will be downloaded as the resolved length is 0.
|
||||||
|
boolean areStartAndEndPositionsAfterTheLastSeekPoint =
|
||||||
|
periodStartPositionUs != periodEndPositionUs
|
||||||
|
&& byteRangeStartPositionOffset == byteRangeEndPositionOffset;
|
||||||
|
byteRangeLength =
|
||||||
|
!areStartAndEndPositionsAfterTheLastSeekPoint
|
||||||
|
? byteRangeEndPositionOffset - byteRangeStartPositionOffset
|
||||||
|
: C.LENGTH_UNSET;
|
||||||
|
}
|
||||||
|
requestBuilder.setByteRange(byteRangeStartPositionOffset, byteRangeLength);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Cannot set download byte range for progressive stream that is unseekable");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
@ -670,6 +836,7 @@ public final class DownloadHelper {
|
|||||||
checkNotNull(mediaPreparer);
|
checkNotNull(mediaPreparer);
|
||||||
checkNotNull(mediaPreparer.mediaPeriods);
|
checkNotNull(mediaPreparer.mediaPeriods);
|
||||||
checkNotNull(mediaPreparer.timeline);
|
checkNotNull(mediaPreparer.timeline);
|
||||||
|
if (mode == MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS) {
|
||||||
int periodCount = mediaPreparer.mediaPeriods.length;
|
int periodCount = mediaPreparer.mediaPeriods.length;
|
||||||
int rendererCount = rendererCapabilities.size();
|
int rendererCount = rendererCapabilities.size();
|
||||||
trackSelectionsByPeriodAndRenderer =
|
trackSelectionsByPeriodAndRenderer =
|
||||||
@ -691,7 +858,12 @@ public final class DownloadHelper {
|
|||||||
trackSelector.onSelectionActivated(trackSelectorResult.info);
|
trackSelector.onSelectionActivated(trackSelectorResult.info);
|
||||||
mappedTrackInfos[i] = checkNotNull(trackSelector.getCurrentMappedTrackInfo());
|
mappedTrackInfos[i] = checkNotNull(trackSelector.getCurrentMappedTrackInfo());
|
||||||
}
|
}
|
||||||
setPreparedWithMedia();
|
setPreparedWithNonProgressiveSourceAndTracksSelected();
|
||||||
|
} else {
|
||||||
|
checkState(mode == MODE_PREPARE_PROGRESSIVE_SOURCE);
|
||||||
|
checkNotNull(mediaPreparer.seekMap);
|
||||||
|
setPreparedWithProgressiveSource();
|
||||||
|
}
|
||||||
checkNotNull(callbackHandler).post(() -> checkNotNull(callback).onPrepared(this));
|
checkNotNull(callbackHandler).post(() -> checkNotNull(callback).onPrepared(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,8 +880,26 @@ public final class DownloadHelper {
|
|||||||
"mediaPreparer.timeline",
|
"mediaPreparer.timeline",
|
||||||
"mediaPreparer.mediaPeriods"
|
"mediaPreparer.mediaPeriods"
|
||||||
})
|
})
|
||||||
private void setPreparedWithMedia() {
|
private void setPreparedWithNonProgressiveSourceAndTracksSelected() {
|
||||||
isPreparedWithMedia = true;
|
isPreparedWithMedia = true;
|
||||||
|
areTracksSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({
|
||||||
|
"mediaPreparer",
|
||||||
|
"mediaPreparer.timeline",
|
||||||
|
"mediaPreparer.seekMap",
|
||||||
|
"mediaPreparer.mediaPeriods"
|
||||||
|
})
|
||||||
|
private void setPreparedWithProgressiveSource() {
|
||||||
|
isPreparedWithMedia = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnsuresNonNull({"mediaPreparer", "mediaPreparer.timeline", "mediaPreparer.mediaPeriods"})
|
||||||
|
@SuppressWarnings("nullness:contracts.postcondition")
|
||||||
|
private void assertPreparedWithMedia() {
|
||||||
|
Assertions.checkState(mode != MODE_NOT_PREPARE);
|
||||||
|
Assertions.checkState(isPreparedWithMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnsuresNonNull({
|
@EnsuresNonNull({
|
||||||
@ -722,7 +912,21 @@ public final class DownloadHelper {
|
|||||||
"mediaPreparer.mediaPeriods"
|
"mediaPreparer.mediaPeriods"
|
||||||
})
|
})
|
||||||
@SuppressWarnings("nullness:contracts.postcondition")
|
@SuppressWarnings("nullness:contracts.postcondition")
|
||||||
private void assertPreparedWithMedia() {
|
private void assertPreparedWithNonProgressiveSourceAndTracksSelected() {
|
||||||
|
Assertions.checkState(mode == MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS);
|
||||||
|
Assertions.checkState(isPreparedWithMedia);
|
||||||
|
Assertions.checkState(areTracksSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnsuresNonNull({
|
||||||
|
"mediaPreparer",
|
||||||
|
"mediaPreparer.timeline",
|
||||||
|
"mediaPreparer.seekMap",
|
||||||
|
"mediaPreparer.mediaPeriods"
|
||||||
|
})
|
||||||
|
@SuppressWarnings("nullness:contracts.postcondition")
|
||||||
|
private void assertPreparedWithProgressiveSource() {
|
||||||
|
Assertions.checkState(mode == MODE_PREPARE_PROGRESSIVE_SOURCE);
|
||||||
Assertions.checkState(isPreparedWithMedia);
|
Assertions.checkState(isPreparedWithMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,8 +987,10 @@ public final class DownloadHelper {
|
|||||||
MediaItem mediaItem,
|
MediaItem mediaItem,
|
||||||
DataSource.Factory dataSourceFactory,
|
DataSource.Factory dataSourceFactory,
|
||||||
@Nullable DrmSessionManager drmSessionManager) {
|
@Nullable DrmSessionManager drmSessionManager) {
|
||||||
DefaultMediaSourceFactory mediaSourceFactory =
|
MediaSource.Factory mediaSourceFactory =
|
||||||
new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY);
|
isProgressive(checkNotNull(mediaItem.localConfiguration))
|
||||||
|
? new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
|
: new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY);
|
||||||
if (drmSessionManager != null) {
|
if (drmSessionManager != null) {
|
||||||
mediaSourceFactory.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
|
mediaSourceFactory.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
|
||||||
}
|
}
|
||||||
@ -798,7 +1004,10 @@ public final class DownloadHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class MediaPreparer
|
private static final class MediaPreparer
|
||||||
implements MediaSourceCaller, MediaPeriod.Callback, Handler.Callback {
|
implements MediaSourceCaller,
|
||||||
|
ProgressiveMediaSource.Listener,
|
||||||
|
MediaPeriod.Callback,
|
||||||
|
Handler.Callback {
|
||||||
|
|
||||||
private static final int MESSAGE_PREPARE_SOURCE = 1;
|
private static final int MESSAGE_PREPARE_SOURCE = 1;
|
||||||
private static final int MESSAGE_CHECK_FOR_FAILURE = 2;
|
private static final int MESSAGE_CHECK_FOR_FAILURE = 2;
|
||||||
@ -817,6 +1026,7 @@ public final class DownloadHelper {
|
|||||||
private final Handler mediaSourceHandler;
|
private final Handler mediaSourceHandler;
|
||||||
|
|
||||||
public @MonotonicNonNull Timeline timeline;
|
public @MonotonicNonNull Timeline timeline;
|
||||||
|
public @MonotonicNonNull SeekMap seekMap;
|
||||||
public MediaPeriod @MonotonicNonNull [] mediaPeriods;
|
public MediaPeriod @MonotonicNonNull [] mediaPeriods;
|
||||||
|
|
||||||
private boolean released;
|
private boolean released;
|
||||||
@ -850,6 +1060,9 @@ public final class DownloadHelper {
|
|||||||
public boolean handleMessage(Message msg) {
|
public boolean handleMessage(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MESSAGE_PREPARE_SOURCE:
|
case MESSAGE_PREPARE_SOURCE:
|
||||||
|
if (mediaSource instanceof ProgressiveMediaSource) {
|
||||||
|
((ProgressiveMediaSource) mediaSource).setListener(this);
|
||||||
|
}
|
||||||
mediaSource.prepareSource(
|
mediaSource.prepareSource(
|
||||||
/* caller= */ this, /* mediaTransferListener= */ null, PlayerId.UNSET);
|
/* caller= */ this, /* mediaTransferListener= */ null, PlayerId.UNSET);
|
||||||
mediaSourceHandler.sendEmptyMessage(MESSAGE_CHECK_FOR_FAILURE);
|
mediaSourceHandler.sendEmptyMessage(MESSAGE_CHECK_FOR_FAILURE);
|
||||||
@ -883,6 +1096,9 @@ public final class DownloadHelper {
|
|||||||
mediaSource.releasePeriod(period);
|
mediaSource.releasePeriod(period);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mediaSource instanceof ProgressiveMediaSource) {
|
||||||
|
((ProgressiveMediaSource) mediaSource).clearListener();
|
||||||
|
}
|
||||||
mediaSource.releaseSource(this);
|
mediaSource.releaseSource(this);
|
||||||
mediaSourceHandler.removeCallbacksAndMessages(null);
|
mediaSourceHandler.removeCallbacksAndMessages(null);
|
||||||
mediaSourceThread.quit();
|
mediaSourceThread.quit();
|
||||||
@ -924,6 +1140,13 @@ public final class DownloadHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProgressiveMediaSource.Listener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSeekMap(MediaSource source, SeekMap seekMap) {
|
||||||
|
this.seekMap = seekMap;
|
||||||
|
}
|
||||||
|
|
||||||
// MediaPeriod.Callback implementation.
|
// MediaPeriod.Callback implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,8 +18,10 @@ package androidx.media3.exoplayer.offline;
|
|||||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
|
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -447,6 +449,121 @@ public class DownloadHelperTest {
|
|||||||
new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 2, /* streamIndex= */ 0));
|
new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 2, /* streamIndex= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getDownloadRequest_createsDownloadRequestWithConcreteTimeRange_requestContainsConcreteByteRange()
|
||||||
|
throws Exception {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
DownloadHelper downloadHelper =
|
||||||
|
DownloadHelper.forMediaItem(
|
||||||
|
context,
|
||||||
|
MediaItem.fromUri("asset:///media/mp4/long_1080p_lowbitrate.mp4"),
|
||||||
|
new DefaultDataSource.Factory(context));
|
||||||
|
prepareDownloadHelper(downloadHelper);
|
||||||
|
|
||||||
|
DownloadRequest downloadRequest =
|
||||||
|
downloadHelper.getDownloadRequest(
|
||||||
|
/* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ 30000);
|
||||||
|
|
||||||
|
assertThat(downloadRequest.byteRange).isNotNull();
|
||||||
|
assertThat(downloadRequest.byteRange.offset).isAtLeast(0);
|
||||||
|
assertThat(downloadRequest.byteRange.length).isGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getDownloadRequest_createsDownloadRequestWithUnsetStartPosition_requestContainsConcreteByteRange()
|
||||||
|
throws Exception {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
DownloadHelper downloadHelper =
|
||||||
|
DownloadHelper.forMediaItem(
|
||||||
|
context,
|
||||||
|
MediaItem.fromUri("asset:///media/mp4/long_1080p_lowbitrate.mp4"),
|
||||||
|
new DefaultDataSource.Factory(context));
|
||||||
|
prepareDownloadHelper(downloadHelper);
|
||||||
|
|
||||||
|
DownloadRequest downloadRequest =
|
||||||
|
downloadHelper.getDownloadRequest(
|
||||||
|
/* data= */ null, /* startPositionMs= */ C.TIME_UNSET, /* durationMs= */ 30000);
|
||||||
|
|
||||||
|
assertThat(downloadRequest.byteRange).isNotNull();
|
||||||
|
assertThat(downloadRequest.byteRange.offset).isAtLeast(0);
|
||||||
|
assertThat(downloadRequest.byteRange.length).isGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDownloadRequest_createsDownloadRequestWithUnsetLength_requestContainsUnsetLength()
|
||||||
|
throws Exception {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
DownloadHelper downloadHelper =
|
||||||
|
DownloadHelper.forMediaItem(
|
||||||
|
context,
|
||||||
|
MediaItem.fromUri("asset:///media/mp4/long_1080p_lowbitrate.mp4"),
|
||||||
|
new DefaultDataSource.Factory(context));
|
||||||
|
prepareDownloadHelper(downloadHelper);
|
||||||
|
|
||||||
|
DownloadRequest downloadRequest =
|
||||||
|
downloadHelper.getDownloadRequest(
|
||||||
|
/* data= */ null, /* startPositionMs= */ 30000, /* durationMs= */ C.TIME_UNSET);
|
||||||
|
|
||||||
|
assertThat(downloadRequest.byteRange).isNotNull();
|
||||||
|
assertThat(downloadRequest.byteRange.offset).isAtLeast(0);
|
||||||
|
assertThat(downloadRequest.byteRange.length).isEqualTo(C.LENGTH_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getDownloadRequest_createsDownloadRequestForTooShortStreamWithTimeRange_requestContainsUnsetLength()
|
||||||
|
throws Exception {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
DownloadHelper downloadHelper =
|
||||||
|
DownloadHelper.forMediaItem(
|
||||||
|
context,
|
||||||
|
MediaItem.fromUri("asset:///media/mp4/sample.mp4"),
|
||||||
|
new DefaultDataSource.Factory(context));
|
||||||
|
prepareDownloadHelper(downloadHelper);
|
||||||
|
|
||||||
|
DownloadRequest downloadRequest =
|
||||||
|
downloadHelper.getDownloadRequest(
|
||||||
|
/* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ 30000);
|
||||||
|
|
||||||
|
assertThat(downloadRequest.byteRange).isNotNull();
|
||||||
|
assertThat(downloadRequest.byteRange.offset).isAtLeast(0);
|
||||||
|
assertThat(downloadRequest.byteRange.length).isEqualTo(C.LENGTH_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getDownloadRequest_createsDownloadRequestWithoutTimeRange_requestContainsNullByteRange()
|
||||||
|
throws Exception {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
DownloadHelper downloadHelper =
|
||||||
|
DownloadHelper.forMediaItem(
|
||||||
|
getApplicationContext(),
|
||||||
|
MediaItem.fromUri("asset:///media/mp4/sample.mp4"),
|
||||||
|
new DefaultDataSource.Factory(context));
|
||||||
|
prepareDownloadHelper(downloadHelper);
|
||||||
|
|
||||||
|
DownloadRequest downloadRequest = downloadHelper.getDownloadRequest(/* data= */ null);
|
||||||
|
|
||||||
|
assertThat(downloadRequest.byteRange).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getDownloadRequest_createDownloadRequestWithTimeRangeForNonProgressiveStream_throwsIllegalStateException()
|
||||||
|
throws Exception {
|
||||||
|
// We use this.downloadHelper as it was created with a TestMediaSource, thus the DownloadHelper
|
||||||
|
// will treat it as non-progressive.
|
||||||
|
prepareDownloadHelper(downloadHelper);
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() ->
|
||||||
|
downloadHelper.getDownloadRequest(
|
||||||
|
/* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ 10000));
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/androidx/media/issues/1224
|
// https://github.com/androidx/media/issues/1224
|
||||||
@Test
|
@Test
|
||||||
public void prepareThenRelease_renderersReleased() throws Exception {
|
public void prepareThenRelease_renderersReleased() throws Exception {
|
||||||
@ -460,10 +577,10 @@ public class DownloadHelperTest {
|
|||||||
new Renderer[] {textRenderer, audioRenderer, videoRenderer};
|
new Renderer[] {textRenderer, audioRenderer, videoRenderer};
|
||||||
DownloadHelper downloadHelper =
|
DownloadHelper downloadHelper =
|
||||||
DownloadHelper.forMediaItem(
|
DownloadHelper.forMediaItem(
|
||||||
testMediaItem,
|
MediaItem.fromUri("asset:///media/mp4/sample.mp4"),
|
||||||
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
|
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
|
||||||
renderersFactory,
|
renderersFactory,
|
||||||
new FakeDataSource.Factory());
|
new DefaultDataSource.Factory(getApplicationContext()));
|
||||||
|
|
||||||
prepareDownloadHelper(downloadHelper);
|
prepareDownloadHelper(downloadHelper);
|
||||||
downloadHelper.release();
|
downloadHelper.release();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user