Commit 246d4644 by ybai001 Committed by GitHub

Merge pull request #2 from google/dev-v2

Pull from google/ExoPlayer dev-v2
parents 7a924d64 556dd7e9
Showing with 404 additions and 279 deletions
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
([#3314](https://github.com/google/ExoPlayer/issues/3314)). ([#3314](https://github.com/google/ExoPlayer/issues/3314)).
* Do not retry failed loads whose error is `FileNotFoundException`. * Do not retry failed loads whose error is `FileNotFoundException`.
* Prevent Cea608Decoder from generating Subtitles with null Cues list * 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 ### ### 2.9.2 ###
......
...@@ -77,6 +77,12 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn ...@@ -77,6 +77,12 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn
} }
@Override @Override
@Nullable
public Object getTag() {
return adsMediaSource.getTag();
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
final ExoPlayer player, final ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -25,7 +25,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; ...@@ -25,7 +25,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions; 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 { public interface AudioRendererEventListener {
...@@ -35,14 +36,14 @@ 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 * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
* remains enabled. * remains enabled.
*/ */
void onAudioEnabled(DecoderCounters counters); default void onAudioEnabled(DecoderCounters counters) {}
/** /**
* Called when the audio session is set. * Called when the audio session is set.
* *
* @param audioSessionId The audio session id. * @param audioSessionId The audio session id.
*/ */
void onAudioSessionId(int audioSessionId); default void onAudioSessionId(int audioSessionId) {}
/** /**
* Called when a decoder is created. * Called when a decoder is created.
...@@ -52,15 +53,15 @@ public interface AudioRendererEventListener { ...@@ -52,15 +53,15 @@ public interface AudioRendererEventListener {
* finished. * finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/ */
void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, default void onAudioDecoderInitialized(
long initializationDurationMs); String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/** /**
* Called when the format of the media being consumed by the renderer changes. * Called when the format of the media being consumed by the renderer changes.
* *
* @param format The new format. * @param format The new format.
*/ */
void onAudioInputFormatChanged(Format format); default void onAudioInputFormatChanged(Format format) {}
/** /**
* Called when an {@link AudioSink} underrun occurs. * Called when an {@link AudioSink} underrun occurs.
...@@ -71,14 +72,15 @@ public interface AudioRendererEventListener { ...@@ -71,14 +72,15 @@ public interface AudioRendererEventListener {
* as the buffered media can have a variable bitrate so the duration may be unknown. * 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. * @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. * Called when the renderer is disabled.
* *
* @param counters {@link DecoderCounters} that were updated by the renderer. * @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}. * Dispatches events to a {@link AudioRendererEventListener}.
......
...@@ -15,15 +15,21 @@ ...@@ -15,15 +15,21 @@
*/ */
package com.google.android.exoplayer2.offline; package com.google.android.exoplayer2.offline;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.List; 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. */ /** A callback to be notified when the {@link DownloadHelper} is prepared. */
public interface Callback { public interface Callback {
...@@ -44,6 +50,26 @@ public abstract class DownloadHelper { ...@@ -44,6 +50,26 @@ public abstract class DownloadHelper {
void onPrepareError(DownloadHelper helper, IOException e); void onPrepareError(DownloadHelper helper, IOException e);
} }
private final String downloadType;
private final Uri uri;
@Nullable private final String cacheKey;
@Nullable private 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. * Initializes the helper for starting a download.
* *
...@@ -51,14 +77,15 @@ public abstract class DownloadHelper { ...@@ -51,14 +77,15 @@ public abstract class DownloadHelper {
* will be invoked on the calling thread unless that thread does not have an associated {@link * 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. * 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 = final Handler handler =
new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper()); new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
try { try {
prepareInternal(); manifest = loadManifest(uri);
trackGroupArrays = getTrackGroupArrays(manifest);
handler.post(() -> callback.onPrepared(DownloadHelper.this)); handler.post(() -> callback.onPrepared(DownloadHelper.this));
} catch (final IOException e) { } catch (final IOException e) {
handler.post(() -> callback.onPrepareError(DownloadHelper.this, e)); handler.post(() -> callback.onPrepareError(DownloadHelper.this, e));
...@@ -67,18 +94,20 @@ public abstract class DownloadHelper { ...@@ -67,18 +94,20 @@ public abstract class DownloadHelper {
}.start(); }.start();
} }
/** /** Returns the manifest. Must not be called until after preparation completes. */
* Called on a background thread during preparation. public final T getManifest() {
* Assertions.checkNotNull(manifest);
* @throws IOException If preparation fails. return manifest;
*/ }
protected abstract void prepareInternal() throws IOException;
/** /**
* Returns the number of periods for which media is available. Must not be called until after * Returns the number of periods for which media is available. Must not be called until after
* preparation completes. * 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 * Returns the track groups for the given period. Must not be called until after preparation
...@@ -88,7 +117,10 @@ public abstract class DownloadHelper { ...@@ -88,7 +117,10 @@ public abstract class DownloadHelper {
* @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream * @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream
* content. * 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 * Builds a {@link DownloadAction} for downloading the specified tracks. Must not be called until
...@@ -98,12 +130,41 @@ public abstract class DownloadHelper { ...@@ -98,12 +130,41 @@ public abstract class DownloadHelper {
* @param trackKeys The selected tracks. If empty, all streams will be downloaded. * @param trackKeys The selected tracks. If empty, all streams will be downloaded.
* @return The built {@link DownloadAction}. * @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. * Builds a {@link DownloadAction} for removing the media. May be called in any state.
* *
* @return The built {@link DownloadAction}. * @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);
} }
...@@ -565,7 +565,7 @@ public final class DownloadManager { ...@@ -565,7 +565,7 @@ public final class DownloadManager {
*/ */
@TargetState private volatile int targetState; @TargetState private volatile int targetState;
@MonotonicNonNull private volatile Downloader downloader; @MonotonicNonNull private Downloader downloader;
@MonotonicNonNull private Thread thread; @MonotonicNonNull private Thread thread;
@MonotonicNonNull private Throwable error; @MonotonicNonNull private Throwable error;
...@@ -624,6 +624,7 @@ public final class DownloadManager { ...@@ -624,6 +624,7 @@ public final class DownloadManager {
state = STATE_STARTED; state = STATE_STARTED;
targetState = STATE_COMPLETED; targetState = STATE_COMPLETED;
downloadManager.onTaskStateChange(this); downloadManager.onTaskStateChange(this);
downloader = downloaderFactory.createDownloader(action);
thread = new Thread(this); thread = new Thread(this);
thread.start(); thread.start();
} }
...@@ -648,11 +649,7 @@ public final class DownloadManager { ...@@ -648,11 +649,7 @@ public final class DownloadManager {
private void stopDownloadThread(@TargetState int targetState) { private void stopDownloadThread(@TargetState int targetState) {
this.targetState = targetState; this.targetState = targetState;
// TODO: The possibility of downloader being null here may prevent the download thread from Assertions.checkNotNull(downloader).cancel();
// stopping in a timely way. Fix this.
if (downloader != null) {
downloader.cancel();
}
Assertions.checkNotNull(thread).interrupt(); Assertions.checkNotNull(thread).interrupt();
} }
...@@ -675,7 +672,6 @@ public final class DownloadManager { ...@@ -675,7 +672,6 @@ public final class DownloadManager {
logd("Task is started", this); logd("Task is started", this);
Throwable error = null; Throwable error = null;
try { try {
downloader = downloaderFactory.createDownloader(action);
if (action.isRemoveAction) { if (action.isRemoveAction) {
downloader.remove(); downloader.remove();
} else { } else {
......
...@@ -22,47 +22,28 @@ import java.util.Collections; ...@@ -22,47 +22,28 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** A {@link DownloadHelper} for progressive streams. */ /** A {@link DownloadHelper} for progressive streams. */
public final class ProgressiveDownloadHelper extends DownloadHelper { public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
private final Uri uri;
private final @Nullable String customCacheKey;
public ProgressiveDownloadHelper(Uri uri) { public ProgressiveDownloadHelper(Uri uri) {
this(uri, null); this(uri, null);
} }
public ProgressiveDownloadHelper(Uri uri, @Nullable String customCacheKey) { public ProgressiveDownloadHelper(Uri uri, @Nullable String customCacheKey) {
this.uri = uri; super(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey);
this.customCacheKey = customCacheKey;
}
@Override
protected void prepareInternal() {
// Do nothing.
}
@Override
public int getPeriodCount() {
return 1;
} }
@Override @Override
public TrackGroupArray getTrackGroups(int periodIndex) { protected Void loadManifest(Uri uri) {
return TrackGroupArray.EMPTY; return null;
} }
@Override @Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) { protected TrackGroupArray[] getTrackGroupArrays(Void manifest) {
return DownloadAction.createDownloadAction( return new TrackGroupArray[] {TrackGroupArray.EMPTY};
DownloadAction.TYPE_PROGRESSIVE,
uri,
/* keys= */ Collections.emptyList(),
customCacheKey,
data);
} }
@Override @Override
public DownloadAction getRemoveAction() { protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
return DownloadAction.createRemoveAction(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey); return Collections.emptyList();
} }
} }
...@@ -62,7 +62,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -62,7 +62,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
private static final int BUFFER_SIZE_BYTES = 128 * 1024; private static final int BUFFER_SIZE_BYTES = 128 * 1024;
private final Uri manifestUri; private final DataSpec manifestDataSpec;
private final Cache cache; private final Cache cache;
private final CacheDataSource dataSource; private final CacheDataSource dataSource;
private final CacheDataSource offlineDataSource; private final CacheDataSource offlineDataSource;
...@@ -84,7 +84,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -84,7 +84,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
*/ */
public SegmentDownloader( public SegmentDownloader(
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) { Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
this.manifestUri = manifestUri; this.manifestDataSpec = getCompressibleDataSpec(manifestUri);
this.streamKeys = new ArrayList<>(streamKeys); this.streamKeys = new ArrayList<>(streamKeys);
this.cache = constructorHelper.getCache(); this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.createCacheDataSource(); this.dataSource = constructorHelper.createCacheDataSource();
...@@ -171,7 +171,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -171,7 +171,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
@Override @Override
public final void remove() throws InterruptedException { public final void remove() throws InterruptedException {
try { try {
M manifest = getManifest(offlineDataSource, manifestUri); M manifest = getManifest(offlineDataSource, manifestDataSpec);
List<Segment> segments = getSegments(offlineDataSource, manifest, true); List<Segment> segments = getSegments(offlineDataSource, manifest, true);
for (int i = 0; i < segments.size(); i++) { for (int i = 0; i < segments.size(); i++) {
removeDataSpec(segments.get(i).dataSpec); removeDataSpec(segments.get(i).dataSpec);
...@@ -180,7 +180,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -180,7 +180,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Ignore exceptions when removing. // Ignore exceptions when removing.
} finally { } finally {
// Always attempt to remove the manifest. // 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 ...@@ -190,11 +190,11 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
* Loads and parses the manifest. * Loads and parses the manifest.
* *
* @param dataSource The {@link DataSource} through which to load. * @param dataSource The {@link DataSource} through which to load.
* @param uri The manifest uri. * @param dataSpec The manifest {@link DataSpec}.
* @return The manifest. * @return The manifest.
* @throws IOException If an error occurs reading data. * @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. * 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 ...@@ -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(). // Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
@SuppressWarnings("NonAtomicVolatileUpdate") @SuppressWarnings("NonAtomicVolatileUpdate")
private List<Segment> initDownload() throws IOException, InterruptedException { private List<Segment> initDownload() throws IOException, InterruptedException {
M manifest = getManifest(dataSource, manifestUri); M manifest = getManifest(dataSource, manifestDataSpec);
if (!streamKeys.isEmpty()) { if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys); manifest = manifest.copy(streamKeys);
} }
...@@ -252,4 +252,12 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme ...@@ -252,4 +252,12 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
CacheUtil.remove(dataSpec, cache, cacheKeyFactory); 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);
}
} }
...@@ -187,6 +187,12 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> { ...@@ -187,6 +187,12 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
} }
@Override @Override
@Nullable
public Object getTag() {
return mediaSource.getTag();
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -454,6 +454,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -454,6 +454,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
} }
@Override @Override
@Nullable
public Object getTag() {
return null;
}
@Override
public final synchronized void prepareSourceInternal( public final synchronized void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
...@@ -1070,6 +1076,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -1070,6 +1076,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
} }
@Override @Override
@Nullable
public Object getTag() {
return null;
}
@Override
protected void releaseSourceInternal() { protected void releaseSourceInternal() {
// Do nothing. // Do nothing.
} }
......
...@@ -850,6 +850,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -850,6 +850,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private DataSpec dataSpec; private DataSpec dataSpec;
private long length; private long length;
@SuppressWarnings("method.invocation.invalid")
public ExtractingLoadable( public ExtractingLoadable(
Uri uri, Uri uri,
DataSource dataSource, DataSource dataSource,
...@@ -864,7 +865,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -864,7 +865,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.positionHolder = new PositionHolder(); this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true; this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNSET; this.length = C.LENGTH_UNSET;
dataSpec = new DataSpec(uri, positionHolder.position, C.LENGTH_UNSET, customCacheKey); dataSpec = buildDataSpec(/* position= */ 0);
} }
// Loadable implementation. // Loadable implementation.
...@@ -881,7 +882,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -881,7 +882,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
ExtractorInput input = null; ExtractorInput input = null;
try { try {
long position = positionHolder.position; long position = positionHolder.position;
dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, customCacheKey); dataSpec = buildDataSpec(position);
length = dataSource.open(dataSpec); length = dataSource.open(dataSpec);
if (length != C.LENGTH_UNSET) { if (length != C.LENGTH_UNSET) {
length += position; length += position;
...@@ -915,6 +916,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -915,6 +916,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// Internal methods. // 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) { private void setLoadPosition(long position, long timeUs) {
positionHolder.position = position; positionHolder.position = position;
seekTimeUs = timeUs; seekTimeUs = timeUs;
......
...@@ -359,6 +359,12 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -359,6 +359,12 @@ public final class ExtractorMediaSource extends BaseMediaSource
} }
@Override @Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -65,6 +65,12 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> { ...@@ -65,6 +65,12 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
} }
@Override @Override
@Nullable
public Object getTag() {
return childSource.getTag();
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -219,6 +219,12 @@ public interface MediaSource { ...@@ -219,6 +219,12 @@ public interface MediaSource {
*/ */
void removeEventListener(MediaSourceEventListener eventListener); 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 * Starts source preparation if not yet started, and adds a listener for timeline and/or manifest
* updates. * updates.
......
...@@ -99,6 +99,12 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> { ...@@ -99,6 +99,12 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
} }
@Override @Override
@Nullable
public Object getTag() {
return mediaSources.length > 0 ? mediaSources[0].getTag() : null;
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -185,6 +185,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -185,6 +185,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final boolean treatLoadErrorsAsEndOfStream; private final boolean treatLoadErrorsAsEndOfStream;
private final Timeline timeline; private final Timeline timeline;
@Nullable private final Object tag;
private @Nullable TransferListener transferListener; private @Nullable TransferListener transferListener;
...@@ -287,8 +288,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -287,8 +288,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
this.durationUs = durationUs; this.durationUs = durationUs;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
dataSpec = this.tag = tag;
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
timeline = timeline =
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false, tag); new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false, tag);
} }
...@@ -296,6 +297,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -296,6 +297,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
// MediaSource implementation. // MediaSource implementation.
@Override @Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -320,6 +320,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -320,6 +320,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
} }
@Override @Override
@Nullable
public Object getTag() {
return contentMediaSource.getTag();
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
final ExoPlayer player, final ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -560,6 +560,14 @@ public final class Cea608Decoder extends CeaDecoder { ...@@ -560,6 +560,14 @@ public final class Cea608Decoder extends CeaDecoder {
int oldCaptionMode = this.captionMode; int oldCaptionMode = this.captionMode;
this.captionMode = 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. // Clear the working memory.
resetCueBuilders(); resetCueBuilders();
if (oldCaptionMode == CC_MODE_PAINT_ON || captionMode == CC_MODE_ROLL_UP if (oldCaptionMode == CC_MODE_PAINT_ON || captionMode == CC_MODE_ROLL_UP
...@@ -664,6 +672,10 @@ public final class Cea608Decoder extends CeaDecoder { ...@@ -664,6 +672,10 @@ public final class Cea608Decoder extends CeaDecoder {
tabOffset = 0; tabOffset = 0;
} }
public void setCaptionMode(int captionMode) {
this.captionMode = captionMode;
}
public void setCaptionRowCount(int captionRowCount) { public void setCaptionRowCount(int captionRowCount) {
this.captionRowCount = captionRowCount; this.captionRowCount = captionRowCount;
} }
......
...@@ -32,32 +32,29 @@ public final class DataSpec { ...@@ -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} * 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 @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef( @IntDef(
flag = true, flag = true,
value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}) value = {FLAG_ALLOW_GZIP, FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN})
public @interface Flags {} public @interface Flags {}
/** /**
* Permits an underlying network stack to request that the server use gzip compression. * 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 * <p>Should not typically be set if the data being requested is already compressed (e.g. most
* and video requests). May be set when requesting other data. * 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 * <p>When a {@link DataSource} is used to request data with this flag set, and if the {@link
* {@link DataSource} does make a network request, then the value returned from * DataSource} does make a network request, then the value returned from {@link
* {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from * DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from {@link
* {@link DataSource#read(byte[], int, int)} will be the decompressed data. * DataSource#read(byte[], int, int)} will be the decompressed data.
*/ */
public static final int FLAG_ALLOW_GZIP = 1; public static final int FLAG_ALLOW_GZIP = 1;
/** /** Prevents caching if the length cannot be resolved when the {@link DataSource} is opened. */
* Permits content to be cached even if its length can not be resolved. Typically this's the case public static final int FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN = 1 << 1; // 2
* for progressive live streams and when {@link #FLAG_ALLOW_GZIP} is used.
*/
public static final int FLAG_ALLOW_CACHING_UNKNOWN_LENGTH = 1 << 1; // 2
/** /**
* The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link * The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link
......
...@@ -70,6 +70,24 @@ public final class ParsingLoadable<T> implements Loadable { ...@@ -70,6 +70,24 @@ public final class ParsingLoadable<T> implements Loadable {
} }
/** /**
* 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. * The {@link DataSpec} that defines the data to be loaded.
*/ */
public final DataSpec dataSpec; public final DataSpec dataSpec;
...@@ -91,11 +109,7 @@ public final class ParsingLoadable<T> implements Loadable { ...@@ -91,11 +109,7 @@ public final class ParsingLoadable<T> implements Loadable {
* @param parser Parses the object from the response. * @param parser Parses the object from the response.
*/ */
public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<? extends T> parser) { public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<? extends T> parser) {
this( this(dataSource, new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP), type, parser);
dataSource,
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH),
type,
parser);
} }
/** /**
......
...@@ -121,7 +121,7 @@ public final class CacheDataSink implements DataSink { ...@@ -121,7 +121,7 @@ public final class CacheDataSink implements DataSink {
@Override @Override
public void open(DataSpec dataSpec) throws CacheDataSinkException { public void open(DataSpec dataSpec) throws CacheDataSinkException {
if (dataSpec.length == C.LENGTH_UNSET 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; this.dataSpec = null;
return; return;
} }
......
...@@ -268,7 +268,7 @@ public final class CacheUtil { ...@@ -268,7 +268,7 @@ public final class CacheUtil {
dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition, dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition,
C.LENGTH_UNSET, C.LENGTH_UNSET,
dataSpec.key, dataSpec.key,
dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); dataSpec.flags);
long resolvedLength = dataSource.open(dataSpec); long resolvedLength = dataSource.open(dataSpec);
if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength; counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength;
......
...@@ -48,8 +48,15 @@ import com.google.android.exoplayer2.C; ...@@ -48,8 +48,15 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; 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.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.upstream.DataSource;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
...@@ -1842,6 +1849,32 @@ public final class Util { ...@@ -1842,6 +1849,32 @@ public final class Util {
return displaySize; 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 @Nullable
private static String getSystemProperty(String name) { private static String getSystemProperty(String name) {
try { try {
......
...@@ -26,7 +26,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; ...@@ -26,7 +26,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions; 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 { public interface VideoRendererEventListener {
...@@ -36,7 +37,7 @@ 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 * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
* remains enabled. * remains enabled.
*/ */
void onVideoEnabled(DecoderCounters counters); default void onVideoEnabled(DecoderCounters counters) {}
/** /**
* Called when a decoder is created. * Called when a decoder is created.
...@@ -46,15 +47,15 @@ public interface VideoRendererEventListener { ...@@ -46,15 +47,15 @@ public interface VideoRendererEventListener {
* finished. * finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/ */
void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, default void onVideoDecoderInitialized(
long initializationDurationMs); String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/** /**
* Called when the format of the media being consumed by the renderer changes. * Called when the format of the media being consumed by the renderer changes.
* *
* @param format The new format. * @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 * Called to report the number of frames dropped by the renderer. Dropped frames are reported
...@@ -62,12 +63,11 @@ public interface VideoRendererEventListener { ...@@ -62,12 +63,11 @@ public interface VideoRendererEventListener {
* reaches a specified threshold whilst the renderer is started. * reaches a specified threshold whilst the renderer is started.
* *
* @param count The number of dropped frames. * @param count The number of dropped frames.
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This * @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
* duration is timed from when the renderer was started or from when dropped frames were * is timed from when the renderer was started or from when dropped frames were last reported
* last reported (whichever was more recent), and not from when the first of the reported * (whichever was more recent), and not from when the first of the reported drops occurred.
* 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 * Called before a frame is rendered for the first time since setting the surface, and each time
...@@ -82,12 +82,12 @@ public interface VideoRendererEventListener { ...@@ -82,12 +82,12 @@ public interface VideoRendererEventListener {
* this is not possible. Applications that use {@link TextureView} can apply the rotation by * 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 * calling {@link TextureView#setTransform}. Applications that do not expect to encounter
* rotated videos can safely ignore this parameter. * rotated videos can safely ignore this parameter.
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
* of square pixels this will be equal to 1.0. Different values are indicative of anamorphic * square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content. * content.
*/ */
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, default void onVideoSizeChanged(
float pixelWidthHeightRatio); 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 * 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 { ...@@ -96,14 +96,14 @@ public interface VideoRendererEventListener {
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if * @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}. * 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. * Called when the renderer is disabled.
* *
* @param counters {@link DecoderCounters} that were updated by the renderer. * @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}. * Dispatches events to a {@link VideoRendererEventListener}.
......
...@@ -602,7 +602,6 @@ public final class CacheDataSourceTest { ...@@ -602,7 +602,6 @@ public final class CacheDataSourceTest {
} }
private DataSpec buildDataSpec(long position, long length, @Nullable String key) { private DataSpec buildDataSpec(long position, long length, @Nullable String key) {
return new DataSpec( return new DataSpec(testDataUri, position, length, key);
testDataUri, position, length, key, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
} }
} }
...@@ -608,6 +608,12 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -608,6 +608,12 @@ public final class DashMediaSource extends BaseMediaSource {
// MediaSource implementation. // MediaSource implementation.
@Override @Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.dash.offline; package com.google.android.exoplayer2.source.dash.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
...@@ -31,74 +30,49 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; ...@@ -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.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for DASH streams. */ /** 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 final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull DashManifest manifest;
public DashDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { public DashDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri; super(DownloadAction.TYPE_DASH, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestDataSourceFactory = manifestDataSourceFactory;
} }
@Override @Override
protected void prepareInternal() throws IOException { protected DashManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource(); DataSource dataSource = manifestDataSourceFactory.createDataSource();
manifest = return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_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;
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(manifest);
return manifest.getPeriodCount();
} }
@Override @Override
public TrackGroupArray getTrackGroups(int periodIndex) { public TrackGroupArray[] getTrackGroupArrays(DashManifest manifest) {
Assertions.checkNotNull(manifest); int periodCount = manifest.getPeriodCount();
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; TrackGroupArray[] trackGroupArrays = new TrackGroupArray[periodCount];
TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()]; for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
for (int i = 0; i < trackGroups.length; i++) { List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
List<Representation> representations = adaptationSets.get(i).representations; TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()];
Format[] formats = new Format[representations.size()]; for (int i = 0; i < trackGroups.length; i++) {
int representationsCount = representations.size(); List<Representation> representations = adaptationSets.get(i).representations;
for (int j = 0; j < representationsCount; j++) { Format[] formats = new Format[representations.size()];
formats[j] = representations.get(j).format; 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 @Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) { protected List<StreamKey> toStreamKeys(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) {
List<StreamKey> streamKeys = new ArrayList<>(trackKeys.size()); List<StreamKey> streamKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) { for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i); TrackKey trackKey = trackKeys.get(i);
......
...@@ -28,11 +28,13 @@ import com.google.android.exoplayer2.source.dash.DashUtil; ...@@ -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.DashWrappingSegmentIndex;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; 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.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.Period;
import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -73,8 +75,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> { ...@@ -73,8 +75,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
} }
@Override @Override
protected DashManifest getManifest(DataSource dataSource, Uri uri) throws IOException { protected DashManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return DashUtil.loadManifest(dataSource, uri); return ParsingLoadable.load(
dataSource, new DashManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
} }
@Override @Override
...@@ -121,8 +124,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> { ...@@ -121,8 +124,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
if (!allowIncompleteList) { if (!allowIncompleteList) {
throw e; throw e;
} }
// Loading failed, but generating an incomplete segment list is allowed. Advance to the next // Generating an incomplete segment list is allowed. Advance to the next representation.
// representation.
continue; continue;
} }
......
...@@ -391,6 +391,12 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -391,6 +391,12 @@ public final class HlsMediaSource extends BaseMediaSource
} }
@Override @Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.hls.offline; package com.google.android.exoplayer2.source.hls.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
...@@ -36,46 +35,31 @@ import java.io.IOException; ...@@ -36,46 +35,31 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for HLS streams. */ /** 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 final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull HlsPlaylist playlist;
private int[] renditionGroups; private int[] renditionGroups;
public HlsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { public HlsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri; super(DownloadAction.TYPE_HLS, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestDataSourceFactory = manifestDataSourceFactory;
} }
@Override @Override
protected void prepareInternal() throws IOException { protected HlsPlaylist loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource(); DataSource dataSource = manifestDataSourceFactory.createDataSource();
playlist = ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST); return 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;
} }
@Override @Override
public int getPeriodCount() { protected TrackGroupArray[] getTrackGroupArrays(HlsPlaylist playlist) {
Assertions.checkNotNull(playlist);
return 1;
}
@Override
public TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(playlist); Assertions.checkNotNull(playlist);
if (playlist instanceof HlsMediaPlaylist) { if (playlist instanceof HlsMediaPlaylist) {
renditionGroups = new int[0]; renditionGroups = new int[0];
return TrackGroupArray.EMPTY; return new TrackGroupArray[] {TrackGroupArray.EMPTY};
} }
// TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction. // TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction.
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
...@@ -94,24 +78,18 @@ public final class HlsDownloadHelper extends DownloadHelper { ...@@ -94,24 +78,18 @@ public final class HlsDownloadHelper extends DownloadHelper {
renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE; renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE;
trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles)); trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles));
} }
return new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex)); return new TrackGroupArray[] {new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex))};
} }
@Override @Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) { protected List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
Assertions.checkNotNull(renditionGroups); List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
return DownloadAction.createDownloadAction( for (int i = 0; i < trackKeys.size(); i++) {
DownloadAction.TYPE_HLS, TrackKey trackKey = trackKeys.get(i);
uri, representationKeys.add(
toStreamKeys(trackKeys, renditionGroups), new StreamKey(renditionGroups[trackKey.groupIndex], trackKey.trackIndex));
/* customCacheKey= */ null, }
data); return representationKeys;
}
@Override
public DownloadAction getRemoveAction() {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_HLS, uri, /* customCacheKey= */ null);
} }
private static Format[] toFormats(List<HlsMasterPlaylist.HlsUrl> hlsUrls) { private static Format[] toFormats(List<HlsMasterPlaylist.HlsUrl> hlsUrls) {
...@@ -121,13 +99,4 @@ public final class HlsDownloadHelper extends DownloadHelper { ...@@ -121,13 +99,4 @@ public final class HlsDownloadHelper extends DownloadHelper {
} }
return formats; 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;
}
} }
...@@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -71,35 +71,37 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
} }
@Override @Override
protected HlsPlaylist getManifest(DataSource dataSource, Uri uri) throws IOException { protected HlsPlaylist getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return loadManifest(dataSource, uri); return loadManifest(dataSource, dataSpec);
} }
@Override @Override
protected List<Segment> getSegments( protected List<Segment> getSegments(
DataSource dataSource, HlsPlaylist playlist, boolean allowIncompleteList) throws IOException { 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) { if (playlist instanceof HlsMasterPlaylist) {
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.variants, mediaPlaylistUris); addMediaPlaylistDataSpecs(baseUri, masterPlaylist.variants, mediaPlaylistDataSpecs);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.audios, mediaPlaylistUris); addMediaPlaylistDataSpecs(baseUri, masterPlaylist.audios, mediaPlaylistDataSpecs);
addResolvedUris(masterPlaylist.baseUri, masterPlaylist.subtitles, mediaPlaylistUris); addMediaPlaylistDataSpecs(baseUri, masterPlaylist.subtitles, mediaPlaylistDataSpecs);
} else { } 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<>(); HashSet<Uri> seenEncryptionKeyUris = new HashSet<>();
for (Uri mediaPlaylistUri : mediaPlaylistUris) { for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) {
segments.add(new Segment(/* startTimeUs= */ 0, mediaPlaylistDataSpec));
HlsMediaPlaylist mediaPlaylist; HlsMediaPlaylist mediaPlaylist;
try { try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistUri); mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistDataSpec);
segments.add(new Segment(mediaPlaylist.startTimeUs, new DataSpec(mediaPlaylistUri)));
} catch (IOException e) { } catch (IOException e) {
if (!allowIncompleteList) { if (!allowIncompleteList) {
throw e; throw e;
} }
segments.add(new Segment(0, new DataSpec(mediaPlaylistUri))); // Generating an incomplete segment list is allowed. Advance to the next media playlist.
continue; continue;
} }
HlsMediaPlaylist.Segment lastInitSegment = null; HlsMediaPlaylist.Segment lastInitSegment = null;
...@@ -109,39 +111,43 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> { ...@@ -109,39 +111,43 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
HlsMediaPlaylist.Segment initSegment = segment.initializationSegment; HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
if (initSegment != null && initSegment != lastInitSegment) { if (initSegment != null && initSegment != lastInitSegment) {
lastInitSegment = initSegment; lastInitSegment = initSegment;
addSegment(segments, mediaPlaylist, initSegment, seenEncryptionKeyUris); addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments);
} }
addSegment(segments, mediaPlaylist, segment, seenEncryptionKeyUris); addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments);
} }
} }
return segments; return segments;
} }
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException { private void addMediaPlaylistDataSpecs(String baseUri, List<HlsUrl> urls, List<DataSpec> out) {
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST); 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( private static HlsPlaylist loadManifest(DataSource dataSource, DataSpec dataSpec)
ArrayList<Segment> segments, throws IOException {
return ParsingLoadable.load(
dataSource, new HlsPlaylistParser(), dataSpec, C.DATA_TYPE_MANIFEST);
}
private void addSegment(
HlsMediaPlaylist mediaPlaylist, HlsMediaPlaylist mediaPlaylist,
HlsMediaPlaylist.Segment hlsSegment, HlsMediaPlaylist.Segment segment,
HashSet<Uri> seenEncryptionKeyUris) { HashSet<Uri> seenEncryptionKeyUris,
long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs; ArrayList<Segment> out) {
if (hlsSegment.fullSegmentEncryptionKeyUri != null) { String baseUri = mediaPlaylist.baseUri;
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
hlsSegment.fullSegmentEncryptionKeyUri); if (segment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(baseUri, segment.fullSegmentEncryptionKeyUri);
if (seenEncryptionKeyUris.add(keyUri)) { 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); Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
segments.add(new Segment(startTimeUs, DataSpec dataSpec =
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null))); new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null);
} out.add(new Segment(startTimeUs, dataSpec));
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));
}
} }
} }
...@@ -504,6 +504,12 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -504,6 +504,12 @@ public final class SsMediaSource extends BaseMediaSource
// MediaSource implementation. // MediaSource implementation.
@Override @Override
@Nullable
public Object getTag() {
return tag;
}
@Override
public void prepareSourceInternal( public void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming.offline; package com.google.android.exoplayer2.source.smoothstreaming.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper; import com.google.android.exoplayer2.offline.DownloadHelper;
...@@ -28,67 +27,38 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; ...@@ -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.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A {@link DownloadHelper} for SmoothStreaming streams. */ /** 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 final DataSource.Factory manifestDataSourceFactory;
private @MonotonicNonNull SsManifest manifest;
public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
this.uri = uri; super(DownloadAction.TYPE_SS, uri, /* cacheKey= */ null);
this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestDataSourceFactory = manifestDataSourceFactory;
} }
@Override @Override
protected void prepareInternal() throws IOException { protected SsManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource(); DataSource dataSource = manifestDataSourceFactory.createDataSource();
manifest = ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); return 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;
}
@Override
public int getPeriodCount() {
Assertions.checkNotNull(manifest);
return 1;
} }
@Override @Override
public TrackGroupArray getTrackGroups(int periodIndex) { protected TrackGroupArray[] getTrackGroupArrays(SsManifest manifest) {
Assertions.checkNotNull(manifest);
SsManifest.StreamElement[] streamElements = manifest.streamElements; SsManifest.StreamElement[] streamElements = manifest.streamElements;
TrackGroup[] trackGroups = new TrackGroup[streamElements.length]; TrackGroup[] trackGroups = new TrackGroup[streamElements.length];
for (int i = 0; i < streamElements.length; i++) { for (int i = 0; i < streamElements.length; i++) {
trackGroups[i] = new TrackGroup(streamElements[i].formats); trackGroups[i] = new TrackGroup(streamElements[i].formats);
} }
return new TrackGroupArray(trackGroups); return new TrackGroupArray[] {new TrackGroupArray(trackGroups)};
} }
@Override @Override
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) { protected List<StreamKey> toStreamKeys(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) {
List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size()); List<StreamKey> representationKeys = new ArrayList<>(trackKeys.size());
for (int i = 0; i < trackKeys.size(); i++) { for (int i = 0; i < trackKeys.size(); i++) {
TrackKey trackKey = trackKeys.get(i); TrackKey trackKey = trackKeys.get(i);
......
...@@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> { ...@@ -68,8 +68,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
} }
@Override @Override
protected SsManifest getManifest(DataSource dataSource, Uri uri) throws IOException { protected SsManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException {
return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); return ParsingLoadable.load(dataSource, new SsManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST);
} }
@Override @Override
......
...@@ -89,6 +89,13 @@ public class FakeMediaSource extends BaseMediaSource { ...@@ -89,6 +89,13 @@ public class FakeMediaSource extends BaseMediaSource {
} }
@Override @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( public synchronized void prepareSourceInternal(
ExoPlayer player, ExoPlayer player,
boolean isTopLevelSource, boolean isTopLevelSource,
......
...@@ -83,7 +83,7 @@ public final class CacheAsserts { ...@@ -83,7 +83,7 @@ public final class CacheAsserts {
* @throws IOException If an error occurred reading from the Cache. * @throws IOException If an error occurred reading from the Cache.
*/ */
public static void assertDataCached(Cache cache, Uri uri, byte[] expected) throws IOException { 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); assertDataCached(cache, dataSpec, expected);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment