Skip to content

Commit

Permalink
Refactor #6.1: Pull up manifest requests.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ojw28 committed Jun 15, 2016
1 parent 15a890c commit b38d004
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,6 @@ public boolean prepare(long positionUs) throws IOException {
if (prepared) {
return true;
}
if (!chunkSource.prepare()) {
return false;
}
TrackGroup tracks = chunkSource.getTracks();
if (tracks != null) {
trackGroups = new TrackGroupArray(tracks);
Expand Down Expand Up @@ -207,7 +204,6 @@ public TrackStream[] selectTracks(List<TrackStream> oldStreams,
@Override
public void continueBuffering(long positionUs) {
downstreamPositionUs = positionUs;
chunkSource.continueBuffering();
if (!loader.isLoading()) {
maybeStartLoading();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -114,24 +110,12 @@ public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFet
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
Expand All @@ -155,33 +139,10 @@ public void enable(int[] tracks) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;
Expand All @@ -38,6 +37,7 @@

import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Pair;

import java.io.IOException;
Expand All @@ -50,10 +50,13 @@
*/
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;
Expand All @@ -65,34 +68,32 @@ public DashSampleSource(Uri uri, DataSourceFactory dataSourceFactory,
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];
Expand All @@ -103,6 +104,20 @@ public boolean prepare(long positionUs) throws IOException {
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);
Expand Down Expand Up @@ -171,6 +186,30 @@ public TrackStream[] selectTracks(List<TrackStream> oldStreams,

@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);
}
Expand Down Expand Up @@ -215,6 +254,7 @@ public void seekToUs(long positionUs) {

@Override
public void release() {
manifestFetcher.release();
for (SampleSource source : sources) {
source.release();
}
Expand Down
Loading

0 comments on commit b38d004

Please sign in to comment.