Merge pull request #2 from google/dev-v2

Pull from google/ExoPlayer dev-v2
This commit is contained in:
ybai001 2018-12-06 09:44:39 +08:00 committed by GitHub
commit 246d464466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 404 additions and 279 deletions

View file

@ -9,6 +9,9 @@
([#3314](https://github.com/google/ExoPlayer/issues/3314)).
* Do not retry failed loads whose error is `FileNotFoundException`.
* Prevent Cea608Decoder from generating Subtitles with null Cues list
* Caching: Cache data with unknown length by default. The previous flag to opt in
to this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been
replaced with an opt out flag (`DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN`).
### 2.9.2 ###

View file

@ -76,6 +76,12 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn
adUiViewGroup, eventHandler, eventListener);
}
@Override
@Nullable
public Object getTag() {
return adsMediaSource.getTag();
}
@Override
public void prepareSourceInternal(
final ExoPlayer player,

View file

@ -25,7 +25,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions;
/**
* Listener of audio {@link Renderer} events.
* Listener of audio {@link Renderer} events. All methods have no-op default implementations to
* allow selective overrides.
*/
public interface AudioRendererEventListener {
@ -35,14 +36,14 @@ public interface AudioRendererEventListener {
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
* remains enabled.
*/
void onAudioEnabled(DecoderCounters counters);
default void onAudioEnabled(DecoderCounters counters) {}
/**
* Called when the audio session is set.
*
* @param audioSessionId The audio session id.
*/
void onAudioSessionId(int audioSessionId);
default void onAudioSessionId(int audioSessionId) {}
/**
* Called when a decoder is created.
@ -52,15 +53,15 @@ public interface AudioRendererEventListener {
* finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/
void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs);
default void onAudioDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
*/
void onAudioInputFormatChanged(Format format);
default void onAudioInputFormatChanged(Format format) {}
/**
* Called when an {@link AudioSink} underrun occurs.
@ -71,14 +72,15 @@ public interface AudioRendererEventListener {
* as the buffered media can have a variable bitrate so the duration may be unknown.
* @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data.
*/
void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
default void onAudioSinkUnderrun(
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
/**
* Called when the renderer is disabled.
*
* @param counters {@link DecoderCounters} that were updated by the renderer.
*/
void onAudioDisabled(DecoderCounters counters);
default void onAudioDisabled(DecoderCounters counters) {}
/**
* Dispatches events to a {@link AudioRendererEventListener}.

View file

@ -15,15 +15,21 @@
*/
package com.google.android.exoplayer2.offline;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.List;
/** A helper for initializing and removing downloads. */
public abstract class DownloadHelper {
/**
* A helper for initializing and removing downloads.
*
* @param <T> The manifest type.
*/
public abstract class DownloadHelper<T> {
/** A callback to be notified when the {@link DownloadHelper} is prepared. */
public interface Callback {
@ -44,6 +50,26 @@ public abstract class DownloadHelper {
void onPrepareError(DownloadHelper helper, IOException e);
}
private final String downloadType;
private final Uri uri;
@Nullable private final String cacheKey;
@Nullable private T manifest;
@Nullable private TrackGroupArray[] trackGroupArrays;
/**
* Create download helper.
*
* @param downloadType A download type. This value will be used as {@link DownloadAction#type}.
* @param uri A {@link Uri}.
* @param cacheKey An optional cache key.
*/
public DownloadHelper(String downloadType, Uri uri, @Nullable String cacheKey) {
this.downloadType = downloadType;
this.uri = uri;
this.cacheKey = cacheKey;
}
/**
* Initializes the helper for starting a download.
*
@ -51,14 +77,15 @@ public abstract class DownloadHelper {
* will be invoked on the calling thread unless that thread does not have an associated {@link
* Looper}, in which case it will be called on the application's main thread.
*/
public void prepare(final Callback callback) {
public final void prepare(final Callback callback) {
final Handler handler =
new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
new Thread() {
@Override
public void run() {
try {
prepareInternal();
manifest = loadManifest(uri);
trackGroupArrays = getTrackGroupArrays(manifest);
handler.post(() -> callback.onPrepared(DownloadHelper.this));
} catch (final IOException e) {
handler.post(() -> callback.onPrepareError(DownloadHelper.this, e));
@ -67,18 +94,20 @@ public abstract class DownloadHelper {
}.start();
}
/**
* Called on a background thread during preparation.
*
* @throws IOException If preparation fails.
*/
protected abstract void prepareInternal() throws IOException;
/** Returns the manifest. Must not be called until after preparation completes. */
public final T getManifest() {
Assertions.checkNotNull(manifest);
return manifest;
}
/**
* Returns the number of periods for which media is available. Must not be called until after
* preparation completes.
*/
public abstract int getPeriodCount();
public final int getPeriodCount() {
Assertions.checkNotNull(trackGroupArrays);
return trackGroupArrays.length;
}
/**
* Returns the track groups for the given period. Must not be called until after preparation
@ -88,7 +117,10 @@ public abstract class DownloadHelper {
* @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream
* content.
*/
public abstract TrackGroupArray getTrackGroups(int periodIndex);
public final TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(trackGroupArrays);
return trackGroupArrays[periodIndex];
}
/**
* Builds a {@link DownloadAction} for downloading the specified tracks. Must not be called until
@ -98,12 +130,41 @@ public abstract class DownloadHelper {
* @param trackKeys The selected tracks. If empty, all streams will be downloaded.
* @return The built {@link DownloadAction}.
*/
public abstract DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys);
public final DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
downloadType, uri, toStreamKeys(trackKeys), cacheKey, data);
}
/**
* Builds a {@link DownloadAction} for removing the media. May be called in any state.
*
* @return The built {@link DownloadAction}.
*/
public abstract DownloadAction getRemoveAction();
public final DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(downloadType, uri, cacheKey);
}
/**
* Loads the manifest. This method is called on a background thread.
*
* @param uri The manifest uri.
* @throws IOException If loading fails.
*/
protected abstract T loadManifest(Uri uri) throws IOException;
/**
* Returns the track group arrays for each period in the manifest.
*
* @param manifest The manifest.
* @return An array of {@link TrackGroupArray}s. One for each period in the manifest.
*/
protected abstract TrackGroupArray[] getTrackGroupArrays(T manifest);
/**
* Converts a list of {@link TrackKey track keys} to {@link StreamKey stream keys}.
*
* @param trackKeys A list of track keys.
* @return A corresponding list of stream keys.
*/
protected abstract List<StreamKey> toStreamKeys(List<TrackKey> trackKeys);
}

View file

@ -565,7 +565,7 @@ public final class DownloadManager {
*/
@TargetState private volatile int targetState;
@MonotonicNonNull private volatile Downloader downloader;
@MonotonicNonNull private Downloader downloader;
@MonotonicNonNull private Thread thread;
@MonotonicNonNull private Throwable error;
@ -624,6 +624,7 @@ public final class DownloadManager {
state = STATE_STARTED;
targetState = STATE_COMPLETED;
downloadManager.onTaskStateChange(this);
downloader = downloaderFactory.createDownloader(action);
thread = new Thread(this);
thread.start();
}
@ -648,11 +649,7 @@ public final class DownloadManager {
private void stopDownloadThread(@TargetState int targetState) {
this.targetState = targetState;
// TODO: The possibility of downloader being null here may prevent the download thread from
// stopping in a timely way. Fix this.
if (downloader != null) {
downloader.cancel();
}
Assertions.checkNotNull(downloader).cancel();
Assertions.checkNotNull(thread).interrupt();
}
@ -675,7 +672,6 @@ public final class DownloadManager {
logd("Task is started", this);
Throwable error = null;
try {
downloader = downloaderFactory.createDownloader(action);
if (action.isRemoveAction) {
downloader.remove();
} else {

View file

@ -22,47 +22,28 @@ import java.util.Collections;
import java.util.List;
/** A {@link DownloadHelper} for progressive streams. */
public final class ProgressiveDownloadHelper extends DownloadHelper {
private final Uri uri;
private final @Nullable String customCacheKey;
public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
public ProgressiveDownloadHelper(Uri uri) {
this(uri, null);
}
public ProgressiveDownloadHelper(Uri uri, @Nullable String customCacheKey) {
this.uri = uri;
this.customCacheKey = customCacheKey;
super(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey);
}
@Override
protected void prepareInternal() {
// Do nothing.
protected Void loadManifest(Uri uri) {
return null;
}
@Override
public int getPeriodCount() {
return 1;
protected TrackGroupArray[] getTrackGroupArrays(Void manifest) {
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
return TrackGroupArray.EMPTY;
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri,
/* keys= */ Collections.emptyList(),
customCacheKey,
data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey);
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
return Collections.emptyList();
}
}

View file

@ -62,7 +62,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
private static final int BUFFER_SIZE_BYTES = 128 * 1024;
private final Uri manifestUri;
private final DataSpec manifestDataSpec;
private final Cache cache;
private final CacheDataSource dataSource;
private final CacheDataSource offlineDataSource;
@ -84,7 +84,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
*/
public SegmentDownloader(
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
this.manifestUri = manifestUri;
this.manifestDataSpec = getCompressibleDataSpec(manifestUri);
this.streamKeys = new ArrayList<>(streamKeys);
this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.createCacheDataSource();
@ -171,7 +171,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
@Override
public final void remove() throws InterruptedException {
try {
M manifest = getManifest(offlineDataSource, manifestUri);
M manifest = getManifest(offlineDataSource, manifestDataSpec);
List<Segment> segments = getSegments(offlineDataSource, manifest, true);
for (int i = 0; i < segments.size(); i++) {
removeDataSpec(segments.get(i).dataSpec);
@ -180,7 +180,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Ignore exceptions when removing.
} finally {
// Always attempt to remove the manifest.
removeDataSpec(new DataSpec(manifestUri));
removeDataSpec(manifestDataSpec);
}
}
@ -190,11 +190,11 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
* Loads and parses the manifest.
*
* @param dataSource The {@link DataSource} through which to load.
* @param uri The manifest uri.
* @param dataSpec The manifest {@link DataSpec}.
* @return The manifest.
* @throws IOException If an error occurs reading data.
*/
protected abstract M getManifest(DataSource dataSource, Uri uri) throws IOException;
protected abstract M getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException;
/**
* Returns a list of all downloadable {@link Segment}s for a given manifest.
@ -217,7 +217,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
@SuppressWarnings("NonAtomicVolatileUpdate")
private List<Segment> initDownload() throws IOException, InterruptedException {
M manifest = getManifest(dataSource, manifestUri);
M manifest = getManifest(dataSource, manifestDataSpec);
if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys);
}
@ -252,4 +252,12 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
CacheUtil.remove(dataSpec, cache, cacheKeyFactory);
}
protected static DataSpec getCompressibleDataSpec(Uri uri) {
return new DataSpec(
uri,
/* absoluteStreamPosition= */ 0,
/* length= */ C.LENGTH_UNSET,
/* key= */ null,
/* flags= */ DataSpec.FLAG_ALLOW_GZIP);
}
}

View file

@ -186,6 +186,12 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
window = new Timeline.Window();
}
@Override
@Nullable
public Object getTag() {
return mediaSource.getTag();
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -453,6 +453,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
}
@Override
@Nullable
public Object getTag() {
return null;
}
@Override
public final synchronized void prepareSourceInternal(
ExoPlayer player,
@ -1069,6 +1075,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
// Do nothing.
}
@Override
@Nullable
public Object getTag() {
return null;
}
@Override
protected void releaseSourceInternal() {
// Do nothing.

View file

@ -850,6 +850,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private DataSpec dataSpec;
private long length;
@SuppressWarnings("method.invocation.invalid")
public ExtractingLoadable(
Uri uri,
DataSource dataSource,
@ -864,7 +865,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNSET;
dataSpec = new DataSpec(uri, positionHolder.position, C.LENGTH_UNSET, customCacheKey);
dataSpec = buildDataSpec(/* position= */ 0);
}
// Loadable implementation.
@ -881,7 +882,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
ExtractorInput input = null;
try {
long position = positionHolder.position;
dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, customCacheKey);
dataSpec = buildDataSpec(position);
length = dataSource.open(dataSpec);
if (length != C.LENGTH_UNSET) {
length += position;
@ -915,6 +916,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// Internal methods.
private DataSpec buildDataSpec(long position) {
// Disable caching if the content length cannot be resolved, since this is indicative of a
// progressive live stream.
return new DataSpec(
uri,
position,
C.LENGTH_UNSET,
customCacheKey,
DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
}
private void setLoadPosition(long position, long timeUs) {
positionHolder.position = position;
seekTimeUs = timeUs;

View file

@ -358,6 +358,12 @@ public final class ExtractorMediaSource extends BaseMediaSource
this.tag = tag;
}
@Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -64,6 +64,12 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
mediaPeriodToChildMediaPeriodId = new HashMap<>();
}
@Override
@Nullable
public Object getTag() {
return childSource.getTag();
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -219,6 +219,12 @@ public interface MediaSource {
*/
void removeEventListener(MediaSourceEventListener eventListener);
/** Returns the tag set on the media source, or null if none was set. */
@Nullable
default Object getTag() {
return null;
}
/**
* Starts source preparation if not yet started, and adds a listener for timeline and/or manifest
* updates.

View file

@ -98,6 +98,12 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
timelines = new Timeline[mediaSources.length];
}
@Override
@Nullable
public Object getTag() {
return mediaSources.length > 0 ? mediaSources[0].getTag() : null;
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -185,6 +185,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final boolean treatLoadErrorsAsEndOfStream;
private final Timeline timeline;
@Nullable private final Object tag;
private @Nullable TransferListener transferListener;
@ -287,14 +288,20 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
this.durationUs = durationUs;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
dataSpec =
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
this.tag = tag;
dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
timeline =
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false, tag);
}
// MediaSource implementation.
@Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -319,6 +319,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes());
}
@Override
@Nullable
public Object getTag() {
return contentMediaSource.getTag();
}
@Override
public void prepareSourceInternal(
final ExoPlayer player,

View file

@ -560,6 +560,14 @@ public final class Cea608Decoder extends CeaDecoder {
int oldCaptionMode = this.captionMode;
this.captionMode = captionMode;
if (captionMode == CC_MODE_PAINT_ON) {
// Switching to paint-on mode should have no effect except to select the mode.
for (int i = 0; i < cueBuilders.size(); i++) {
cueBuilders.get(i).setCaptionMode(captionMode);
}
return;
}
// Clear the working memory.
resetCueBuilders();
if (oldCaptionMode == CC_MODE_PAINT_ON || captionMode == CC_MODE_ROLL_UP
@ -664,6 +672,10 @@ public final class Cea608Decoder extends CeaDecoder {
tabOffset = 0;
}
public void setCaptionMode(int captionMode) {
this.captionMode = captionMode;
}
public void setCaptionRowCount(int captionRowCount) {
this.captionRowCount = captionRowCount;
}

View file

@ -32,32 +32,29 @@ public final class DataSpec {
/**
* The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP}
* and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}.
* and {@link #FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH})
value = {FLAG_ALLOW_GZIP, FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN})
public @interface Flags {}
/**
* Permits an underlying network stack to request that the server use gzip compression.
* <p>
* Should not typically be set if the data being requested is already compressed (e.g. most audio
* and video requests). May be set when requesting other data.
* <p>
* When a {@link DataSource} is used to request data with this flag set, and if the
* {@link DataSource} does make a network request, then the value returned from
* {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from
* {@link DataSource#read(byte[], int, int)} will be the decompressed data.
* Allows an underlying network stack to request that the server use gzip compression.
*
* <p>Should not typically be set if the data being requested is already compressed (e.g. most
* audio and video requests). May be set when requesting other data.
*
* <p>When a {@link DataSource} is used to request data with this flag set, and if the {@link
* DataSource} does make a network request, then the value returned from {@link
* DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from {@link
* DataSource#read(byte[], int, int)} will be the decompressed data.
*/
public static final int FLAG_ALLOW_GZIP = 1;
/**
* Permits content to be cached even if its length can not be resolved. Typically this's the case
* for progressive live streams and when {@link #FLAG_ALLOW_GZIP} is used.
*/
public static final int FLAG_ALLOW_CACHING_UNKNOWN_LENGTH = 1 << 1; // 2
/** Prevents caching if the length cannot be resolved when the {@link DataSource} is opened. */
public static final int FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN = 1 << 1; // 2
/**
* The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link

View file

@ -69,6 +69,24 @@ public final class ParsingLoadable<T> implements Loadable {
return Assertions.checkNotNull(loadable.getResult());
}
/**
* Loads a single parsable object.
*
* @param dataSource The {@link DataSource} through which the object should be read.
* @param parser The {@link Parser} to parse the object from the response.
* @param dataSpec The {@link DataSpec} of the object to read.
* @param type The type of the data. One of the {@link C}{@code DATA_TYPE_*} constants.
* @return The parsed object
* @throws IOException Thrown if there is an error while loading or parsing.
*/
public static <T> T load(
DataSource dataSource, Parser<? extends T> parser, DataSpec dataSpec, int type)
throws IOException {
ParsingLoadable<T> loadable = new ParsingLoadable<>(dataSource, dataSpec, type, parser);
loadable.load();
return Assertions.checkNotNull(loadable.getResult());
}
/**
* The {@link DataSpec} that defines the data to be loaded.
*/
@ -91,11 +109,7 @@ public final class ParsingLoadable<T> implements Loadable {
* @param parser Parses the object from the response.
*/
public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<? extends T> parser) {
this(
dataSource,
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH),
type,
parser);
this(dataSource, new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP), type, parser);
}
/**

View file

@ -121,7 +121,7 @@ public final class CacheDataSink implements DataSink {
@Override
public void open(DataSpec dataSpec) throws CacheDataSinkException {
if (dataSpec.length == C.LENGTH_UNSET
&& !dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)) {
&& dataSpec.isFlagSet(DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN)) {
this.dataSpec = null;
return;
}

View file

@ -268,7 +268,7 @@ public final class CacheUtil {
dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition,
C.LENGTH_UNSET,
dataSpec.key,
dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
dataSpec.flags);
long resolvedLength = dataSource.open(dataSpec);
if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength;

View file

@ -48,8 +48,15 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@ -1842,6 +1849,32 @@ public final class Util {
return displaySize;
}
/**
* Extract renderer capabilities for the renderers created by the provided renderers factory.
*
* @param renderersFactory A {@link RenderersFactory}.
* @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers.
* @return The {@link RendererCapabilities} for each renderer created by the {@code
* renderersFactory}.
*/
public static RendererCapabilities[] getRendererCapabilities(
RenderersFactory renderersFactory,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
Renderer[] renderers =
renderersFactory.createRenderers(
new Handler(),
new VideoRendererEventListener() {},
new AudioRendererEventListener() {},
(cues) -> {},
(metadata) -> {},
drmSessionManager);
RendererCapabilities[] capabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) {
capabilities[i] = renderers[i].getCapabilities();
}
return capabilities;
}
@Nullable
private static String getSystemProperty(String name) {
try {

View file

@ -26,7 +26,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions;
/**
* Listener of video {@link Renderer} events.
* Listener of video {@link Renderer} events. All methods have no-op default implementations to
* allow selective overrides.
*/
public interface VideoRendererEventListener {
@ -36,7 +37,7 @@ public interface VideoRendererEventListener {
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
* remains enabled.
*/
void onVideoEnabled(DecoderCounters counters);
default void onVideoEnabled(DecoderCounters counters) {}
/**
* Called when a decoder is created.
@ -46,15 +47,15 @@ public interface VideoRendererEventListener {
* finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/
void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs);
default void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
*/
void onVideoInputFormatChanged(Format format);
default void onVideoInputFormatChanged(Format format) {}
/**
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
@ -62,12 +63,11 @@ public interface VideoRendererEventListener {
* reaches a specified threshold whilst the renderer is started.
*
* @param count The number of dropped frames.
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This
* duration is timed from when the renderer was started or from when dropped frames were
* last reported (whichever was more recent), and not from when the first of the reported
* drops occurred.
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
* is timed from when the renderer was started or from when dropped frames were last reported
* (whichever was more recent), and not from when the first of the reported drops occurred.
*/
void onDroppedFrames(int count, long elapsedMs);
default void onDroppedFrames(int count, long elapsedMs) {}
/**
* Called before a frame is rendered for the first time since setting the surface, and each time
@ -82,12 +82,12 @@ public interface VideoRendererEventListener {
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
* rotated videos can safely ignore this parameter.
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case
* of square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
* square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content.
*/
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio);
default void onVideoSizeChanged(
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {}
/**
* Called when a frame is rendered for the first time since setting the surface, and when a frame
@ -96,14 +96,14 @@ public interface VideoRendererEventListener {
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if
* the renderer renders to something that isn't a {@link Surface}.
*/
void onRenderedFirstFrame(@Nullable Surface surface);
default void onRenderedFirstFrame(@Nullable Surface surface) {}
/**
* Called when the renderer is disabled.
*
* @param counters {@link DecoderCounters} that were updated by the renderer.
*/
void onVideoDisabled(DecoderCounters counters);
default void onVideoDisabled(DecoderCounters counters) {}
/**
* Dispatches events to a {@link VideoRendererEventListener}.

View file

@ -602,7 +602,6 @@ public final class CacheDataSourceTest {
}
private DataSpec buildDataSpec(long position, long length, @Nullable String key) {
return new DataSpec(
testDataUri, position, length, key, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
return new DataSpec(testDataUri, position, length, key);
}
}

View file

@ -607,6 +607,12 @@ public final class DashMediaSource extends BaseMediaSource {
// MediaSource implementation.
@Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.dash.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.DownloadAction;
@ -31,74 +30,49 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for DASH streams. */
public final class DashDownloadHelper extends DownloadHelper {
public final class DashDownloadHelper extends DownloadHelper<DashManifest> {
private final Uri uri;
private final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull DashManifest manifest;
public DashDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri;
super(DownloadAction.TYPE_DASH, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected void prepareInternal() throws IOException {
protected DashManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
manifest =
ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
/** Returns the DASH manifest. Must not be called until after preparation completes. */
public DashManifest getManifest() {
Assertions.checkNotNull(manifest);
return manifest;
return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(manifest);
return manifest.getPeriodCount();
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(manifest);
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()];
for (int i = 0; i < trackGroups.length; i++) {
List<Representation> representations = adaptationSets.get(i).representations;
Format[] formats = new Format[representations.size()];
int representationsCount = representations.size();
for (int j = 0; j < representationsCount; j++) {
formats[j] = representations.get(j).format;
public TrackGroupArray[] getTrackGroupArrays(DashManifest manifest) {
int periodCount = manifest.getPeriodCount();
TrackGroupArray[] trackGroupArrays = new TrackGroupArray[periodCount];
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()];
for (int i = 0; i < trackGroups.length; i++) {
List<Representation> representations = adaptationSets.get(i).representations;
Format[] formats = new Format[representations.size()];
int representationsCount = representations.size();
for (int j = 0; j < representationsCount; j++) {
formats[j] = representations.get(j).format;
}
trackGroups[i] = new TrackGroup(formats);
}
trackGroups[i] = new TrackGroup(formats);
trackGroupArrays[periodIndex] = new TrackGroupArray(trackGroups);
}
return new TrackGroupArray(trackGroups);
return trackGroupArrays;
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_DASH, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null);
}
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
List<StreamKey> streamKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);

View file

@ -28,11 +28,13 @@ import com.google.android.exoplayer2.source.dash.DashUtil;
import com.google.android.exoplayer2.source.dash.DashWrappingSegmentIndex;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.dash.manifest.Period;
import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -73,8 +75,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
}
@Override
protected DashManifest getManifest(DataSource dataSource, Uri uri) throws IOException {
return DashUtil.loadManifest(dataSource, uri);
protected DashManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return ParsingLoadable.load(
dataSource, new DashManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
@Override
@ -121,8 +124,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
if (!allowIncompleteList) {
throw e;
}
// Loading failed, but generating an incomplete segment list is allowed. Advance to the next
// representation.
// Generating an incomplete segment list is allowed. Advance to the next representation.
continue;
}

View file

@ -390,6 +390,12 @@ public final class HlsMediaSource extends BaseMediaSource
this.tag = tag;
}
@Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.hls.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.DownloadAction;
@ -36,46 +35,31 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for HLS streams. */
public final class HlsDownloadHelper extends DownloadHelper {
public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> {
private final Uri uri;
private final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull HlsPlaylist playlist;
private int[] renditionGroups;
public HlsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri;
super(DownloadAction.TYPE_HLS, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected void prepareInternal() throws IOException {
protected HlsPlaylist loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
playlist = ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
}
/** Returns the HLS playlist. Must not be called until after preparation completes. */
public HlsPlaylist getPlaylist() {
Assertions.checkNotNull(playlist);
return playlist;
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(playlist);
return 1;
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
protected TrackGroupArray[] getTrackGroupArrays(HlsPlaylist playlist) {
Assertions.checkNotNull(playlist);
if (playlist instanceof HlsMediaPlaylist) {
renditionGroups = new int[0];
return TrackGroupArray.EMPTY;
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
// TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction.
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
@ -94,24 +78,18 @@ public final class HlsDownloadHelper extends DownloadHelper {
renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE;
trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles));
}
return new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex));
return new TrackGroupArray[] {new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex))};
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
Assertions.checkNotNull(renditionGroups);
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_HLS,
uri,
toStreamKeys(trackKeys, renditionGroups),
/* customCacheKey= */ null,
data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_HLS, uri, /* customCacheKey= */ null);
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);
representationKeys.add(
new StreamKey(renditionGroups[trackKey.groupIndex], trackKey.trackIndex));
}
return representationKeys;
}
private static Format[] toFormats(List<HlsMasterPlaylist.HlsUrl> hlsUrls) {
@ -121,13 +99,4 @@ public final class HlsDownloadHelper extends DownloadHelper {
}
return formats;
}
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys, int[] groups) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);
representationKeys.add(new StreamKey(groups[trackKey.groupIndex], trackKey.trackIndex));
}
return representationKeys;
}
}

View file

@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
}
@Override
protected HlsPlaylist getManifest(DataSource dataSource, Uri uri) throws IOException {
return loadManifest(dataSource, uri);
protected HlsPlaylist getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return loadManifest(dataSource, dataSpec);
}
@Override
protected List<Segment> getSegments(
DataSource dataSource, HlsPlaylist playlist, boolean allowIncompleteList) throws IOException {
ArrayList<Uri> mediaPlaylistUris = new ArrayList<>();
String baseUri = playlist.baseUri;
ArrayList<DataSpec> mediaPlaylistDataSpecs = new ArrayList<>();
if (playlist instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.variants, mediaPlaylistUris);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.audios, mediaPlaylistUris);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.subtitles, mediaPlaylistUris);
addMediaPlaylistDataSpecs(baseUri, masterPlaylist.variants, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(baseUri, masterPlaylist.audios, mediaPlaylistDataSpecs);
addMediaPlaylistDataSpecs(baseUri, masterPlaylist.subtitles, mediaPlaylistDataSpecs);
} else {
mediaPlaylistUris.add(Uri.parse(playlist.baseUri));
mediaPlaylistDataSpecs.add(SegmentDownloader.getCompressibleDataSpec(Uri.parse(baseUri)));
}
ArrayList<Segment> segments = new ArrayList<>();
ArrayList<Segment> segments = new ArrayList<>();
HashSet<Uri> seenEncryptionKeyUris = new HashSet<>();
for (Uri mediaPlaylistUri : mediaPlaylistUris) {
for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) {
segments.add(new Segment(/* startTimeUs= */ 0, mediaPlaylistDataSpec));
HlsMediaPlaylist mediaPlaylist;
try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistUri);
segments.add(new Segment(mediaPlaylist.startTimeUs, new DataSpec(mediaPlaylistUri)));
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistDataSpec);
} catch (IOException e) {
if (!allowIncompleteList) {
throw e;
}
segments.add(new Segment(0, new DataSpec(mediaPlaylistUri)));
// Generating an incomplete segment list is allowed. Advance to the next media playlist.
continue;
}
HlsMediaPlaylist.Segment lastInitSegment = null;
@ -109,39 +111,43 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
if (initSegment != null && initSegment != lastInitSegment) {
lastInitSegment = initSegment;
addSegment(segments, mediaPlaylist, initSegment, seenEncryptionKeyUris);
addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments);
}
addSegment(segments, mediaPlaylist, segment, seenEncryptionKeyUris);
addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments);
}
}
return segments;
}
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException {
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
private void addMediaPlaylistDataSpecs(String baseUri, List<HlsUrl> urls, List<DataSpec> out) {
for (int i = 0; i < urls.size(); i++) {
Uri playlistUri = UriUtil.resolveToUri(baseUri, urls.get(i).url);
out.add(SegmentDownloader.getCompressibleDataSpec(playlistUri));
}
}
private static void addSegment(
ArrayList<Segment> segments,
private static HlsPlaylist loadManifest(DataSource dataSource, DataSpec dataSpec)
throws IOException {
return ParsingLoadable.load(
dataSource, new HlsPlaylistParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
private void addSegment(
HlsMediaPlaylist mediaPlaylist,
HlsMediaPlaylist.Segment hlsSegment,
HashSet<Uri> seenEncryptionKeyUris) {
long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs;
if (hlsSegment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri,
hlsSegment.fullSegmentEncryptionKeyUri);
HlsMediaPlaylist.Segment segment,
HashSet<Uri> seenEncryptionKeyUris,
ArrayList<Segment> out) {
String baseUri = mediaPlaylist.baseUri;
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
if (segment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(baseUri, segment.fullSegmentEncryptionKeyUri);
if (seenEncryptionKeyUris.add(keyUri)) {
segments.add(new Segment(startTimeUs, new DataSpec(keyUri)));
out.add(new Segment(startTimeUs, SegmentDownloader.getCompressibleDataSpec(keyUri)));
}
}
Uri resolvedUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, hlsSegment.url);
segments.add(new Segment(startTimeUs,
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null)));
}
private static void addResolvedUris(String baseUri, List<HlsUrl> urls, List<Uri> out) {
for (int i = 0; i < urls.size(); i++) {
out.add(UriUtil.resolveToUri(baseUri, urls.get(i).url));
}
Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
DataSpec dataSpec =
new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null);
out.add(new Segment(startTimeUs, dataSpec));
}
}

