java.lang.Object
↳androidx.media3.exoplayer.offline.DownloadHelper
Gradle dependencies
compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.5.0-alpha01'
- groupId: androidx.media3
- artifactId: media3-exoplayer
- version: 1.5.0-alpha01
Artifact androidx.media3:media3-exoplayer:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)
Overview
A helper for initializing and removing downloads.
The helper extracts track information from the media, selects tracks for downloading, and
creates download requests based on the selected tracks.
A typical usage of DownloadHelper follows these steps:
- Build the helper using one of the forMediaItem methods.
- Prepare the helper using DownloadHelper.prepare(DownloadHelper.Callback) and wait for the callback.
- Optional: Inspect the selected tracks using DownloadHelper.getMappedTrackInfo(int) and DownloadHelper.getTrackSelections(int, int), and make adjustments using DownloadHelper.clearTrackSelections(int), DownloadHelper.replaceTrackSelections(int, TrackSelectionParameters)
and DownloadHelper.addTrackSelection(int, TrackSelectionParameters).
- Create a download request for the selected track using DownloadHelper.getDownloadRequest(byte[]).
- Release the helper using DownloadHelper.release().
Summary
Methods |
---|
public void | addAudioLanguagesToSelection(java.lang.String languages[])
Convenience method to add selections of tracks for all specified audio languages. |
public void | addTextLanguagesToSelection(boolean selectUndeterminedTextLanguage, java.lang.String languages[])
Convenience method to add selections of tracks for all specified text languages. |
public void | addTrackSelection(int periodIndex, TrackSelectionParameters trackSelectionParameters)
Adds a selection of tracks to be downloaded. |
public void | addTrackSelectionForSingleRenderer(int periodIndex, int rendererIndex, DefaultTrackSelector.Parameters trackSelectorParameters, java.util.List<DefaultTrackSelector.SelectionOverride> overrides)
Convenience method to add a selection of tracks to be downloaded for a single renderer. |
public void | clearTrackSelections(int periodIndex)
Clears the selection of tracks for a period. |
public static MediaSource | createMediaSource(DownloadRequest downloadRequest, DataSource.Factory dataSourceFactory)
Equivalent to createMediaSource(downloadRequest, dataSourceFactory, null). |
public static MediaSource | createMediaSource(DownloadRequest downloadRequest, DataSource.Factory dataSourceFactory, DrmSessionManager drmSessionManager)
Utility method to create a MediaSource that only exposes the tracks defined in downloadRequest. |
public static DownloadHelper | forMediaItem(Context context, MediaItem mediaItem)
Creates a DownloadHelper for the given progressive media item. |
public static DownloadHelper | forMediaItem(Context context, MediaItem mediaItem, RenderersFactory renderersFactory, DataSource.Factory dataSourceFactory)
Creates a DownloadHelper for the given media item. |
public static DownloadHelper | forMediaItem(MediaItem mediaItem, TrackSelectionParameters trackSelectionParameters, RenderersFactory renderersFactory, DataSource.Factory dataSourceFactory)
Creates a DownloadHelper for the given media item. |
public static DownloadHelper | forMediaItem(MediaItem mediaItem, TrackSelectionParameters trackSelectionParameters, RenderersFactory renderersFactory, DataSource.Factory dataSourceFactory, DrmSessionManager drmSessionManager)
Creates a DownloadHelper for the given media item. |
public static DefaultTrackSelector.Parameters | getDefaultTrackSelectorParameters(Context context)
Returns the default parameters used for track selection for downloading. |
public DownloadRequest | getDownloadRequest(byte[] data[])
Builds a DownloadRequest for downloading the selected tracks. |
public DownloadRequest | getDownloadRequest(java.lang.String id, byte[] data[])
Builds a DownloadRequest for downloading the selected tracks. |
public java.lang.Object | getManifest()
Returns the manifest, or null if no manifest is loaded. |
public MappingTrackSelector.MappedTrackInfo | getMappedTrackInfo(int periodIndex)
Returns the mapped track info for the given period. |
public int | getPeriodCount()
Returns the number of periods for which media is available. |
public static RendererCapabilities | getRendererCapabilities(RenderersFactory renderersFactory)
|
public TrackGroupArray | getTrackGroups(int periodIndex)
Returns the track groups for the given period. |
public Tracks | getTracks(int periodIndex)
Returns Tracks for the given period. |
public java.util.List<ExoTrackSelection> | getTrackSelections(int periodIndex, int rendererIndex)
Returns all track selections for a period and renderer. |
public void | prepare(DownloadHelper.Callback callback)
Initializes the helper for starting a download. |
public void | release()
Releases the helper and all resources it is holding. |
public void | replaceTrackSelections(int periodIndex, TrackSelectionParameters trackSelectionParameters)
Replaces a selection of tracks to be downloaded. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
Default track selection parameters for downloading, but without any
constraints.
If possible, use DownloadHelper.getDefaultTrackSelectorParameters(Context) instead.
See also:
Constructors
Deprecated: The Renderer instances used to produce rendererCapabilities must be
kept alive for the lifetime of this DownloadHelper instance and then released (to
avoid a resource leak). Use DownloadHelper.DownloadHelper(MediaItem, MediaSource, TrackSelectionParameters, RendererCapabilitiesList) instead to avoid needing to manually
manage this bookkeeping.
Creates download helper.
Parameters:
mediaItem: The media item.
mediaSource: A MediaSource for which tracks are selected, or null if no track
selection needs to be made.
trackSelectionParameters: TrackSelectionParameters for selecting tracks for
downloading.
rendererCapabilities: The RendererCapabilitiesList of the renderers for which
tracks are selected.
Methods
Returns the default parameters used for track selection for downloading.
Deprecated: This method leaks un-released Renderer instances. There is no direct
replacement. Equivalent functionality can be implemented by constructing the renderer
instances, calling Renderer.getCapabilities() on each one, then releasing the
renderers when the capabilities are no longer required.
Creates a DownloadHelper for the given progressive media item.
Parameters:
context: The context.
mediaItem: A MediaItem.
Returns:
A DownloadHelper for progressive streams.
Creates a DownloadHelper for the given media item.
Parameters:
context: The context.
mediaItem: A MediaItem.
renderersFactory: A RenderersFactory creating the renderers for which tracks are
selected.
dataSourceFactory: A used to load the manifest for adaptive
streams. This argument is required for adaptive streams and ignored for progressive
streams.
Returns:
A DownloadHelper.
Creates a DownloadHelper for the given media item.
Parameters:
mediaItem: A MediaItem.
renderersFactory: A RenderersFactory creating the renderers for which tracks are
selected.
trackSelectionParameters: TrackSelectionParameters for selecting tracks for
downloading.
dataSourceFactory: A used to load the manifest for adaptive
streams. This argument is required for adaptive streams and ignored for progressive
streams.
Returns:
A DownloadHelper.
Creates a DownloadHelper for the given media item.
Parameters:
mediaItem: A MediaItem.
renderersFactory: A RenderersFactory creating the renderers for which tracks are
selected.
trackSelectionParameters: TrackSelectionParameters for selecting tracks for
downloading.
dataSourceFactory: A used to load the manifest for adaptive
streams. This argument is required for adaptive streams and ignored for progressive
streams.
drmSessionManager: An optional DrmSessionManager. Used to help determine which
tracks can be selected.
Returns:
A DownloadHelper.
Equivalent to createMediaSource(downloadRequest, dataSourceFactory, null).
Utility method to create a MediaSource that only exposes the tracks defined in downloadRequest.
Parameters:
downloadRequest: A DownloadRequest.
dataSourceFactory: A factory for DataSources to read the media.
drmSessionManager: An optional DrmSessionManager to be passed to the MediaSource.
Returns:
A MediaSource that only exposes the tracks defined in downloadRequest.
Initializes the helper for starting a download.
Parameters:
callback: A callback to be notified when preparation completes or fails.
Releases the helper and all resources it is holding.
public java.lang.Object
getManifest()
Returns the manifest, or null if no manifest is loaded. Must not be called until after
preparation completes.
public int
getPeriodCount()
Returns the number of periods for which media is available. Must not be called until after
preparation completes.
public
Tracks getTracks(int periodIndex)
Returns Tracks for the given period. Must not be called until after preparation
completes.
Parameters:
periodIndex: The period index.
Returns:
The Tracks for the period. May be Tracks.EMPTY for single stream
content.
Returns the track groups for the given period. Must not be called until after preparation
completes.
Use DownloadHelper.getMappedTrackInfo(int) to get the track groups mapped to renderers.
Parameters:
periodIndex: The period index.
Returns:
The track groups for the period. May be TrackGroupArray.EMPTY for single stream
content.
Returns the mapped track info for the given period. Must not be called until after preparation
completes.
Parameters:
periodIndex: The period index.
Returns:
The MappingTrackSelector.MappedTrackInfo for the period.
public java.util.List<ExoTrackSelection>
getTrackSelections(int periodIndex, int rendererIndex)
Returns all track selections for a period and renderer. Must not be
called until after preparation completes.
Parameters:
periodIndex: The period index.
rendererIndex: The renderer index.
Returns:
A list of selected track selections.
public void
clearTrackSelections(int periodIndex)
Clears the selection of tracks for a period. Must not be called until after preparation
completes.
Parameters:
periodIndex: The period index for which track selections are cleared.
Replaces a selection of tracks to be downloaded. Must not be called until after preparation
completes.
Parameters:
periodIndex: The period index for which the track selection is replaced.
trackSelectionParameters: The TrackSelectionParameters to obtain the new
selection of tracks.
Adds a selection of tracks to be downloaded. Must not be called until after preparation
completes.
Parameters:
periodIndex: The period index this track selection is added for.
trackSelectionParameters: The TrackSelectionParameters to obtain the new
selection of tracks.
public void
addAudioLanguagesToSelection(java.lang.String languages[])
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.
Parameters:
languages: A list of audio languages for which tracks should be added to the download
selection, as IETF BCP 47 conformant tags.
public void
addTextLanguagesToSelection(boolean selectUndeterminedTextLanguage, java.lang.String languages[])
Convenience method to add selections of tracks for all specified text languages. Must not be
called until after preparation completes.
Parameters:
selectUndeterminedTextLanguage: Whether a text track with undetermined language should be
selected for downloading if no track with one of the specified languages is
available.
languages: A list of text languages for which tracks should be added to the download
selection, as IETF BCP 47 conformant tags.
public void
addTrackSelectionForSingleRenderer(int periodIndex, int rendererIndex,
DefaultTrackSelector.Parameters trackSelectorParameters, java.util.List<DefaultTrackSelector.SelectionOverride> overrides)
Convenience method to add a selection of tracks to be downloaded for a single renderer. Must
not be called until after preparation completes.
Parameters:
periodIndex: The period index the track selection is added for.
rendererIndex: The renderer index the track selection is added for.
trackSelectorParameters: The to obtain the new
selection of tracks.
overrides: A list of SelectionOverrides to apply to the trackSelectorParameters. If empty, trackSelectorParameters are used as they are.
Builds a DownloadRequest for downloading the selected tracks. Must not be called until
after preparation completes. The uri of the DownloadRequest will be used as content id.
Parameters:
data: Application provided data to store in DownloadRequest.data.
Returns:
The built DownloadRequest.
public
DownloadRequest getDownloadRequest(java.lang.String id, byte[] data[])
Builds a DownloadRequest for downloading the selected tracks. Must not be called until
after preparation completes.
Parameters:
id: The unique content id.
data: Application provided data to store in DownloadRequest.data.
Returns:
The built DownloadRequest.
Source
/*
* 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 androidx.media3.exoplayer.offline;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.SparseIntArray;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.StreamKey;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.LoadingInfo;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.RendererCapabilitiesList;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.source.chunk.MediaChunk;
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
import androidx.media3.exoplayer.trackselection.BaseTrackSelection;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
import androidx.media3.exoplayer.trackselection.TrackSelectionUtil;
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.upstream.DefaultAllocator;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.extractor.ExtractorsFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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 forMediaItem} 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, TrackSelectionParameters)}
* and {@link #addTrackSelection(int, TrackSelectionParameters)}.
* <li>Create a download request for the selected track using {@link #getDownloadRequest(byte[])}.
* <li>Release the helper using {@link #release()}.
* </ol>
*/
@UnstableApi
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 DefaultTrackSelector.Parameters#DEFAULT_WITHOUT_CONTEXT
*/
public static final DefaultTrackSelector.Parameters
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT =
DefaultTrackSelector.Parameters.DEFAULT_WITHOUT_CONTEXT
.buildUpon()
.setForceHighestSupportedBitrate(true)
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.build();
/** Returns the default parameters used for track selection for downloading. */
public static DefaultTrackSelector.Parameters getDefaultTrackSelectorParameters(Context context) {
return DefaultTrackSelector.Parameters.getDefaults(context)
.buildUpon()
.setForceHighestSupportedBitrate(true)
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.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 {}
/**
* @deprecated This method leaks un-released {@link Renderer} instances. There is no direct
* replacement. Equivalent functionality can be implemented by constructing the renderer
* instances, calling {@link Renderer#getCapabilities()} on each one, then releasing the
* renderers when the capabilities are no longer required.
*/
@Deprecated
public static RendererCapabilities[] getRendererCapabilities(RenderersFactory renderersFactory) {
Renderer[] renderers =
renderersFactory.createRenderers(
Util.createHandlerForCurrentOrMainLooper(),
new VideoRendererEventListener() {},
new AudioRendererEventListener() {},
(cues) -> {},
(metadata) -> {});
RendererCapabilities[] capabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) {
capabilities[i] = renderers[i].getCapabilities();
}
return capabilities;
}
/**
* Creates a {@link DownloadHelper} for the given progressive media item.
*
* @param context The context.
* @param mediaItem A {@link MediaItem}.
* @return A {@link DownloadHelper} for progressive streams.
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
*/
public static DownloadHelper forMediaItem(Context context, MediaItem mediaItem) {
Assertions.checkArgument(isProgressive(checkNotNull(mediaItem.localConfiguration)));
return forMediaItem(
mediaItem,
getDefaultTrackSelectorParameters(context),
/* renderersFactory= */ null,
/* dataSourceFactory= */ null,
/* drmSessionManager= */ null);
}
/**
* Creates a {@link DownloadHelper} for the given media item.
*
* @param context The context.
* @param mediaItem A {@link MediaItem}.
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
* selected.
* @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.
* @return A {@link DownloadHelper}.
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
* SmoothStreaming media items.
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
*/
public static DownloadHelper forMediaItem(
Context context,
MediaItem mediaItem,
@Nullable RenderersFactory renderersFactory,
@Nullable DataSource.Factory dataSourceFactory) {
return forMediaItem(
mediaItem,
getDefaultTrackSelectorParameters(context),
renderersFactory,
dataSourceFactory,
/* drmSessionManager= */ null);
}
/**
* Creates a {@link DownloadHelper} for the given media item.
*
* @param mediaItem A {@link MediaItem}.
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
* selected.
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
* downloading.
* @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.
* @return A {@link DownloadHelper}.
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
* SmoothStreaming media items.
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
*/
public static DownloadHelper forMediaItem(
MediaItem mediaItem,
TrackSelectionParameters trackSelectionParameters,
@Nullable RenderersFactory renderersFactory,
@Nullable DataSource.Factory dataSourceFactory) {
return forMediaItem(
mediaItem,
trackSelectionParameters,
renderersFactory,
dataSourceFactory,
/* drmSessionManager= */ null);
}
/**
* Creates a {@link DownloadHelper} for the given media item.
*
* @param mediaItem A {@link MediaItem}.
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
* selected.
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
* downloading.
* @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.
* @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
* tracks can be selected.
* @return A {@link DownloadHelper}.
* @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
* SmoothStreaming media items.
* @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
*/
public static DownloadHelper forMediaItem(
MediaItem mediaItem,
TrackSelectionParameters trackSelectionParameters,
@Nullable RenderersFactory renderersFactory,
@Nullable DataSource.Factory dataSourceFactory,
@Nullable DrmSessionManager drmSessionManager) {
boolean isProgressive = isProgressive(checkNotNull(mediaItem.localConfiguration));
Assertions.checkArgument(isProgressive || dataSourceFactory != null);
return new DownloadHelper(
mediaItem,
isProgressive
? null
: createMediaSourceInternal(
mediaItem, castNonNull(dataSourceFactory), drmSessionManager),
trackSelectionParameters,
renderersFactory != null
? new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList()
: new UnreleaseableRendererCapabilitiesList(new RendererCapabilities[0]));
}
/**
* Equivalent to {@link #createMediaSource(DownloadRequest, DataSource.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) {
return createMediaSourceInternal(
downloadRequest.toMediaItem(), dataSourceFactory, drmSessionManager);
}
private final MediaItem.LocalConfiguration localConfiguration;
@Nullable private final MediaSource mediaSource;
private final DefaultTrackSelector trackSelector;
private final RendererCapabilitiesList 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<ExoTrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
private List<ExoTrackSelection> @MonotonicNonNull [][]
immutableTrackSelectionsByPeriodAndRenderer;
/**
* @deprecated The {@link Renderer} instances used to produce {@code rendererCapabilities} must be
* kept alive for the lifetime of this {@code DownloadHelper} instance and then released (to
* avoid a resource leak). Use {@link DownloadHelper#DownloadHelper(MediaItem, MediaSource,
* TrackSelectionParameters, RendererCapabilitiesList)} instead to avoid needing to manually
* manage this bookkeeping.
*/
@Deprecated
public DownloadHelper(
MediaItem mediaItem,
@Nullable MediaSource mediaSource,
TrackSelectionParameters trackSelectionParameters,
RendererCapabilities[] rendererCapabilities) {
this(
mediaItem,
mediaSource,
trackSelectionParameters,
new UnreleaseableRendererCapabilitiesList(rendererCapabilities));
}
/**
* Creates download helper.
*
* @param mediaItem The media item.
* @param mediaSource A {@link MediaSource} for which tracks are selected, or null if no track
* selection needs to be made.
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
* downloading.
* @param rendererCapabilities The {@link RendererCapabilitiesList} of the renderers for which
* tracks are selected.
*/
public DownloadHelper(
MediaItem mediaItem,
@Nullable MediaSource mediaSource,
TrackSelectionParameters trackSelectionParameters,
RendererCapabilitiesList rendererCapabilities) {
this.localConfiguration = checkNotNull(mediaItem.localConfiguration);
this.mediaSource = mediaSource;
this.trackSelector =
new DefaultTrackSelector(trackSelectionParameters, new DownloadTrackSelection.Factory());
this.rendererCapabilities = rendererCapabilities;
this.scratchSet = new SparseIntArray();
trackSelector.init(/* listener= */ () -> {}, new FakeBandwidthMeter());
callbackHandler = Util.createHandlerForCurrentOrMainLooper();
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();
}
trackSelector.release();
rendererCapabilities.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 {@link Tracks} for the given period. Must not be called until after preparation
* completes.
*
* @param periodIndex The period index.
* @return The {@link Tracks} for the period. May be {@link Tracks#EMPTY} for single stream
* content.
*/
public Tracks getTracks(int periodIndex) {
assertPreparedWithMedia();
return TrackSelectionUtil.buildTracks(
mappedTrackInfos[periodIndex], immutableTrackSelectionsByPeriodAndRenderer[periodIndex]);
}
/**
* 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 ExoTrackSelection 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 ExoTrackSelection track selections}.
*/
public List<ExoTrackSelection> 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.size(); 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 trackSelectionParameters The {@link TrackSelectionParameters} to obtain the new
* selection of tracks.
*/
public void replaceTrackSelections(
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
try {
assertPreparedWithMedia();
clearTrackSelections(periodIndex);
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
} catch (ExoPlaybackException e) {
throw new IllegalStateException(e);
}
}
/**
* 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 trackSelectionParameters The {@link TrackSelectionParameters} to obtain the new
* selection of tracks.
*/
public void addTrackSelection(
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
try {
assertPreparedWithMedia();
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
} catch (ExoPlaybackException e) {
throw new IllegalStateException(e);
}
}
/**
* 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) {
try {
assertPreparedWithMedia();
TrackSelectionParameters.Builder parametersBuilder =
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
// Prefer highest supported bitrate for downloads.
parametersBuilder.setForceHighestSupportedBitrate(true);
// Disable all non-audio track types supported by the renderers.
for (RendererCapabilities capabilities : rendererCapabilities.getRendererCapabilities()) {
@C.TrackType int trackType = capabilities.getTrackType();
parametersBuilder.setTrackTypeDisabled(
trackType, /* disabled= */ trackType != C.TRACK_TYPE_AUDIO);
}
// Add a track selection to each period for each of the languages.
int periodCount = getPeriodCount();
for (String language : languages) {
TrackSelectionParameters parameters =
parametersBuilder.setPreferredAudioLanguage(language).build();
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
addTrackSelectionInternal(periodIndex, parameters);
}
}
} catch (ExoPlaybackException e) {
throw new IllegalStateException(e);
}
}
/**
* 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) {
try {
assertPreparedWithMedia();
TrackSelectionParameters.Builder parametersBuilder =
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
parametersBuilder.setSelectUndeterminedTextLanguage(selectUndeterminedTextLanguage);
// Prefer highest supported bitrate for downloads.
parametersBuilder.setForceHighestSupportedBitrate(true);
// Disable all non-text track types supported by the renderers.
for (RendererCapabilities capabilities : rendererCapabilities.getRendererCapabilities()) {
@C.TrackType int trackType = capabilities.getTrackType();
parametersBuilder.setTrackTypeDisabled(
trackType, /* disabled= */ trackType != C.TRACK_TYPE_TEXT);
}
// Add a track selection to each period for each of the languages.
int periodCount = getPeriodCount();
for (String language : languages) {
TrackSelectionParameters parameters =
parametersBuilder.setPreferredTextLanguage(language).build();
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
addTrackSelectionInternal(periodIndex, parameters);
}
}
} catch (ExoPlaybackException e) {
throw new IllegalStateException(e);
}
}
/**
* 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) {
try {
assertPreparedWithMedia();
DefaultTrackSelector.Parameters.Builder builder = trackSelectorParameters.buildUpon();
for (int i = 0; i < mappedTrackInfos[periodIndex].getRendererCount(); i++) {
builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex);
}
if (overrides.isEmpty()) {
addTrackSelectionInternal(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));
addTrackSelectionInternal(periodIndex, builder.build());
}
}
} catch (ExoPlaybackException e) {
throw new IllegalStateException(e);
}
}
/**
* 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(localConfiguration.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) {
DownloadRequest.Builder requestBuilder =
new DownloadRequest.Builder(id, localConfiguration.uri)
.setMimeType(localConfiguration.mimeType)
.setKeySetId(
localConfiguration.drmConfiguration != null
? localConfiguration.drmConfiguration.getKeySetId()
: null)
.setCustomCacheKey(localConfiguration.customCacheKey)
.setData(data);
if (mediaSource == null) {
return requestBuilder.build();
}
assertPreparedWithMedia();
List<StreamKey> streamKeys = new ArrayList<>();
List<ExoTrackSelection> 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 requestBuilder.setStreamKeys(streamKeys).build();
}
@RequiresNonNull({
"trackGroupArrays",
"trackSelectionsByPeriodAndRenderer",
"mediaPreparer",
"mediaPreparer.timeline"
})
private void addTrackSelectionInternal(
int periodIndex, TrackSelectionParameters trackSelectionParameters)
throws ExoPlaybackException {
trackSelector.setParameters(trackSelectionParameters);
runTrackSelection(periodIndex);
// TrackSelectionParameters can contain multiple overrides for each track type. The track
// selector will only use one of them (because it's designed for playback), but for downloads we
// want to use all of them. Run selection again with each override being the only one of its
// type, to ensure that all of the desired tracks are included.
for (TrackSelectionOverride override : trackSelectionParameters.overrides.values()) {
trackSelector.setParameters(
trackSelectionParameters.buildUpon().setOverrideForType(override).build());
runTrackSelection(periodIndex);
}
}
@SuppressWarnings("unchecked") // Initialization of array of Lists.
private void onMediaPrepared() throws ExoPlaybackException {
checkNotNull(mediaPreparer);
checkNotNull(mediaPreparer.mediaPeriods);
checkNotNull(mediaPreparer.timeline);
int periodCount = mediaPreparer.mediaPeriods.length;
int rendererCount = rendererCapabilities.size();
trackSelectionsByPeriodAndRenderer =
(List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
immutableTrackSelectionsByPeriodAndRenderer =
(List<ExoTrackSelection>[][]) 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] = checkNotNull(trackSelector.getCurrentMappedTrackInfo());
}
setPreparedWithMedia();
checkNotNull(callbackHandler).post(() -> checkNotNull(callback).onPrepared(this));
}
private void onMediaPreparationFailed(IOException error) {
checkNotNull(callbackHandler).post(() -> 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")
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}.
*/
@RequiresNonNull({
"trackGroupArrays",
"trackSelectionsByPeriodAndRenderer",
"mediaPreparer",
"mediaPreparer.timeline"
})
private TrackSelectorResult runTrackSelection(int periodIndex) throws ExoPlaybackException {
TrackSelectorResult trackSelectorResult =
trackSelector.selectTracks(
rendererCapabilities.getRendererCapabilities(),
trackGroupArrays[periodIndex],
new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
mediaPreparer.timeline);
for (int i = 0; i < trackSelectorResult.length; i++) {
@Nullable ExoTrackSelection newSelection = trackSelectorResult.selections[i];
if (newSelection == null) {
continue;
}
List<ExoTrackSelection> existingSelectionList =
trackSelectionsByPeriodAndRenderer[periodIndex][i];
boolean mergedWithExistingSelection = false;
for (int j = 0; j < existingSelectionList.size(); j++) {
ExoTrackSelection existingSelection = existingSelectionList.get(j);
if (existingSelection.getTrackGroup().equals(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;
}
private static MediaSource createMediaSourceInternal(
MediaItem mediaItem,
DataSource.Factory dataSourceFactory,
@Nullable DrmSessionManager drmSessionManager) {
DefaultMediaSourceFactory mediaSourceFactory =
new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY);
if (drmSessionManager != null) {
mediaSourceFactory.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
}
return mediaSourceFactory.createMediaSource(mediaItem);
}
private static boolean isProgressive(MediaItem.LocalConfiguration localConfiguration) {
return Util.inferContentTypeForUriAndMimeType(
localConfiguration.uri, localConfiguration.mimeType)
== C.CONTENT_TYPE_OTHER;
}
private static final class MediaPreparer
implements MediaSourceCaller, MediaPeriod.Callback, Handler.Callback {
private static final int MESSAGE_PREPARE_SOURCE = 1;
private static final int MESSAGE_CHECK_FOR_FAILURE = 2;
private static final int MESSAGE_CONTINUE_LOADING = 3;
private static final int MESSAGE_RELEASE = 4;
private static final int DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED = 1;
private static final int DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED = 2;
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("nullness:methodref.receiver.bound")
Handler downloadThreadHandler =
Util.createHandlerForCurrentOrMainLooper(this::handleDownloadHelperCallbackMessage);
this.downloadHelperHandler = downloadThreadHandler;
mediaSourceThread = new HandlerThread("ExoPlayer: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, PlayerId.UNSET);
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(new LoadingInfo.Builder().setPlaybackPositionUs(0).build());
}
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:
try {
downloadHelper.onMediaPrepared();
} catch (ExoPlaybackException e) {
downloadHelperHandler
.obtainMessage(
DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED, /* obj= */ new IOException(e))
.sendToTarget();
}
return true;
case DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED:
release();
downloadHelper.onMediaPreparationFailed((IOException) castNonNull(msg.obj));
return true;
default:
return false;
}
}
}
private static final class DownloadTrackSelection extends BaseTrackSelection {
private static final class Factory implements ExoTrackSelection.Factory {
@Override
public @NullableType ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId,
Timeline timeline) {
@NullableType ExoTrackSelection[] selections = new ExoTrackSelection[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 @C.SelectionReason int getSelectionReason() {
return C.SELECTION_REASON_UNKNOWN;
}
@Override
@Nullable
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 FakeBandwidthMeter implements BandwidthMeter {
@Override
public long getBitrateEstimate() {
return 0;
}
@Override
@Nullable
public TransferListener getTransferListener() {
return null;
}
@Override
public void addEventListener(Handler eventHandler, EventListener eventListener) {
// Do nothing.
}
@Override
public void removeEventListener(EventListener eventListener) {
// Do nothing.
}
}
private static final class UnreleaseableRendererCapabilitiesList
implements RendererCapabilitiesList {
private final RendererCapabilities[] rendererCapabilities;
private UnreleaseableRendererCapabilitiesList(RendererCapabilities[] rendererCapabilities) {
this.rendererCapabilities = rendererCapabilities;
}
@Override
public RendererCapabilities[] getRendererCapabilities() {
return rendererCapabilities;
}
@Override
public int size() {
return rendererCapabilities.length;
}
@Override
public void release() {}
}
}