mirror of
https://github.com/samsonjs/media.git
synced 2026-04-07 11:35:46 +00:00
Merge pull request #2 from google/dev-v2
Pull from google/ExoPlayer dev-v2
This commit is contained in:
commit
246d464466
35 changed files with 404 additions and 279 deletions
|
|
@ -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 ###
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue