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) {
|
||||
return true;
|
||||
}
|
||||
if (!chunkSource.prepare()) {
|
||||
return false;
|
||||
}
|
||||
TrackGroup tracks = chunkSource.getTracks();
|
||||
if (tracks != null) {
|
||||
trackGroups = new TrackGroupArray(tracks);
|
||||
@ -207,7 +204,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
@Override
|
||||
public void continueBuffering(long positionUs) {
|
||||
downstreamPositionUs = positionUs;
|
||||
chunkSource.continueBuffering();
|
||||
if (!loader.isLoading()) {
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
@ -41,16 +41,6 @@ public interface ChunkSource {
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
@ -80,13 +70,6 @@ public interface ChunkSource {
|
||||
*/
|
||||
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.
|
||||
* <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.upstream.DataSource;
|
||||
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.Util;
|
||||
|
||||
@ -71,7 +70,6 @@ public class DashChunkSource implements ChunkSource {
|
||||
private final DataSource dataSource;
|
||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||
private final Evaluation evaluation;
|
||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||
|
||||
// Properties of the initial manifest.
|
||||
private boolean live;
|
||||
@ -93,15 +91,13 @@ public class DashChunkSource implements ChunkSource {
|
||||
private boolean[] adaptiveFormatBlacklistFlags;
|
||||
|
||||
/**
|
||||
* @param manifestFetcher A fetcher for the manifest.
|
||||
* @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}.
|
||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||
*/
|
||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||
int adaptationSetType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) {
|
||||
this.manifestFetcher = manifestFetcher;
|
||||
public DashChunkSource(int adaptationSetType, DataSource dataSource,
|
||||
FormatEvaluator adaptiveFormatEvaluator) {
|
||||
this.adaptationSetType = adaptationSetType;
|
||||
this.dataSource = dataSource;
|
||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||
@ -114,24 +110,12 @@ public class DashChunkSource implements ChunkSource {
|
||||
public void maybeThrowError() throws IOException {
|
||||
if (fatalError != null) {
|
||||
throw fatalError;
|
||||
} else if (live) {
|
||||
manifestFetcher.maybeThrowError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare() throws IOException {
|
||||
if (currentManifest == null) {
|
||||
currentManifest = manifestFetcher.getManifest();
|
||||
if (currentManifest == null) {
|
||||
manifestFetcher.maybeThrowError();
|
||||
manifestFetcher.requestRefresh();
|
||||
return false;
|
||||
} else {
|
||||
initForManifest(currentManifest);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
public void init(MediaPresentationDescription initialManifest) {
|
||||
currentManifest = initialManifest;
|
||||
initForManifest(currentManifest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -155,33 +139,10 @@ public class DashChunkSource implements ChunkSource {
|
||||
adaptiveFormatEvaluator.enable(enabledFormats);
|
||||
adaptiveFormatBlacklistFlags = new boolean[tracks.length];
|
||||
}
|
||||
processManifest(manifestFetcher.getManifest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering() {
|
||||
if (!currentManifest.dynamic || fatalError != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaPresentationDescription newManifest = manifestFetcher.getManifest();
|
||||
if (newManifest != null && newManifest != currentManifest) {
|
||||
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();
|
||||
}
|
||||
public void updateManifest(MediaPresentationDescription newManifest) {
|
||||
processManifest(newManifest);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,7 +25,6 @@ import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
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.dash.mpd.MediaPresentationDescription;
|
||||
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.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -50,10 +50,13 @@ import java.util.List;
|
||||
*/
|
||||
public final class DashSampleSource implements SampleSource {
|
||||
|
||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||
private final DashChunkSource[] chunkSources;
|
||||
private final SampleSource[] sources;
|
||||
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
||||
private final int[] selectedTrackCounts;
|
||||
|
||||
private MediaPresentationDescription currentManifest;
|
||||
private boolean prepared;
|
||||
private boolean seenFirstTrackSelection;
|
||||
private long durationUs;
|
||||
@ -65,34 +68,32 @@ public final class DashSampleSource implements SampleSource {
|
||||
ChunkSampleSourceEventListener eventListener) {
|
||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||
DataSource manifestDataSource = dataSourceFactory.createDataSource();
|
||||
// TODO[REFACTOR]: This needs releasing.
|
||||
ManifestFetcher<MediaPresentationDescription> manifestFetcher = new ManifestFetcher<>(uri,
|
||||
manifestDataSource, parser);
|
||||
manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser);
|
||||
|
||||
LoadControl loadControl = new DefaultLoadControl(
|
||||
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_VIDEO,
|
||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
|
||||
DashChunkSource videoChunkSource = new DashChunkSource(C.TRACK_TYPE_VIDEO, videoDataSource,
|
||||
new AdaptiveEvaluator(bandwidthMeter));
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_AUDIO,
|
||||
audioDataSource, null);
|
||||
DashChunkSource audioChunkSource = new DashChunkSource(C.TRACK_TYPE_AUDIO, audioDataSource,
|
||||
null);
|
||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
|
||||
|
||||
// Build the text renderer.
|
||||
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
|
||||
textDataSource, null);
|
||||
DashChunkSource textChunkSource = new DashChunkSource(C.TRACK_TYPE_TEXT, textDataSource, null);
|
||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
|
||||
|
||||
chunkSources = new DashChunkSource[] {videoChunkSource, audioChunkSource, textChunkSource};
|
||||
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
|
||||
trackStreamSources = new IdentityHashMap<>();
|
||||
selectedTrackCounts = new int[sources.length];
|
||||
@ -103,6 +104,20 @@ public final class DashSampleSource implements SampleSource {
|
||||
if (prepared) {
|
||||
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;
|
||||
for (SampleSource source : sources) {
|
||||
sourcesPrepared &= source.prepare(positionUs);
|
||||
@ -171,6 +186,30 @@ public final class DashSampleSource implements SampleSource {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
source.continueBuffering(positionUs);
|
||||
}
|
||||
@ -215,6 +254,7 @@ public final class DashSampleSource implements SampleSource {
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
manifestFetcher.release();
|
||||
for (SampleSource source : sources) {
|
||||
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.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
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).
|
||||
public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
|
||||
private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000;
|
||||
private static final int INITIALIZATION_VECTOR_SIZE = 8;
|
||||
|
||||
private final int streamElementType;
|
||||
private final DataSource dataSource;
|
||||
private final Evaluation evaluation;
|
||||
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
||||
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||
|
||||
private boolean live;
|
||||
private long durationUs;
|
||||
private TrackEncryptionBox[] trackEncryptionBoxes;
|
||||
private DrmInitData.Mapped drmInitData;
|
||||
@ -84,59 +79,48 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
private IOException fatalError;
|
||||
|
||||
/**
|
||||
* @param manifestFetcher A fetcher for the manifest.
|
||||
* @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}.
|
||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||
*/
|
||||
public SmoothStreamingChunkSource(ManifestFetcher<SmoothStreamingManifest> manifestFetcher,
|
||||
int streamElementType, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) {
|
||||
this.manifestFetcher = manifestFetcher;
|
||||
public SmoothStreamingChunkSource(int streamElementType, DataSource dataSource,
|
||||
FormatEvaluator adaptiveFormatEvaluator) {
|
||||
this.streamElementType = streamElementType;
|
||||
this.dataSource = dataSource;
|
||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||
evaluation = new Evaluation();
|
||||
}
|
||||
|
||||
public boolean needManifestRefresh() {
|
||||
return needManifestRefresh;
|
||||
}
|
||||
|
||||
// ChunkSource implementation.
|
||||
|
||||
@Override
|
||||
public void maybeThrowError() throws IOException {
|
||||
if (fatalError != null) {
|
||||
throw fatalError;
|
||||
} else if (live) {
|
||||
manifestFetcher.maybeThrowError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare() throws IOException {
|
||||
if (currentManifest == null) {
|
||||
currentManifest = manifestFetcher.getManifest();
|
||||
if (currentManifest == null) {
|
||||
manifestFetcher.maybeThrowError();
|
||||
manifestFetcher.requestRefresh();
|
||||
return false;
|
||||
} else {
|
||||
live = currentManifest.isLive;
|
||||
durationUs = currentManifest.durationUs;
|
||||
ProtectionElement protectionElement = currentManifest.protectionElement;
|
||||
if (protectionElement != null) {
|
||||
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
|
||||
trackEncryptionBoxes = new TrackEncryptionBox[1];
|
||||
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
|
||||
drmInitData = new DrmInitData.Mapped();
|
||||
drmInitData.put(protectionElement.uuid,
|
||||
new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data));
|
||||
} else {
|
||||
trackEncryptionBoxes = null;
|
||||
drmInitData = null;
|
||||
}
|
||||
initForManifest(currentManifest);
|
||||
}
|
||||
public void init(SmoothStreamingManifest initialManifest) {
|
||||
currentManifest = initialManifest;
|
||||
durationUs = currentManifest.durationUs;
|
||||
ProtectionElement protectionElement = currentManifest.protectionElement;
|
||||
if (protectionElement != null) {
|
||||
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
|
||||
trackEncryptionBoxes = new TrackEncryptionBox[1];
|
||||
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
|
||||
drmInitData = new DrmInitData.Mapped();
|
||||
drmInitData.put(protectionElement.uuid,
|
||||
new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data));
|
||||
} else {
|
||||
trackEncryptionBoxes = null;
|
||||
drmInitData = null;
|
||||
}
|
||||
return true;
|
||||
initForManifest(currentManifest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -162,40 +146,27 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering() {
|
||||
if (!currentManifest.isLive || fatalError != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SmoothStreamingManifest newManifest = manifestFetcher.getManifest();
|
||||
if (currentManifest != newManifest && newManifest != null) {
|
||||
StreamElement currentElement = currentManifest.streamElements[elementIndex];
|
||||
int currentElementChunkCount = currentElement.chunkCount;
|
||||
StreamElement newElement = newManifest.streamElements[elementIndex];
|
||||
if (currentElementChunkCount == 0 || newElement.chunkCount == 0) {
|
||||
// There's no overlap between the old and new elements because at least one is empty.
|
||||
public void updateManifest(SmoothStreamingManifest newManifest) {
|
||||
StreamElement currentElement = currentManifest.streamElements[elementIndex];
|
||||
int currentElementChunkCount = currentElement.chunkCount;
|
||||
StreamElement newElement = newManifest.streamElements[elementIndex];
|
||||
if (currentElementChunkCount == 0 || newElement.chunkCount == 0) {
|
||||
// There's no overlap between the old and new elements because at least one is empty.
|
||||
currentManifestChunkOffset += currentElementChunkCount;
|
||||
} else {
|
||||
long currentElementEndTimeUs = currentElement.getStartTimeUs(currentElementChunkCount - 1)
|
||||
+ currentElement.getChunkDurationUs(currentElementChunkCount - 1);
|
||||
long newElementStartTimeUs = newElement.getStartTimeUs(0);
|
||||
if (currentElementEndTimeUs <= newElementStartTimeUs) {
|
||||
// There's no overlap between the old and new elements.
|
||||
currentManifestChunkOffset += currentElementChunkCount;
|
||||
} else {
|
||||
long currentElementEndTimeUs = currentElement.getStartTimeUs(currentElementChunkCount - 1)
|
||||
+ currentElement.getChunkDurationUs(currentElementChunkCount - 1);
|
||||
long newElementStartTimeUs = newElement.getStartTimeUs(0);
|
||||
if (currentElementEndTimeUs <= newElementStartTimeUs) {
|
||||
// There's no overlap between the old and new elements.
|
||||
currentManifestChunkOffset += currentElementChunkCount;
|
||||
} else {
|
||||
// The new element overlaps with the old one.
|
||||
currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs);
|
||||
}
|
||||
// The new element overlaps with the old one.
|
||||
currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs);
|
||||
}
|
||||
currentManifest = newManifest;
|
||||
needManifestRefresh = false;
|
||||
}
|
||||
|
||||
if (needManifestRefresh && (SystemClock.elapsedRealtime()
|
||||
> manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS)) {
|
||||
manifestFetcher.requestRefresh();
|
||||
}
|
||||
currentManifest = newManifest;
|
||||
needManifestRefresh = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,7 +25,6 @@ import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
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.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
@ -37,6 +36,7 @@ import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -49,10 +49,15 @@ import java.util.List;
|
||||
*/
|
||||
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 IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
||||
private final int[] selectedTrackCounts;
|
||||
|
||||
private SmoothStreamingManifest currentManifest;
|
||||
private boolean prepared;
|
||||
private boolean seenFirstTrackSelection;
|
||||
private long durationUs;
|
||||
@ -67,33 +72,33 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
||||
}
|
||||
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
||||
DataSource manifestDataSource = dataSourceFactory.createDataSource();
|
||||
// TODO[REFACTOR]: This needs releasing.
|
||||
ManifestFetcher<SmoothStreamingManifest> manifestFetcher = new ManifestFetcher<>(uri,
|
||||
manifestDataSource, parser);
|
||||
manifestFetcher = new ManifestFetcher<>(uri, manifestDataSource, parser);
|
||||
LoadControl loadControl = new DefaultLoadControl(
|
||||
new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
C.TRACK_TYPE_VIDEO, videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
|
||||
SmoothStreamingChunkSource videoChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_VIDEO,
|
||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter));
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
C.DEFAULT_VIDEO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_VIDEO);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
C.TRACK_TYPE_AUDIO, audioDataSource, null);
|
||||
SmoothStreamingChunkSource audioChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_AUDIO,
|
||||
audioDataSource, null);
|
||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
C.DEFAULT_AUDIO_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_AUDIO);
|
||||
|
||||
// Build the text renderer.
|
||||
DataSource textDataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher, C.TRACK_TYPE_TEXT,
|
||||
SmoothStreamingChunkSource textChunkSource = new SmoothStreamingChunkSource(C.TRACK_TYPE_TEXT,
|
||||
textDataSource, null);
|
||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
||||
C.DEFAULT_TEXT_BUFFER_SIZE, eventHandler, eventListener, C.TRACK_TYPE_TEXT);
|
||||
|
||||
chunkSources = new SmoothStreamingChunkSource[] {videoChunkSource, audioChunkSource,
|
||||
textChunkSource};
|
||||
sources = new SampleSource[] {videoSampleSource, audioSampleSource, textSampleSource};
|
||||
trackStreamSources = new IdentityHashMap<>();
|
||||
selectedTrackCounts = new int[sources.length];
|
||||
@ -104,6 +109,20 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
||||
if (prepared) {
|
||||
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;
|
||||
for (SampleSource source : sources) {
|
||||
sourcesPrepared &= source.prepare(positionUs);
|
||||
@ -172,6 +191,26 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
source.continueBuffering(positionUs);
|
||||
}
|
||||
@ -216,6 +255,7 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
manifestFetcher.release();
|
||||
for (SampleSource source : sources) {
|
||||
source.release();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user