summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/offline/DownloadHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/offline/DownloadHelper.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/offline/DownloadHelper.java1174
1 files changed, 1174 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/offline/DownloadHelper.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/offline/DownloadHelper.java
new file mode 100644
index 0000000000..6070b3a80f
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/offline/DownloadHelper.java
@@ -0,0 +1,1174 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.offline;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.SparseIntArray;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlaybackException;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.RendererCapabilities;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.RenderersFactory;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Timeline;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSessionManager;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaPeriod;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSourceFactory;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.TrackGroup;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.TrackGroupArray;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk.MediaChunk;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.BaseTrackSelection;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelection;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelectorResult;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Allocator;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.BandwidthMeter;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource.Factory;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultAllocator;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.TransferListener;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
+
+/**
+ * A helper for initializing and removing downloads.
+ *
+ * <p>The helper extracts track information from the media, selects tracks for downloading, and
+ * creates {@link DownloadRequest download requests} based on the selected tracks.
+ *
+ * <p>A typical usage of DownloadHelper follows these steps:
+ *
+ * <ol>
+ * <li>Build the helper using one of the {@code forXXX} methods.
+ * <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 a download request for the selected track using {@link #getDownloadRequest(byte[])}.
+ * <li>Release the helper using {@link #release()}.
+ * </ol>
+ */
+public final class DownloadHelper {
+
+ /**
+ * Default track selection parameters for downloading, but without any {@link Context}
+ * constraints.
+ *
+ * <p>If possible, use {@link #getDefaultTrackSelectorParameters(Context)} instead.
+ *
+ * @see Parameters#DEFAULT_WITHOUT_CONTEXT
+ */
+ public static final Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT =
+ Parameters.DEFAULT_WITHOUT_CONTEXT.buildUpon().setForceHighestSupportedBitrate(true).build();
+
+ /**
+ * @deprecated This instance does not have {@link Context} constraints. Use {@link
+ * #getDefaultTrackSelectorParameters(Context)} instead.
+ */
+ @Deprecated
+ public static final Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT =
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT;
+
+ /**
+ * @deprecated This instance does not have {@link Context} constraints. Use {@link
+ * #getDefaultTrackSelectorParameters(Context)} instead.
+ */
+ @Deprecated
+ public static final DefaultTrackSelector.Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS =
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT;
+
+ /** Returns the default parameters used for track selection for downloading. */
+ public static DefaultTrackSelector.Parameters getDefaultTrackSelectorParameters(Context context) {
+ return Parameters.getDefaults(context)
+ .buildUpon()
+ .setForceHighestSupportedBitrate(true)
+ .build();
+ }
+
+ /** A callback to be notified when the {@link DownloadHelper} is prepared. */
+ public interface Callback {
+
+ /**
+ * Called when preparation completes.
+ *
+ * @param helper The reporting {@link DownloadHelper}.
+ */
+ void onPrepared(DownloadHelper helper);
+
+ /**
+ * Called when preparation fails.
+ *
+ * @param helper The reporting {@link DownloadHelper}.
+ * @param e The error.
+ */
+ void onPrepareError(DownloadHelper helper, IOException e);
+ }
+
+ /** Thrown at an attempt to download live content. */
+ public static class LiveContentUnsupportedException extends IOException {}
+
+ @Nullable
+ private static final Constructor<? extends MediaSourceFactory> DASH_FACTORY_CONSTRUCTOR =
+ getConstructor("com.google.android.exoplayer2.source.dash.DashMediaSource$Factory");
+
+ @Nullable
+ private static final Constructor<? extends MediaSourceFactory> SS_FACTORY_CONSTRUCTOR =
+ getConstructor("com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory");
+
+ @Nullable
+ private static final Constructor<? extends MediaSourceFactory> HLS_FACTORY_CONSTRUCTOR =
+ getConstructor("com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory");
+
+ /** @deprecated Use {@link #forProgressive(Context, Uri)} */
+ @Deprecated
+ @SuppressWarnings("deprecation")
+ public static DownloadHelper forProgressive(Uri uri) {
+ return forProgressive(uri, /* cacheKey= */ null);
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for progressive streams.
+ *
+ * @param context Any {@link Context}.
+ * @param uri A stream {@link Uri}.
+ * @return A {@link DownloadHelper} for progressive streams.
+ */
+ public static DownloadHelper forProgressive(Context context, Uri uri) {
+ return forProgressive(context, uri, /* cacheKey= */ null);
+ }
+
+ /** @deprecated Use {@link #forProgressive(Context, Uri, String)} */
+ @Deprecated
+ public static DownloadHelper forProgressive(Uri uri, @Nullable String cacheKey) {
+ return new DownloadHelper(
+ DownloadRequest.TYPE_PROGRESSIVE,
+ uri,
+ cacheKey,
+ /* mediaSource= */ null,
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT,
+ /* rendererCapabilities= */ new RendererCapabilities[0]);
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for progressive streams.
+ *
+ * @param context Any {@link Context}.
+ * @param uri A stream {@link Uri}.
+ * @param cacheKey An optional cache key.
+ * @return A {@link DownloadHelper} for progressive streams.
+ */
+ public static DownloadHelper forProgressive(Context context, Uri uri, @Nullable String cacheKey) {
+ return new DownloadHelper(
+ DownloadRequest.TYPE_PROGRESSIVE,
+ uri,
+ cacheKey,
+ /* mediaSource= */ null,
+ getDefaultTrackSelectorParameters(context),
+ /* rendererCapabilities= */ new RendererCapabilities[0]);
+ }
+
+ /** @deprecated Use {@link #forDash(Context, Uri, Factory, RenderersFactory)} */
+ @Deprecated
+ public static DownloadHelper forDash(
+ Uri uri, DataSource.Factory dataSourceFactory, RenderersFactory renderersFactory) {
+ return forDash(
+ uri,
+ dataSourceFactory,
+ renderersFactory,
+ /* drmSessionManager= */ null,
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT);
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for DASH streams.
+ *
+ * @param context Any {@link Context}.
+ * @param uri A manifest {@link Uri}.
+ * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest.
+ * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
+ * selected.
+ * @return A {@link DownloadHelper} for DASH streams.
+ * @throws IllegalStateException If the DASH module is missing.
+ */
+ public static DownloadHelper forDash(
+ Context context,
+ Uri uri,
+ DataSource.Factory dataSourceFactory,
+ RenderersFactory renderersFactory) {
+ return forDash(
+ uri,
+ dataSourceFactory,
+ renderersFactory,
+ /* drmSessionManager= */ null,
+ getDefaultTrackSelectorParameters(context));
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for DASH streams.
+ *
+ * @param uri A manifest {@link Uri}.
+ * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest.
+ * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
+ * selected.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
+ * tracks can be selected.
+ * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
+ * downloading.
+ * @return A {@link DownloadHelper} for DASH streams.
+ * @throws IllegalStateException If the DASH module is missing.
+ */
+ public static DownloadHelper forDash(
+ Uri uri,
+ DataSource.Factory dataSourceFactory,
+ RenderersFactory renderersFactory,
+ @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
+ DefaultTrackSelector.Parameters trackSelectorParameters) {
+ return new DownloadHelper(
+ DownloadRequest.TYPE_DASH,
+ uri,
+ /* cacheKey= */ null,
+ createMediaSourceInternal(
+ DASH_FACTORY_CONSTRUCTOR,
+ uri,
+ dataSourceFactory,
+ drmSessionManager,
+ /* streamKeys= */ null),
+ trackSelectorParameters,
+ Util.getRendererCapabilities(renderersFactory));
+ }
+
+ /** @deprecated Use {@link #forHls(Context, Uri, Factory, RenderersFactory)} */
+ @Deprecated
+ public static DownloadHelper forHls(
+ Uri uri, DataSource.Factory dataSourceFactory, RenderersFactory renderersFactory) {
+ return forHls(
+ uri,
+ dataSourceFactory,
+ renderersFactory,
+ /* drmSessionManager= */ null,
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT);
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for HLS streams.
+ *
+ * @param context Any {@link Context}.
+ * @param uri A playlist {@link Uri}.
+ * @param dataSourceFactory A {@link DataSource.Factory} used to load the playlist.
+ * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
+ * selected.
+ * @return A {@link DownloadHelper} for HLS streams.
+ * @throws IllegalStateException If the HLS module is missing.
+ */
+ public static DownloadHelper forHls(
+ Context context,
+ Uri uri,
+ DataSource.Factory dataSourceFactory,
+ RenderersFactory renderersFactory) {
+ return forHls(
+ uri,
+ dataSourceFactory,
+ renderersFactory,
+ /* drmSessionManager= */ null,
+ getDefaultTrackSelectorParameters(context));
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for HLS streams.
+ *
+ * @param uri A playlist {@link Uri}.
+ * @param dataSourceFactory A {@link DataSource.Factory} used to load the playlist.
+ * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
+ * selected.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
+ * tracks can be selected.
+ * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
+ * downloading.
+ * @return A {@link DownloadHelper} for HLS streams.
+ * @throws IllegalStateException If the HLS module is missing.
+ */
+ public static DownloadHelper forHls(
+ Uri uri,
+ DataSource.Factory dataSourceFactory,
+ RenderersFactory renderersFactory,
+ @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
+ DefaultTrackSelector.Parameters trackSelectorParameters) {
+ return new DownloadHelper(
+ DownloadRequest.TYPE_HLS,
+ uri,
+ /* cacheKey= */ null,
+ createMediaSourceInternal(
+ HLS_FACTORY_CONSTRUCTOR,
+ uri,
+ dataSourceFactory,
+ drmSessionManager,
+ /* streamKeys= */ null),
+ trackSelectorParameters,
+ Util.getRendererCapabilities(renderersFactory));
+ }
+
+ /** @deprecated Use {@link #forSmoothStreaming(Context, Uri, Factory, RenderersFactory)} */
+ @Deprecated
+ public static DownloadHelper forSmoothStreaming(
+ Uri uri, DataSource.Factory dataSourceFactory, RenderersFactory renderersFactory) {
+ return forSmoothStreaming(
+ uri,
+ dataSourceFactory,
+ renderersFactory,
+ /* drmSessionManager= */ null,
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT);
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for SmoothStreaming streams.
+ *
+ * @param context Any {@link Context}.
+ * @param uri A manifest {@link Uri}.
+ * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest.
+ * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
+ * selected.
+ * @return A {@link DownloadHelper} for SmoothStreaming streams.
+ * @throws IllegalStateException If the SmoothStreaming module is missing.
+ */
+ public static DownloadHelper forSmoothStreaming(
+ Context context,
+ Uri uri,
+ DataSource.Factory dataSourceFactory,
+ RenderersFactory renderersFactory) {
+ return forSmoothStreaming(
+ uri,
+ dataSourceFactory,
+ renderersFactory,
+ /* drmSessionManager= */ null,
+ getDefaultTrackSelectorParameters(context));
+ }
+
+ /**
+ * Creates a {@link DownloadHelper} for SmoothStreaming streams.
+ *
+ * @param uri A manifest {@link Uri}.
+ * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest.
+ * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
+ * selected.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
+ * tracks can be selected.
+ * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
+ * downloading.
+ * @return A {@link DownloadHelper} for SmoothStreaming streams.
+ * @throws IllegalStateException If the SmoothStreaming module is missing.
+ */
+ public static DownloadHelper forSmoothStreaming(
+ Uri uri,
+ DataSource.Factory dataSourceFactory,
+ RenderersFactory renderersFactory,
+ @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
+ DefaultTrackSelector.Parameters trackSelectorParameters) {
+ return new DownloadHelper(
+ DownloadRequest.TYPE_SS,
+ uri,
+ /* cacheKey= */ null,
+ createMediaSourceInternal(
+ SS_FACTORY_CONSTRUCTOR,
+ uri,
+ dataSourceFactory,
+ drmSessionManager,
+ /* streamKeys= */ null),
+ trackSelectorParameters,
+ Util.getRendererCapabilities(renderersFactory));
+ }
+
+ /**
+ * Equivalent to {@link #createMediaSource(DownloadRequest, Factory, DrmSessionManager)
+ * createMediaSource(downloadRequest, dataSourceFactory, null)}.
+ */
+ public static MediaSource createMediaSource(
+ DownloadRequest downloadRequest, DataSource.Factory dataSourceFactory) {
+ return createMediaSource(downloadRequest, dataSourceFactory, /* drmSessionManager= */ null);
+ }
+
+ /**
+ * Utility method to create a {@link MediaSource} that only exposes the tracks defined in {@code
+ * downloadRequest}.
+ *
+ * @param downloadRequest A {@link DownloadRequest}.
+ * @param dataSourceFactory A factory for {@link DataSource}s to read the media.
+ * @param drmSessionManager An optional {@link DrmSessionManager} to be passed to the {@link
+ * MediaSource}.
+ * @return A {@link MediaSource} that only exposes the tracks defined in {@code downloadRequest}.
+ */
+ public static MediaSource createMediaSource(
+ DownloadRequest downloadRequest,
+ DataSource.Factory dataSourceFactory,
+ @Nullable DrmSessionManager<?> drmSessionManager) {
+ @Nullable Constructor<? extends MediaSourceFactory> constructor;
+ switch (downloadRequest.type) {
+ case DownloadRequest.TYPE_DASH:
+ constructor = DASH_FACTORY_CONSTRUCTOR;
+ break;
+ case DownloadRequest.TYPE_SS:
+ constructor = SS_FACTORY_CONSTRUCTOR;
+ break;
+ case DownloadRequest.TYPE_HLS:
+ constructor = HLS_FACTORY_CONSTRUCTOR;
+ break;
+ case DownloadRequest.TYPE_PROGRESSIVE:
+ return new ProgressiveMediaSource.Factory(dataSourceFactory)
+ .setCustomCacheKey(downloadRequest.customCacheKey)
+ .createMediaSource(downloadRequest.uri);
+ default:
+ throw new IllegalStateException("Unsupported type: " + downloadRequest.type);
+ }
+ return createMediaSourceInternal(
+ constructor,
+ downloadRequest.uri,
+ dataSourceFactory,
+ drmSessionManager,
+ downloadRequest.streamKeys);
+ }
+
+ 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 final Handler callbackHandler;
+ private final Timeline.Window window;
+
+ private boolean isPreparedWithMedia;
+ private @MonotonicNonNull Callback callback;
+ private @MonotonicNonNull MediaPreparer mediaPreparer;
+ private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
+ private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
+ private List<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
+ private List<TrackSelection> @MonotonicNonNull [][] immutableTrackSelectionsByPeriodAndRenderer;
+
+ /**
+ * Creates download helper.
+ *
+ * @param downloadType A download type. This value will be used as {@link DownloadRequest#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 rendererCapabilities The {@link RendererCapabilities} of the renderers for which tracks
+ * are selected.
+ */
+ public DownloadHelper(
+ String downloadType,
+ Uri uri,
+ @Nullable String cacheKey,
+ @Nullable MediaSource mediaSource,
+ DefaultTrackSelector.Parameters trackSelectorParameters,
+ RendererCapabilities[] rendererCapabilities) {
+ this.downloadType = downloadType;
+ this.uri = uri;
+ this.cacheKey = cacheKey;
+ this.mediaSource = mediaSource;
+ this.trackSelector =
+ new DefaultTrackSelector(trackSelectorParameters, new DownloadTrackSelection.Factory());
+ this.rendererCapabilities = rendererCapabilities;
+ this.scratchSet = new SparseIntArray();
+ trackSelector.init(/* listener= */ () -> {}, new DummyBandwidthMeter());
+ callbackHandler = new Handler(Util.getLooper());
+ window = new Timeline.Window();
+ }
+
+ /**
+ * Initializes the helper for starting a download.
+ *
+ * @param callback A callback to be notified when preparation completes or fails.
+ * @throws IllegalStateException If the download helper has already been prepared.
+ */
+ public void prepare(Callback callback) {
+ Assertions.checkState(this.callback == null);
+ this.callback = callback;
+ if (mediaSource != null) {
+ mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
+ } else {
+ callbackHandler.post(() -> callback.onPrepared(this));
+ }
+ }
+
+ /** Releases the helper and all resources it is holding. */
+ public 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 Object getManifest() {
+ if (mediaSource == null) {
+ return null;
+ }
+ assertPreparedWithMedia();
+ return mediaPreparer.timeline.getWindowCount() > 0
+ ? mediaPreparer.timeline.getWindow(/* windowIndex= */ 0, window).manifest
+ : null;
+ }
+
+ /**
+ * Returns the number of periods for which media is available. Must not be called until after
+ * preparation completes.
+ */
+ public int getPeriodCount() {
+ if (mediaSource == null) {
+ return 0;
+ }
+ assertPreparedWithMedia();
+ return trackGroupArrays.length;
+ }
+
+ /**
+ * Returns the track groups for the given period. Must not be called until after preparation
+ * completes.
+ *
+ * <p>Use {@link #getMappedTrackInfo(int)} to get the track groups mapped to renderers.
+ *
+ * @param periodIndex The period index.
+ * @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream
+ * content.
+ */
+ public TrackGroupArray getTrackGroups(int periodIndex) {
+ assertPreparedWithMedia();
+ return trackGroupArrays[periodIndex];
+ }
+
+ /**
+ * Returns the mapped track info for the given period. Must not be called until after preparation
+ * completes.
+ *
+ * @param periodIndex The period index.
+ * @return The {@link MappedTrackInfo} for the period.
+ */
+ public MappedTrackInfo getMappedTrackInfo(int periodIndex) {
+ assertPreparedWithMedia();
+ return mappedTrackInfos[periodIndex];
+ }
+
+ /**
+ * Returns all {@link TrackSelection track selections} for a period and renderer. Must not be
+ * called until after preparation completes.
+ *
+ * @param periodIndex The period index.
+ * @param rendererIndex The renderer index.
+ * @return A list of selected {@link TrackSelection track selections}.
+ */
+ public List<TrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
+ assertPreparedWithMedia();
+ return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
+ }
+
+ /**
+ * Clears the selection of tracks for a period. Must not be called until after preparation
+ * completes.
+ *
+ * @param periodIndex The period index for which track selections are cleared.
+ */
+ public void clearTrackSelections(int periodIndex) {
+ assertPreparedWithMedia();
+ for (int i = 0; i < rendererCapabilities.length; i++) {
+ trackSelectionsByPeriodAndRenderer[periodIndex][i].clear();
+ }
+ }
+
+ /**
+ * Replaces a selection of tracks to be downloaded. Must not be called until after preparation
+ * completes.
+ *
+ * @param periodIndex The period index for which the track selection is replaced.
+ * @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
+ * selection of tracks.
+ */
+ public void replaceTrackSelections(
+ int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
+ clearTrackSelections(periodIndex);
+ addTrackSelection(periodIndex, trackSelectorParameters);
+ }
+
+ /**
+ * Adds a selection of tracks to be downloaded. Must not be called until after preparation
+ * completes.
+ *
+ * @param periodIndex The period index this track selection is added for.
+ * @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
+ * selection of tracks.
+ */
+ public void addTrackSelection(
+ int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
+ assertPreparedWithMedia();
+ trackSelector.setParameters(trackSelectorParameters);
+ runTrackSelection(periodIndex);
+ }
+
+ /**
+ * Convenience method to add selections of tracks for all specified audio languages. If an audio
+ * track in one of the specified languages is not available, the default fallback audio track is
+ * used instead. Must not be called until after preparation completes.
+ *
+ * @param languages A list of audio languages for which tracks should be added to the download
+ * selection, as IETF BCP 47 conformant tags.
+ */
+ public void addAudioLanguagesToSelection(String... languages) {
+ assertPreparedWithMedia();
+ for (int periodIndex = 0; periodIndex < mappedTrackInfos.length; periodIndex++) {
+ DefaultTrackSelector.ParametersBuilder parametersBuilder =
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
+ MappedTrackInfo mappedTrackInfo = mappedTrackInfos[periodIndex];
+ int rendererCount = mappedTrackInfo.getRendererCount();
+ for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
+ if (mappedTrackInfo.getRendererType(rendererIndex) != C.TRACK_TYPE_AUDIO) {
+ parametersBuilder.setRendererDisabled(rendererIndex, /* disabled= */ true);
+ }
+ }
+ for (String language : languages) {
+ parametersBuilder.setPreferredAudioLanguage(language);
+ addTrackSelection(periodIndex, parametersBuilder.build());
+ }
+ }
+ }
+
+ /**
+ * Convenience method to add selections of tracks for all specified text languages. Must not be
+ * called until after preparation completes.
+ *
+ * @param selectUndeterminedTextLanguage Whether a text track with undetermined language should be
+ * selected for downloading if no track with one of the specified {@code languages} is
+ * available.
+ * @param languages A list of text languages for which tracks should be added to the download
+ * selection, as IETF BCP 47 conformant tags.
+ */
+ public void addTextLanguagesToSelection(
+ boolean selectUndeterminedTextLanguage, String... languages) {
+ assertPreparedWithMedia();
+ for (int periodIndex = 0; periodIndex < mappedTrackInfos.length; periodIndex++) {
+ DefaultTrackSelector.ParametersBuilder parametersBuilder =
+ DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
+ MappedTrackInfo mappedTrackInfo = mappedTrackInfos[periodIndex];
+ int rendererCount = mappedTrackInfo.getRendererCount();
+ for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
+ if (mappedTrackInfo.getRendererType(rendererIndex) != C.TRACK_TYPE_TEXT) {
+ parametersBuilder.setRendererDisabled(rendererIndex, /* disabled= */ true);
+ }
+ }
+ parametersBuilder.setSelectUndeterminedTextLanguage(selectUndeterminedTextLanguage);
+ for (String language : languages) {
+ parametersBuilder.setPreferredTextLanguage(language);
+ addTrackSelection(periodIndex, parametersBuilder.build());
+ }
+ }
+ }
+
+ /**
+ * Convenience method to add a selection of tracks to be downloaded for a single renderer. Must
+ * not be called until after preparation completes.
+ *
+ * @param periodIndex The period index the track selection is added for.
+ * @param rendererIndex The renderer index the track selection is added for.
+ * @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
+ * selection of tracks.
+ * @param overrides A list of {@link SelectionOverride SelectionOverrides} to apply to the {@code
+ * trackSelectorParameters}. If empty, {@code trackSelectorParameters} are used as they are.
+ */
+ public void addTrackSelectionForSingleRenderer(
+ int periodIndex,
+ int rendererIndex,
+ DefaultTrackSelector.Parameters trackSelectorParameters,
+ List<SelectionOverride> overrides) {
+ assertPreparedWithMedia();
+ DefaultTrackSelector.ParametersBuilder builder = trackSelectorParameters.buildUpon();
+ for (int i = 0; i < mappedTrackInfos[periodIndex].getRendererCount(); i++) {
+ builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex);
+ }
+ if (overrides.isEmpty()) {
+ addTrackSelection(periodIndex, builder.build());
+ } else {
+ TrackGroupArray trackGroupArray = mappedTrackInfos[periodIndex].getTrackGroups(rendererIndex);
+ for (int i = 0; i < overrides.size(); i++) {
+ builder.setSelectionOverride(rendererIndex, trackGroupArray, overrides.get(i));
+ addTrackSelection(periodIndex, builder.build());
+ }
+ }
+ }
+
+ /**
+ * Builds a {@link DownloadRequest} for downloading the selected tracks. Must not be called until
+ * 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}.
+ * @return The built {@link DownloadRequest}.
+ */
+ public DownloadRequest getDownloadRequest(@Nullable byte[] data) {
+ return getDownloadRequest(uri.toString(), data);
+ }
+
+ /**
+ * Builds a {@link DownloadRequest} for downloading the selected tracks. Must not be called until
+ * after preparation completes.
+ *
+ * @param id The unique content id.
+ * @param data Application provided data to store in {@link DownloadRequest#data}.
+ * @return The built {@link DownloadRequest}.
+ */
+ public DownloadRequest getDownloadRequest(String id, @Nullable byte[] data) {
+ if (mediaSource == null) {
+ return new DownloadRequest(
+ id, downloadType, uri, /* streamKeys= */ 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++) {
+ allSelections.addAll(trackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex]);
+ }
+ streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
+ }
+ return new DownloadRequest(id, downloadType, uri, streamKeys, cacheKey, data);
+ }
+
+ // Initialization of array of Lists.
+ @SuppressWarnings("unchecked")
+ 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 =
+ (List<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
+ for (int i = 0; i < periodCount; i++) {
+ for (int j = 0; j < rendererCount; j++) {
+ trackSelectionsByPeriodAndRenderer[i][j] = new ArrayList<>();
+ immutableTrackSelectionsByPeriodAndRenderer[i][j] =
+ Collections.unmodifiableList(trackSelectionsByPeriodAndRenderer[i][j]);
+ }
+ }
+ trackGroupArrays = new TrackGroupArray[periodCount];
+ mappedTrackInfos = new MappedTrackInfo[periodCount];
+ for (int i = 0; i < periodCount; i++) {
+ trackGroupArrays[i] = mediaPreparer.mediaPeriods[i].getTrackGroups();
+ TrackSelectorResult trackSelectorResult = runTrackSelection(/* periodIndex= */ i);
+ trackSelector.onSelectionActivated(trackSelectorResult.info);
+ mappedTrackInfos[i] = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
+ }
+ setPreparedWithMedia();
+ Assertions.checkNotNull(callbackHandler)
+ .post(() -> Assertions.checkNotNull(callback).onPrepared(this));
+ }
+
+ private void onMediaPreparationFailed(IOException error) {
+ Assertions.checkNotNull(callbackHandler)
+ .post(() -> Assertions.checkNotNull(callback).onPrepareError(this, error));
+ }
+
+ @RequiresNonNull({
+ "trackGroupArrays",
+ "mappedTrackInfos",
+ "trackSelectionsByPeriodAndRenderer",
+ "immutableTrackSelectionsByPeriodAndRenderer",
+ "mediaPreparer",
+ "mediaPreparer.timeline",
+ "mediaPreparer.mediaPeriods"
+ })
+ private void setPreparedWithMedia() {
+ isPreparedWithMedia = true;
+ }
+
+ @EnsuresNonNull({
+ "trackGroupArrays",
+ "mappedTrackInfos",
+ "trackSelectionsByPeriodAndRenderer",
+ "immutableTrackSelectionsByPeriodAndRenderer",
+ "mediaPreparer",
+ "mediaPreparer.timeline",
+ "mediaPreparer.mediaPeriods"
+ })
+ @SuppressWarnings("nullness:contracts.postcondition.not.satisfied")
+ private void assertPreparedWithMedia() {
+ Assertions.checkState(isPreparedWithMedia);
+ }
+
+ /**
+ * Runs the track selection for a given period index with the current parameters. The selected
+ * tracks will be added to {@link #trackSelectionsByPeriodAndRenderer}.
+ */
+ // Intentional reference comparison of track group instances.
+ @SuppressWarnings("ReferenceEquality")
+ @RequiresNonNull({
+ "trackGroupArrays",
+ "trackSelectionsByPeriodAndRenderer",
+ "mediaPreparer",
+ "mediaPreparer.timeline"
+ })
+ private TrackSelectorResult runTrackSelection(int periodIndex) {
+ try {
+ TrackSelectorResult trackSelectorResult =
+ trackSelector.selectTracks(
+ rendererCapabilities,
+ trackGroupArrays[periodIndex],
+ new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
+ mediaPreparer.timeline);
+ for (int i = 0; i < trackSelectorResult.length; i++) {
+ @Nullable TrackSelection newSelection = trackSelectorResult.selections.get(i);
+ if (newSelection == null) {
+ continue;
+ }
+ List<TrackSelection> existingSelectionList =
+ trackSelectionsByPeriodAndRenderer[periodIndex][i];
+ boolean mergedWithExistingSelection = false;
+ for (int j = 0; j < existingSelectionList.size(); j++) {
+ TrackSelection existingSelection = existingSelectionList.get(j);
+ if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) {
+ // Merge with existing selection.
+ scratchSet.clear();
+ for (int k = 0; k < existingSelection.length(); k++) {
+ scratchSet.put(existingSelection.getIndexInTrackGroup(k), 0);
+ }
+ for (int k = 0; k < newSelection.length(); k++) {
+ scratchSet.put(newSelection.getIndexInTrackGroup(k), 0);
+ }
+ int[] mergedTracks = new int[scratchSet.size()];
+ for (int k = 0; k < scratchSet.size(); k++) {
+ mergedTracks[k] = scratchSet.keyAt(k);
+ }
+ existingSelectionList.set(
+ j, new DownloadTrackSelection(existingSelection.getTrackGroup(), mergedTracks));
+ mergedWithExistingSelection = true;
+ break;
+ }
+ }
+ if (!mergedWithExistingSelection) {
+ existingSelectionList.add(newSelection);
+ }
+ }
+ return trackSelectorResult;
+ } catch (ExoPlaybackException e) {
+ // DefaultTrackSelector does not throw exceptions during track selection.
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ @Nullable
+ private static Constructor<? extends MediaSourceFactory> getConstructor(String className) {
+ try {
+ // LINT.IfChange
+ Class<? extends MediaSourceFactory> factoryClazz =
+ Class.forName(className).asSubclass(MediaSourceFactory.class);
+ return factoryClazz.getConstructor(Factory.class);
+ // LINT.ThenChange(../../../../../../../../proguard-rules.txt)
+ } catch (ClassNotFoundException e) {
+ // Expected if the app was built without the respective module.
+ return null;
+ } catch (NoSuchMethodException e) {
+ // Something is wrong with the library or the proguard configuration.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static MediaSource createMediaSourceInternal(
+ @Nullable Constructor<? extends MediaSourceFactory> constructor,
+ Uri uri,
+ Factory dataSourceFactory,
+ @Nullable DrmSessionManager<?> drmSessionManager,
+ @Nullable List<StreamKey> streamKeys) {
+ if (constructor == null) {
+ throw new IllegalStateException("Module missing to create media source.");
+ }
+ try {
+ MediaSourceFactory factory = constructor.newInstance(dataSourceFactory);
+ if (drmSessionManager != null) {
+ factory.setDrmSessionManager(drmSessionManager);
+ }
+ if (streamKeys != null) {
+ factory.setStreamKeys(streamKeys);
+ }
+ return Assertions.checkNotNull(factory.createMediaSource(uri));
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to instantiate media source.", e);
+ }
+ }
+
+ private static final class MediaPreparer
+ implements MediaSourceCaller, MediaPeriod.Callback, Handler.Callback {
+
+ private static final int MESSAGE_PREPARE_SOURCE = 0;
+ private static final int MESSAGE_CHECK_FOR_FAILURE = 1;
+ private static final int MESSAGE_CONTINUE_LOADING = 2;
+ private static final int MESSAGE_RELEASE = 3;
+
+ private static final int DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED = 0;
+ private static final int DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED = 1;
+
+ private final MediaSource mediaSource;
+ private final DownloadHelper downloadHelper;
+ private final Allocator allocator;
+ private final ArrayList<MediaPeriod> pendingMediaPeriods;
+ private final Handler downloadHelperHandler;
+ private final HandlerThread mediaSourceThread;
+ private final Handler mediaSourceHandler;
+
+ public @MonotonicNonNull Timeline timeline;
+ public MediaPeriod @MonotonicNonNull [] mediaPeriods;
+
+ private boolean released;
+
+ public MediaPreparer(MediaSource mediaSource, DownloadHelper downloadHelper) {
+ this.mediaSource = mediaSource;
+ this.downloadHelper = downloadHelper;
+ allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
+ pendingMediaPeriods = new ArrayList<>();
+ @SuppressWarnings("methodref.receiver.bound.invalid")
+ Handler downloadThreadHandler = Util.createHandler(this::handleDownloadHelperCallbackMessage);
+ this.downloadHelperHandler = downloadThreadHandler;
+ mediaSourceThread = new HandlerThread("DownloadHelper");
+ mediaSourceThread.start();
+ mediaSourceHandler = Util.createHandler(mediaSourceThread.getLooper(), /* callback= */ this);
+ mediaSourceHandler.sendEmptyMessage(MESSAGE_PREPARE_SOURCE);
+ }
+
+ public void release() {
+ if (released) {
+ return;
+ }
+ released = true;
+ mediaSourceHandler.sendEmptyMessage(MESSAGE_RELEASE);
+ }
+
+ // Handler.Callback
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_PREPARE_SOURCE:
+ mediaSource.prepareSource(/* caller= */ this, /* mediaTransferListener= */ null);
+ mediaSourceHandler.sendEmptyMessage(MESSAGE_CHECK_FOR_FAILURE);
+ return true;
+ case MESSAGE_CHECK_FOR_FAILURE:
+ try {
+ if (mediaPeriods == null) {
+ mediaSource.maybeThrowSourceInfoRefreshError();
+ } else {
+ for (int i = 0; i < pendingMediaPeriods.size(); i++) {
+ pendingMediaPeriods.get(i).maybeThrowPrepareError();
+ }
+ }
+ mediaSourceHandler.sendEmptyMessageDelayed(
+ MESSAGE_CHECK_FOR_FAILURE, /* delayMillis= */ 100);
+ } catch (IOException e) {
+ downloadHelperHandler
+ .obtainMessage(DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED, /* obj= */ e)
+ .sendToTarget();
+ }
+ return true;
+ case MESSAGE_CONTINUE_LOADING:
+ MediaPeriod mediaPeriod = (MediaPeriod) msg.obj;
+ if (pendingMediaPeriods.contains(mediaPeriod)) {
+ mediaPeriod.continueLoading(/* positionUs= */ 0);
+ }
+ return true;
+ case MESSAGE_RELEASE:
+ if (mediaPeriods != null) {
+ for (MediaPeriod period : mediaPeriods) {
+ mediaSource.releasePeriod(period);
+ }
+ }
+ mediaSource.releaseSource(this);
+ mediaSourceHandler.removeCallbacksAndMessages(null);
+ mediaSourceThread.quit();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // MediaSource.MediaSourceCaller implementation.
+
+ @Override
+ public void onSourceInfoRefreshed(MediaSource source, Timeline timeline) {
+ if (this.timeline != null) {
+ // Ignore dynamic updates.
+ return;
+ }
+ if (timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()).isLive) {
+ downloadHelperHandler
+ .obtainMessage(
+ DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED,
+ /* obj= */ new LiveContentUnsupportedException())
+ .sendToTarget();
+ return;
+ }
+ this.timeline = timeline;
+ mediaPeriods = new MediaPeriod[timeline.getPeriodCount()];
+ for (int i = 0; i < mediaPeriods.length; i++) {
+ MediaPeriod mediaPeriod =
+ mediaSource.createPeriod(
+ new MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ i)),
+ allocator,
+ /* startPositionUs= */ 0);
+ mediaPeriods[i] = mediaPeriod;
+ pendingMediaPeriods.add(mediaPeriod);
+ }
+ for (MediaPeriod mediaPeriod : mediaPeriods) {
+ mediaPeriod.prepare(/* callback= */ this, /* positionUs= */ 0);
+ }
+ }
+
+ // MediaPeriod.Callback implementation.
+
+ @Override
+ public void onPrepared(MediaPeriod mediaPeriod) {
+ pendingMediaPeriods.remove(mediaPeriod);
+ if (pendingMediaPeriods.isEmpty()) {
+ mediaSourceHandler.removeMessages(MESSAGE_CHECK_FOR_FAILURE);
+ downloadHelperHandler.sendEmptyMessage(DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED);
+ }
+ }
+
+ @Override
+ public void onContinueLoadingRequested(MediaPeriod mediaPeriod) {
+ if (pendingMediaPeriods.contains(mediaPeriod)) {
+ mediaSourceHandler.obtainMessage(MESSAGE_CONTINUE_LOADING, mediaPeriod).sendToTarget();
+ }
+ }
+
+ private boolean handleDownloadHelperCallbackMessage(Message msg) {
+ if (released) {
+ // Stale message.
+ return false;
+ }
+ switch (msg.what) {
+ case DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED:
+ downloadHelper.onMediaPrepared();
+ return true;
+ case DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED:
+ release();
+ downloadHelper.onMediaPreparationFailed((IOException) Util.castNonNull(msg.obj));
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ private static final class DownloadTrackSelection extends BaseTrackSelection {
+
+ private static final class Factory implements TrackSelection.Factory {
+
+ @Override
+ public @NullableType TrackSelection[] createTrackSelections(
+ @NullableType Definition[] definitions, BandwidthMeter bandwidthMeter) {
+ @NullableType TrackSelection[] selections = new TrackSelection[definitions.length];
+ for (int i = 0; i < definitions.length; i++) {
+ selections[i] =
+ definitions[i] == null
+ ? null
+ : new DownloadTrackSelection(definitions[i].group, definitions[i].tracks);
+ }
+ return selections;
+ }
+ }
+
+ public DownloadTrackSelection(TrackGroup trackGroup, int[] tracks) {
+ super(trackGroup, tracks);
+ }
+
+ @Override
+ public int getSelectedIndex() {
+ return 0;
+ }
+
+ @Override
+ public int getSelectionReason() {
+ return C.SELECTION_REASON_UNKNOWN;
+ }
+
+ @Nullable
+ @Override
+ public Object getSelectionData() {
+ return null;
+ }
+
+ @Override
+ public void updateSelectedTrack(
+ long playbackPositionUs,
+ long bufferedDurationUs,
+ long availableDurationUs,
+ List<? extends MediaChunk> queue,
+ MediaChunkIterator[] mediaChunkIterators) {
+ // Do nothing.
+ }
+ }
+
+ private static final class DummyBandwidthMeter implements BandwidthMeter {
+
+ @Override
+ public long getBitrateEstimate() {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public TransferListener getTransferListener() {
+ return null;
+ }
+
+ @Override
+ public void addEventListener(Handler eventHandler, EventListener eventListener) {
+ // Do nothing.
+ }
+
+ @Override
+ public void removeEventListener(EventListener eventListener) {
+ // Do nothing.
+ }
+ }
+}