Refactor #6.1: Pull up manifest requests.
This change pulls manifest refresh responsibility up to the top level Dash/SS SampleSource implementations. In following steps more of the manifest processing logic will be pulled up (e.g. extracting track groups from the initial manifest), which will allow ChunkSampleSource/ChunkSource instances to be further simplified and created on demand. I've avoided moving/renaming anything for now, so as to keep it fairly easy to review. Note that this change does the TODO related to releasing the manifest fetchers. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=121001139
This commit is contained in:
parent
15a890c3ce
commit
b38d004553
@ -135,9 +135,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
if (prepared) {
|
if (prepared) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!chunkSource.prepare()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
TrackGroup tracks = chunkSource.getTracks();
|
TrackGroup tracks = chunkSource.getTracks();
|
||||||
if (tracks != null) {
|
if (tracks != null) {
|
||||||
trackGroups = new TrackGroupArray(tracks);
|
trackGroups = new TrackGroupArray(tracks);
|
||||||
@ -207,7 +204,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long positionUs) {
|
public void continueBuffering(long positionUs) {
|
||||||
downstreamPositionUs = positionUs;
|
downstreamPositionUs = positionUs;
|
||||||
chunkSource.continueBuffering();
|
|
||||||
if (!loader.isLoading()) {
|
if (!loader.isLoading()) {
|
||||||
maybeStartLoading();
|
maybeStartLoading();
|
||||||
}
|
}
|
||||||
|
@ -41,16 +41,6 @@ public interface ChunkSource {
|
|||||||
*/
|
*/
|
||||||
void maybeThrowError() throws IOException;
|
void maybeThrowError() throws IOException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares the source.
|
|
||||||
* <p>
|
|
||||||
* The method can be called repeatedly until the return value indicates success.
|
|
||||||
*
|
|
||||||
* @return True if the source was prepared, false otherwise.
|
|
||||||
* @throws IOException If an error occurs preparing the source.
|
|
||||||
*/
|
|
||||||
boolean prepare() throws IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the duration of the source in microseconds.
|
* Gets the duration of the source in microseconds.
|
||||||
* <p>
|
* <p>
|
||||||
@ -80,13 +70,6 @@ public interface ChunkSource {
|
|||||||
*/
|
*/
|
||||||
void enable(int[] tracks);
|
void enable(int[] tracks);
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates to the source that it should still be checking for updates to the stream.
|
|
||||||
* <p>
|
|
||||||
* This method should only be called when the source is enabled.
|
|
||||||
*/
|
|
||||||
void continueBuffering();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates whether {@link MediaChunk}s should be removed from the back of the queue.
|
* Evaluates whether {@link MediaChunk}s should be removed from the back of the queue.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -43,7 +43,6 @@ import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor;
|
|||||||
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
|
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
@ -71,7 +70,6 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||||
private final Evaluation evaluation;
|
private final Evaluation evaluation;
|
||||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
|
||||||
|
|
||||||
// Properties of the initial manifest.
|
// Properties of the initial manifest.
|
||||||
private boolean live;
|
private boolean live;
|
||||||
@ -93,15 +91,13 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
private boolean[] adaptiveFormatBlacklistFlags;
|
private boolean[] adaptiveFormatBlacklistFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param manifestFetcher A fetcher for the manifest.
|
|
||||||
* @param adaptationSetType The type of the adaptation set exposed by this source. One of
|
* @param adaptationSetType The type of the adaptation set exposed by this source. One of
|
||||||
* {@link C#TRACK_TYPE_AUDIO}, {@link C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_TEXT}.
|
* {@link C#TRACK_TYPE_AUDIO}, {@link C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_TEXT}.
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||||
*/
|
*/
|
||||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
public DashChunkSource(int adaptationSetType, DataSource dataSource,
|
||||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) {
|
FormatEvaluator adaptiveFormatEvaluator) {
|
||||||
this.manifestFetcher = manifestFetcher;
|
|
||||||
this.adaptationSetType = adaptationSetType;
|
this.adaptationSetType = adaptationSetType;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
@ -114,25 +110,13 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
public void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
if (fatalError != null) {
|
if (fatalError != null) {
|
||||||
throw fatalError;
|
throw fatalError;
|
||||||
} else if (live) {
|
|
||||||
manifestFetcher.maybeThrowError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void init(MediaPresentationDescription initialManifest) {
|
||||||
public boolean prepare() throws IOException {
|
currentManifest = initialManifest;
|
||||||
if (currentManifest == null) {
|
|
||||||
currentManifest = manifestFetcher.getManifest();
|
|
||||||
if (currentManifest == null) {
|
|
||||||
manifestFetcher.maybeThrowError();
|
|
||||||
manifestFetcher.requestRefresh();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
initForManifest(currentManifest);
|
initForManifest(currentManifest);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getDurationUs() {
|
public long getDurationUs() {
|
||||||
@ -155,35 +139,12 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
adaptiveFormatEvaluator.enable(enabledFormats);
|
adaptiveFormatEvaluator.enable(enabledFormats);
|
||||||
adaptiveFormatBlacklistFlags = new boolean[tracks.length];
|
adaptiveFormatBlacklistFlags = new boolean[tracks.length];
|
||||||
}
|
}
|
||||||
processManifest(manifestFetcher.getManifest());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void updateManifest(MediaPresentationDescription newManifest) {
|
||||||
public void continueBuffering() {
|
|
||||||
if (!currentManifest.dynamic || fatalError != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaPresentationDescription newManifest = manifestFetcher.getManifest();
|
|
||||||
if (newManifest != null && newManifest != currentManifest) {
|
|
||||||
processManifest(newManifest);
|
processManifest(newManifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
|
|
||||||
// minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit
|
|
||||||
// signaling in the stream, according to:
|
|
||||||
// http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/
|
|
||||||
long minUpdatePeriod = currentManifest.minUpdatePeriod;
|
|
||||||
if (minUpdatePeriod == 0) {
|
|
||||||
minUpdatePeriod = 5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (android.os.SystemClock.elapsedRealtime()
|
|
||||||
> manifestFetcher.getManifestLoadStartTimestamp() + minUpdatePeriod) {
|
|
||||||
manifestFetcher.requestRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
||||||
if (fatalError != null || enabledFormats.length < 2) {
|
if (fatalError != null || enabledFormats.length < 2) {
|
||||||
|
@ -25,7 +25,6 @@ import com.google.android.exoplayer.TrackSelection;
|
|||||||
import com.google.android.exoplayer.TrackStream;
|
import com.google.android.exoplayer.TrackStream;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
||||||
@ -38,6 +37,7 @@ import com.google.android.exoplayer.util.ManifestFetcher;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -50,10 +50,13 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class DashSampleSource implements SampleSource {
|
public final class DashSampleSource implements SampleSource {
|
||||||
|
|
||||||
|
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||||
|
private final DashChunkSource[] chunkSources;
|
||||||
private final SampleSource[] sources;
|
private final SampleSource[] sources;
|
||||||
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
||||||
private final int[] selectedTrackCounts;
|
private final int[] selectedTrackCounts;
|
||||||
|
|
||||||
|
private MediaPresentationDescription currentManifest;
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private boolean seenFirstTrackSelection;
|
private boolean seenFirstTrackSelection;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
@ -65,34 +68,32 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
ChunkSampleSourceEventListener eventListener) {
|
ChunkSampleSourceEventListener eventListener) {
|
||||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||||
DataSource manifestDataSource = dataSourceFactory.createDataSource();
|
DataSource manifestDataSource = dataSourceFactory.createDataSource();
|
||||||
// TODO[REFACTOR]: This needs releasing.
|
manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser);
|
||||||
ManifestFetcher<MediaPresentationDescription> manifestFetcher = new ManifestFetcher<>(uri,
|
|
||||||
manifestDataSource, parser);
|
|
||||||
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(
|
LoadControl loadControl = new DefaultLoadControl(
|
||||||
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||||
|
|
||||||
// Build the video renderer.
|
// Build the video renderer.
|
||||||
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_VIDEO,
|
DashChunkSource videoChunkSource = new DashChunkSource(C.TRACK_TYPE_VIDEO, videoDataSource,
|
||||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
|
new AdaptiveEvaluator(bandwidthMeter));
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||||
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
|
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
|
||||||
|
|
||||||
// Build the audio renderer.
|
// Build the audio renderer.
|
||||||
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO,
|
DashChunkSource audioChunkSource = new DashChunkSource(C.TRACK_TYPE_AUDIO, audioDataSource,
|
||||||
audioDataSource, null);
|
null);
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||||
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
|
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
|
||||||
|
|
||||||
// Build the text renderer.
|
// Build the text renderer.
|
||||||
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
|
DashChunkSource textChunkSource = new DashChunkSource(C.TRACK_TYPE_TEXT, textDataSource, null);
|
||||||
textDataSource, null);
|
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||||
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
|
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
|
||||||
|
|
||||||
|
chunkSources = new DashChunkSource[] {videoChunkSource, audioChunkSource, textChunkSource};
|
||||||
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
|
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
|
||||||
trackStreamSources = new IdentityHashMap<>();
|
trackStreamSources = new IdentityHashMap<>();
|
||||||
selectedTrackCounts = new int[sources.length];
|
selectedTrackCounts = new int[sources.length];
|
||||||
@ -103,6 +104,20 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
if (prepared) {
|
if (prepared) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentManifest == null) {
|
||||||
|
currentManifest = manifestFetcher.getManifest();
|
||||||
|
if (currentManifest == null) {
|
||||||
|
manifestFetcher.maybeThrowError();
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (DashChunkSource chunkSource : chunkSources) {
|
||||||
|
chunkSource.init(currentManifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean sourcesPrepared = true;
|
boolean sourcesPrepared = true;
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
sourcesPrepared &= source.prepare(positionUs);
|
sourcesPrepared &= source.prepare(positionUs);
|
||||||
@ -171,6 +186,30 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long positionUs) {
|
public void continueBuffering(long positionUs) {
|
||||||
|
if (currentManifest.dynamic) {
|
||||||
|
MediaPresentationDescription newManifest = manifestFetcher.getManifest();
|
||||||
|
if (newManifest != currentManifest) {
|
||||||
|
currentManifest = newManifest;
|
||||||
|
for (DashChunkSource chunkSource : chunkSources) {
|
||||||
|
chunkSource.updateManifest(newManifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long minUpdatePeriod = currentManifest.minUpdatePeriod;
|
||||||
|
if (minUpdatePeriod == 0) {
|
||||||
|
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
|
||||||
|
// minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit
|
||||||
|
// signaling in the stream, according to:
|
||||||
|
// http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/
|
||||||
|
minUpdatePeriod = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemClock.elapsedRealtime() > manifestFetcher.getManifestLoadStartTimestamp()
|
||||||
|
+ minUpdatePeriod) {
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (SampleSource source : enabledSources) {
|
for (SampleSource source : enabledSources) {
|
||||||
source.continueBuffering(positionUs);
|
source.continueBuffering(positionUs);
|
||||||
}
|
}
|
||||||
@ -215,6 +254,7 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
manifestFetcher.release();
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
source.release();
|
source.release();
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,9 @@ import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.Prot
|
|||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
@ -55,16 +53,13 @@ import java.util.List;
|
|||||||
// TODO[REFACTOR]: Handle multiple stream elements of the same type (at a higher level).
|
// TODO[REFACTOR]: Handle multiple stream elements of the same type (at a higher level).
|
||||||
public class SmoothStreamingChunkSource implements ChunkSource {
|
public class SmoothStreamingChunkSource implements ChunkSource {
|
||||||
|
|
||||||
private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000;
|
|
||||||
private static final int INITIALIZATION_VECTOR_SIZE = 8;
|
private static final int INITIALIZATION_VECTOR_SIZE = 8;
|
||||||
|
|
||||||
private final int streamElementType;
|
private final int streamElementType;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final Evaluation evaluation;
|
private final Evaluation evaluation;
|
||||||
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
|
||||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||||
|
|
||||||
private boolean live;
|
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private TrackEncryptionBox[] trackEncryptionBoxes;
|
private TrackEncryptionBox[] trackEncryptionBoxes;
|
||||||
private DrmInitData.Mapped drmInitData;
|
private DrmInitData.Mapped drmInitData;
|
||||||
@ -84,42 +79,34 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
private IOException fatalError;
|
private IOException fatalError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param manifestFetcher A fetcher for the manifest.
|
|
||||||
* @param streamElementType The type of stream element exposed by this source. One of
|
* @param streamElementType The type of stream element exposed by this source. One of
|
||||||
* {@link C#TRACK_TYPE_VIDEO}, {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_TEXT}.
|
* {@link C#TRACK_TYPE_VIDEO}, {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_TEXT}.
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||||
*/
|
*/
|
||||||
public SmoothStreamingChunkSource(ManifestFetcher<SmoothStreamingManifest> manifestFetcher,
|
public SmoothStreamingChunkSource(int streamElementType, DataSource dataSource,
|
||||||
int streamElementType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) {
|
FormatEvaluator adaptiveFormatEvaluator) {
|
||||||
this.manifestFetcher = manifestFetcher;
|
|
||||||
this.streamElementType = streamElementType;
|
this.streamElementType = streamElementType;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
evaluation = new Evaluation();
|
evaluation = new Evaluation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean needManifestRefresh() {
|
||||||
|
return needManifestRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
// ChunkSource implementation.
|
// ChunkSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
if (fatalError != null) {
|
if (fatalError != null) {
|
||||||
throw fatalError;
|
throw fatalError;
|
||||||
} else if (live) {
|
|
||||||
manifestFetcher.maybeThrowError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void init(SmoothStreamingManifest initialManifest) {
|
||||||
public boolean prepare() throws IOException {
|
currentManifest = initialManifest;
|
||||||
if (currentManifest == null) {
|
|
||||||
currentManifest = manifestFetcher.getManifest();
|
|
||||||
if (currentManifest == null) {
|
|
||||||
manifestFetcher.maybeThrowError();
|
|
||||||
manifestFetcher.requestRefresh();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
live = currentManifest.isLive;
|
|
||||||
durationUs = currentManifest.durationUs;
|
durationUs = currentManifest.durationUs;
|
||||||
ProtectionElement protectionElement = currentManifest.protectionElement;
|
ProtectionElement protectionElement = currentManifest.protectionElement;
|
||||||
if (protectionElement != null) {
|
if (protectionElement != null) {
|
||||||
@ -135,9 +122,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
initForManifest(currentManifest);
|
initForManifest(currentManifest);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getDurationUs() {
|
public long getDurationUs() {
|
||||||
@ -162,14 +146,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void updateManifest(SmoothStreamingManifest newManifest) {
|
||||||
public void continueBuffering() {
|
|
||||||
if (!currentManifest.isLive || fatalError != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SmoothStreamingManifest newManifest = manifestFetcher.getManifest();
|
|
||||||
if (currentManifest != newManifest && newManifest != null) {
|
|
||||||
StreamElement currentElement = currentManifest.streamElements[elementIndex];
|
StreamElement currentElement = currentManifest.streamElements[elementIndex];
|
||||||
int currentElementChunkCount = currentElement.chunkCount;
|
int currentElementChunkCount = currentElement.chunkCount;
|
||||||
StreamElement newElement = newManifest.streamElements[elementIndex];
|
StreamElement newElement = newManifest.streamElements[elementIndex];
|
||||||
@ -192,12 +169,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
needManifestRefresh = false;
|
needManifestRefresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needManifestRefresh && (SystemClock.elapsedRealtime()
|
|
||||||
> manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS)) {
|
|
||||||
manifestFetcher.requestRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
||||||
if (fatalError != null || enabledFormats.length < 2) {
|
if (fatalError != null || enabledFormats.length < 2) {
|
||||||
|
@ -25,7 +25,6 @@ import com.google.android.exoplayer.TrackSelection;
|
|||||||
import com.google.android.exoplayer.TrackStream;
|
import com.google.android.exoplayer.TrackStream;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
@ -37,6 +36,7 @@ import com.google.android.exoplayer.util.Util;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -49,10 +49,15 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class SmoothStreamingSampleSource implements SampleSource {
|
public final class SmoothStreamingSampleSource implements SampleSource {
|
||||||
|
|
||||||
|
private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000;
|
||||||
|
|
||||||
|
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
||||||
|
private final SmoothStreamingChunkSource[] chunkSources;
|
||||||
private final SampleSource[] sources;
|
private final SampleSource[] sources;
|
||||||
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
||||||
private final int[] selectedTrackCounts;
|
private final int[] selectedTrackCounts;
|
||||||
|
|
||||||
|
private SmoothStreamingManifest currentManifest;
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private boolean seenFirstTrackSelection;
|
private boolean seenFirstTrackSelection;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
@ -67,33 +72,33 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
||||||
DataSource manifestDataSource = dataSourceFactory.createDataSource();
|
DataSource manifestDataSource = dataSourceFactory.createDataSource();
|
||||||
// TODO[REFACTOR]: This needs releasing.
|
manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser);
|
||||||
ManifestFetcher<SmoothStreamingManifest> manifestFetcher = new ManifestFetcher<>(uri,
|
|
||||||
manifestDataSource, parser);
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(
|
LoadControl loadControl = new DefaultLoadControl(
|
||||||
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||||
|
|
||||||
// Build the video renderer.
|
// Build the video renderer.
|
||||||
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
SmoothStreamingChunkSource videoChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_VIDEO,
|
||||||
C.TRACK_TYPE_VIDEO, videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
|
videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||||
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
|
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
|
||||||
|
|
||||||
// Build the audio renderer.
|
// Build the audio renderer.
|
||||||
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
SmoothStreamingChunkSource audioChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_AUDIO,
|
||||||
C.TRACK_TYPE_AUDIO, audioDataSource, null);
|
audioDataSource, null);
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||||
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
|
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
|
||||||
|
|
||||||
// Build the text renderer.
|
// Build the text renderer.
|
||||||
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
|
SmoothStreamingChunkSource textChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_TEXT,
|
||||||
textDataSource, null);
|
textDataSource, null);
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||||
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
|
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
|
||||||
|
|
||||||
|
chunkSources = new SmoothStreamingChunkSource[] {videoChunkSource, audioChunkSource,
|
||||||
|
textChunkSource};
|
||||||
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
|
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
|
||||||
trackStreamSources = new IdentityHashMap<>();
|
trackStreamSources = new IdentityHashMap<>();
|
||||||
selectedTrackCounts = new int[sources.length];
|
selectedTrackCounts = new int[sources.length];
|
||||||
@ -104,6 +109,20 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||||||
if (prepared) {
|
if (prepared) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentManifest == null) {
|
||||||
|
currentManifest = manifestFetcher.getManifest();
|
||||||
|
if (currentManifest == null) {
|
||||||
|
manifestFetcher.maybeThrowError();
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (SmoothStreamingChunkSource chunkSource : chunkSources) {
|
||||||
|
chunkSource.init(currentManifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean sourcesPrepared = true;
|
boolean sourcesPrepared = true;
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
sourcesPrepared &= source.prepare(positionUs);
|
sourcesPrepared &= source.prepare(positionUs);
|
||||||
@ -172,6 +191,26 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long positionUs) {
|
public void continueBuffering(long positionUs) {
|
||||||
|
if (currentManifest.isLive) {
|
||||||
|
SmoothStreamingManifest newManifest = manifestFetcher.getManifest();
|
||||||
|
if (newManifest != currentManifest) {
|
||||||
|
currentManifest = newManifest;
|
||||||
|
for (SmoothStreamingChunkSource chunkSource : chunkSources) {
|
||||||
|
chunkSource.updateManifest(newManifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemClock.elapsedRealtime()
|
||||||
|
> manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS) {
|
||||||
|
for (SmoothStreamingChunkSource chunkSource : chunkSources) {
|
||||||
|
if (chunkSource.needManifestRefresh()) {
|
||||||
|
manifestFetcher.requestRefresh();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (SampleSource source : enabledSources) {
|
for (SampleSource source : enabledSources) {
|
||||||
source.continueBuffering(positionUs);
|
source.continueBuffering(positionUs);
|
||||||
}
|
}
|
||||||
@ -216,6 +255,7 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
manifestFetcher.release();
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
source.release();
|
source.release();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user