View file

@ -503,6 +503,12 @@ public final class SsMediaSource extends BaseMediaSource
// MediaSource implementation.
@Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal(
ExoPlayer player,

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
@ -28,67 +27,38 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for SmoothStreaming streams. */
public final class SsDownloadHelper extends DownloadHelper {
public final class SsDownloadHelper extends DownloadHelper<SsManifest> {
private final Uri uri;
private final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull SsManifest manifest;
public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri;
super(DownloadAction.TYPE_SS, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected void prepareInternal() throws IOException {
protected SsManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
manifest = ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
/** Returns the SmoothStreaming manifest. Must not be called until after preparation completes. */
public SsManifest getManifest() {
Assertions.checkNotNull(manifest);
return manifest;
return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(manifest);
return 1;
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(manifest);
protected TrackGroupArray[] getTrackGroupArrays(SsManifest manifest) {
SsManifest.StreamElement[] streamElements = manifest.streamElements;
TrackGroup[] trackGroups = new TrackGroup[streamElements.length];
for (int i = 0; i < streamElements.length; i++) {
trackGroups[i] = new TrackGroup(streamElements[i].formats);
}
return new TrackGroupArray(trackGroups);
return new TrackGroupArray[] {new TrackGroupArray(trackGroups)};
}
@Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_SS, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data);
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_SS, uri, /* customCacheKey= */ null);
}
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i);

View file

@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
}
@Override
protected SsManifest getManifest(DataSource dataSource, Uri uri) throws IOException {
return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST);
protected SsManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return ParsingLoadable.load(dataSource, new SsManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
@Override

View file

@ -88,6 +88,13 @@ public class FakeMediaSource extends BaseMediaSource {
this.trackGroupArray = trackGroupArray;
}
@Override
@Nullable
public Object getTag() {
boolean hasTimeline = timeline != null && !timeline.isEmpty();
return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null;
}
@Override
public synchronized void prepareSourceInternal(
ExoPlayer player,

View file

@ -83,7 +83,7 @@ public final class CacheAsserts {
* @throws IOException If an error occurred reading from the Cache.
*/
public static void assertDataCached(Cache cache, Uri uri, byte[] expected) throws IOException {
DataSpec dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
DataSpec dataSpec = new DataSpec(uri);
assertDataCached(cache, dataSpec, expected);
}