Commit 7b9e47ec by olly Committed by Oliver Woodman

Event based buffering.

At a high level, everything that we need to load in sync
with anything else now implements a new SequenceableLoader
class. This includes ChunkTrackStream, since multiple
demuxed tracks in DASH/SS need to be loaded in sync with
one another. At a higher level, SampleSources are also
SequenceableLoaders, which allows them to be kept in sync
by MultiSampleSource.

CompositeSequenceableLoader is able to load multiple
instances SequenceableLoaders in sync with one another,
and is used in various places where this is required.

This change also removes LoadControl registration, which
was complicated and error prone.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=126632861
parent 7b673fe4
Showing with 498 additions and 327 deletions
......@@ -18,7 +18,7 @@ package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ConcatenatingSampleSourceProvider;
import com.google.android.exoplayer.DefaultBufferingPolicy;
import com.google.android.exoplayer.DefaultBufferingControl;
import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
......@@ -268,8 +268,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
trackSelector.addListener(this);
trackSelector.addListener(eventLogger);
trackSelectionHelper = new TrackSelectionHelper(trackSelector);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultBufferingPolicy(),
drmSessionManager, preferExtensionDecoders);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector,
new DefaultBufferingControl(), drmSessionManager, preferExtensionDecoders);
player.addListener(this);
player.addListener(eventLogger);
player.setDebugListener(eventLogger);
......
......@@ -18,26 +18,9 @@ package com.google.android.exoplayer;
import com.google.android.exoplayer.upstream.Allocator;
/**
* A media buffering policy.
* Controls buffering of media.
*/
public interface BufferingPolicy {
/**
* Invoked by the player to update the playback position.
*
* @param playbackPositionUs The current playback position in microseconds.
*/
void setPlaybackPosition(long playbackPositionUs);
/**
* Invoked by the player to determine whether sufficient media is buffered for playback to be
* started or resumed.
*
* @param bufferedPositionUs The position up to which media is buffered.
* @param rebuffering Whether the player is re-buffering.
* @return True if playback should be allowed to start or resume. False otherwise.
*/
boolean haveSufficientBuffer(long bufferedPositionUs, boolean rebuffering);
public interface BufferingControl {
/**
* Invoked by the player when a track selection occurs.
......@@ -55,55 +38,28 @@ public interface BufferingPolicy {
void reset();
/**
* Returns a {@link LoadControl} that a {@link SampleSource} can use to control loads according to
* this policy.
* Gets the {@link Allocator} that should be used to obtain media buffer allocations.
*
* @return The {@link LoadControl}.
* @return The {@link Allocator}.
*/
LoadControl getLoadControl();
Allocator getAllocator();
/**
* Coordinates multiple loaders of time series data.
* Invoked by the player to determine whether sufficient media is buffered for playback to be
* started or resumed.
*
* @param bufferedDurationUs The duration of media that's currently buffered.
* @param rebuffering Whether the player is re-buffering.
* @return True if playback should be allowed to start or resume. False otherwise.
*/
interface LoadControl {
/**
* Registers a loader.
*
* @param loader The loader being registered.
*/
void register(Object loader);
boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering);
/**
* Unregisters a loader.
*
* @param loader The loader being unregistered.
*/
void unregister(Object loader);
/**
* Gets the {@link Allocator} that loaders should use to obtain memory allocations into which
* data can be loaded.
*
* @return The {@link Allocator} to use.
*/
Allocator getAllocator();
/**
* Invoked by a loader to update the control with its current state.
* <p>
* This method must be called by a registered loader whenever its state changes. This is true
* even if the registered loader does not itself wish to start its next load (since the state of
* the loader will still affect whether other registered loaders are allowed to proceed).
*
* @param loader The loader invoking the update.
* @param nextLoadPositionUs The loader's next load position, or {@link C#UNSET_TIME_US} if
* finished, failed, or if the next load position is not yet known.
* @param loading Whether the loader is currently loading data.
* @return True if the loader is allowed to start its next load. False otherwise.
*/
boolean update(Object loader, long nextLoadPositionUs, boolean loading);
}
/**
* Invoked by the player to determine whether buffering should continue.
*
* @param bufferedDurationUs The duration of media that's currently buffered.
* @return True if the buffering should continue. False otherwise.
*/
boolean shouldContinueBuffering(long bufferedDurationUs);
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
/**
* A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s.
*/
public final class CompositeSequenceableLoader implements SequenceableLoader {
private final SequenceableLoader[] loaders;
public CompositeSequenceableLoader(SequenceableLoader[] loaders) {
this.loaders = loaders;
}
@Override
public long getNextLoadPositionUs() {
long nextLoadPositionUs = Long.MAX_VALUE;
for (SequenceableLoader loader : loaders) {
long loaderNextLoadPositionUs = loader.getNextLoadPositionUs();
if (loaderNextLoadPositionUs != C.END_OF_SOURCE_US) {
nextLoadPositionUs = Math.min(nextLoadPositionUs, loaderNextLoadPositionUs);
}
}
return nextLoadPositionUs == Long.MAX_VALUE ? C.END_OF_SOURCE_US : nextLoadPositionUs;
}
@Override
public boolean continueLoading(long positionUs) {
boolean madeProgress = false;
boolean madeProgressThisIteration;
do {
madeProgressThisIteration = false;
long nextLoadPositionUs = getNextLoadPositionUs();
if (nextLoadPositionUs == C.END_OF_SOURCE_US) {
break;
}
for (SequenceableLoader loader : loaders) {
if (loader.getNextLoadPositionUs() == nextLoadPositionUs) {
madeProgressThisIteration |= loader.continueLoading(positionUs);
}
}
madeProgress |= madeProgressThisIteration;
} while (madeProgressThisIteration);
return madeProgress;
}
}
......@@ -42,7 +42,7 @@ public final class ExoPlayerFactory {
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) {
return newSimpleInstance(context, trackSelector, new DefaultBufferingPolicy(), null);
return newSimpleInstance(context, trackSelector, new DefaultBufferingControl(), null);
}
/**
......@@ -52,13 +52,13 @@ public final class ExoPlayerFactory {
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager) {
return newSimpleInstance(context, trackSelector, bufferingPolicy, drmSessionManager, false);
BufferingControl bufferingControl, DrmSessionManager drmSessionManager) {
return newSimpleInstance(context, trackSelector, bufferingControl, drmSessionManager, false);
}
/**
......@@ -68,7 +68,7 @@ public final class ExoPlayerFactory {
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link TrackRenderer} instances defined in
......@@ -76,9 +76,9 @@ public final class ExoPlayerFactory {
* included in the application build for setting this flag to have any effect.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager,
boolean preferExtensionDecoders) {
return newSimpleInstance(context, trackSelector, bufferingPolicy, drmSessionManager,
return newSimpleInstance(context, trackSelector, bufferingControl, drmSessionManager,
preferExtensionDecoders, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
}
......@@ -89,7 +89,7 @@ public final class ExoPlayerFactory {
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link TrackRenderer} instances defined in
......@@ -99,9 +99,9 @@ public final class ExoPlayerFactory {
* seamlessly join an ongoing playback.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
return new SimpleExoPlayer(context, trackSelector, bufferingPolicy, drmSessionManager,
return new SimpleExoPlayer(context, trackSelector, bufferingControl, drmSessionManager,
preferExtensionDecoders, allowedVideoJoiningTimeMs);
}
......@@ -114,7 +114,7 @@ public final class ExoPlayerFactory {
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector) {
return newInstance(renderers, trackSelector, new DefaultBufferingPolicy());
return newInstance(renderers, trackSelector, new DefaultBufferingControl());
}
/**
......@@ -124,11 +124,11 @@ public final class ExoPlayerFactory {
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector,
BufferingPolicy bufferingPolicy) {
return new ExoPlayerImpl(renderers, trackSelector, bufferingPolicy);
BufferingControl bufferingControl) {
return new ExoPlayerImpl(renderers, trackSelector, bufferingControl);
}
}
......@@ -55,11 +55,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
*/
@SuppressLint("HandlerLeak")
public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector,
BufferingPolicy bufferingPolicy) {
BufferingControl bufferingControl) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
Assertions.checkNotNull(renderers);
Assertions.checkState(renderers.length > 0);
......@@ -72,7 +72,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
ExoPlayerImpl.this.handleEvent(msg);
}
};
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, bufferingPolicy,
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, bufferingControl,
playWhenReady, eventHandler);
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0);
}
......
......@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
import com.google.android.exoplayer.upstream.Allocator;
import android.util.Pair;
......@@ -40,6 +40,7 @@ public final class MultiSampleSource implements SampleSource, SampleSource.Callb
private boolean seenFirstTrackSelection;
private SampleSource[] enabledSources;
private SequenceableLoader sequenceableLoader;
public MultiSampleSource(SampleSource... sources) {
this.sources = sources;
......@@ -49,10 +50,10 @@ public final class MultiSampleSource implements SampleSource, SampleSource.Callb
}
@Override
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
public void prepare(Callback callback, Allocator allocator, long positionUs) {
this.callback = callback;
for (SampleSource source : sources) {
source.prepare(this, loadControl, positionUs);
source.prepare(this, allocator, positionUs);
}
}
......@@ -86,6 +87,7 @@ public final class MultiSampleSource implements SampleSource, SampleSource.Callb
enabledSourceCount++;
}
}
seenFirstTrackSelection = true;
// Update the enabled sources.
enabledSources = new SampleSource[enabledSourceCount];
enabledSourceCount = 0;
......@@ -94,15 +96,18 @@ public final class MultiSampleSource implements SampleSource, SampleSource.Callb
enabledSources[enabledSourceCount++] = sources[i];
}
}
seenFirstTrackSelection = true;
sequenceableLoader = new CompositeSequenceableLoader(enabledSources);
return newStreams;
}
@Override
public void continueBuffering(long positionUs) {
for (SampleSource source : enabledSources) {
source.continueBuffering(positionUs);
}
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
}
@Override
public long getNextLoadPositionUs() {
return sequenceableLoader.getNextLoadPositionUs();
}
@Override
......@@ -185,6 +190,11 @@ public final class MultiSampleSource implements SampleSource, SampleSource.Callb
callback.onSourcePrepared(this);
}
@Override
public void onContinueLoadingRequested(SampleSource ignored) {
callback.onContinueLoadingRequested(this);
}
// Internal methods.
private int selectTracks(SampleSource source, List<TrackStream> allOldStreams,
......
......@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
import com.google.android.exoplayer.upstream.Allocator;
import java.io.IOException;
import java.util.List;
......@@ -23,12 +23,12 @@ import java.util.List;
/**
* A source of media.
*/
public interface SampleSource {
public interface SampleSource extends SequenceableLoader {
/**
* A callback to be notified of {@link SampleSource} events.
*/
interface Callback {
interface Callback extends SequenceableLoader.Callback<SampleSource> {
/**
* Invoked by the source when preparation completes.
......@@ -50,11 +50,10 @@ public interface SampleSource {
* invoked.
*
* @param callback A callback to receive updates from the source.
* @param loadControl A {@link LoadControl} to determine when to load data.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param positionUs The player's current playback position.
* @return True if the source is prepared, false otherwise.
*/
void prepare(Callback callback, LoadControl loadControl, long positionUs);
void prepare(Callback callback, Allocator allocator, long positionUs);
/**
* Throws an error that's preventing the source from becoming prepared. Does nothing if no such
......@@ -109,15 +108,6 @@ public interface SampleSource {
long positionUs);
/**
* Indicates to the source that it should continue buffering data for its enabled tracks.
* <p>
* This method should only be called when at least one track is selected.
*
* @param positionUs The current playback position.
*/
void continueBuffering(long positionUs);
/**
* Attempts to read a discontinuity.
* <p>
* After this method has returned a value other than {@link C#UNSET_TIME_US}, all
......
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
/**
* An loader that can proceed in approximate synchronization with other loaders.
*/
public interface SequenceableLoader {
/**
* A callback to be notified of {@link SequenceableLoader} events.
*/
interface Callback<T extends SequenceableLoader> {
/**
* Invoked by the loader to indicate that it wishes for its {@link #continueLoading(long)}
* method to be called when it can continue to load data.
*/
void onContinueLoadingRequested(T source);
}
/**
* Returns the next load time, or {@link C#END_OF_SOURCE_US} if loading has finished.
*/
long getNextLoadPositionUs();
/**
* Attempts to continue loading.
*
* @param positionUs The current playback position.
* @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return
* a different value than prior to the call. False otherwise.
*/
boolean continueLoading(long positionUs);
}
......@@ -111,7 +111,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
private CodecCounters audioCodecCounters;
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector,
BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler();
bandwidthMeter = new DefaultBandwidthMeter();
......@@ -145,7 +145,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
this.audioRendererCount = audioRendererCount;
// Build the player and associated objects.
player = new ExoPlayerImpl(renderers, trackSelector, bufferingPolicy);
player = new ExoPlayerImpl(renderers, trackSelector, bufferingControl);
}
/**
......
......@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.Loader;
......@@ -109,7 +109,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
// SampleSource implementation.
@Override
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
public void prepare(Callback callback, Allocator allocator, long positionUs) {
callback.onSourcePrepared(this);
}
......@@ -133,26 +133,27 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(oldStreams.size() <= 1);
Assertions.checkState(newSelections.size() <= 1);
// Unselect old tracks.
if (!oldStreams.isEmpty()) {
streamState = STREAM_STATE_END_OF_STREAM;
if (loader.isLoading()) {
loader.cancelLoading();
}
}
// Select new tracks.
TrackStream[] newStreams = new TrackStream[newSelections.size()];
if (!newSelections.isEmpty()) {
newStreams[0] = this;
streamState = STREAM_STATE_SEND_FORMAT;
maybeStartLoading();
}
return newStreams;
}
@Override
public void continueBuffering(long positionUs) {
// Do nothing.
public boolean continueLoading(long positionUs) {
if (loadingFinished || loader.isLoading()) {
return false;
}
loader.startLoading(this, this, minLoadableRetryCount);
return true;
}
@Override
public long getNextLoadPositionUs() {
return loadingFinished || loader.isLoading() ? C.END_OF_SOURCE_US : 0;
}
@Override
......@@ -170,6 +171,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
@Override
public void release() {
sampleData = null;
loader.release();
}
......@@ -225,9 +227,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
@Override
public void onLoadCanceled(SingleSampleSource loadable, long elapsedRealtimeMs,
long loadDurationMs, boolean released) {
if (!released) {
maybeStartLoading();
}
// Never happens.
}
@Override
......@@ -272,13 +272,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
// Internal methods.
private void maybeStartLoading() {
if (loadingFinished || streamState == STREAM_STATE_END_OF_STREAM || loader.isLoading()) {
return;
}
loader.startLoading(this, this, minLoadableRetryCount);
}
private void notifyLoadError(final IOException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
......
......@@ -17,11 +17,12 @@ package com.google.android.exoplayer.dash;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.CompositeSequenceableLoader;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SequenceableLoader;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
......@@ -35,6 +36,7 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.dash.mpd.UtcTimingElement;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
......@@ -61,7 +63,8 @@ import java.util.TimeZone;
/**
* A {@link SampleSource} for DASH media.
*/
public final class DashSampleSource implements SampleSource {
public final class DashSampleSource implements SampleSource,
SequenceableLoader.Callback<ChunkTrackStream<DashChunkSource>> {
private static final String TAG = "DashSampleSource";
......@@ -84,13 +87,16 @@ public final class DashSampleSource implements SampleSource {
private MediaPresentationDescription manifest;
private Callback callback;
private LoadControl loadControl;
private Allocator allocator;
private Handler manifestRefreshHandler;
private boolean prepared;
private long durationUs;
private long elapsedRealtimeOffset;
private TrackGroupArray trackGroups;
private int[] trackGroupAdaptationSetIndices;
private ChunkTrackStream<DashChunkSource>[] trackStreams;
private CompositeSequenceableLoader sequenceableLoader;
public DashSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
......@@ -99,17 +105,19 @@ public final class DashSampleSource implements SampleSource {
this.dataSourceFactory = dataSourceFactory;
this.bandwidthMeter = bandwidthMeter;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
loader = new Loader("Loader:DashSampleSource");
dataSource = dataSourceFactory.createDataSource();
loader = new Loader("Loader:DashSampleSource");
manifestParser = new MediaPresentationDescriptionParser();
manifestCallback = new ManifestCallback();
trackStreams = newTrackStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(trackStreams);
}
@Override
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
public void prepare(Callback callback, Allocator allocator, long positionUs) {
this.callback = callback;
this.loadControl = loadControl;
this.allocator = allocator;
manifestRefreshHandler = new Handler();
startLoadingManifest();
}
......@@ -154,28 +162,18 @@ public final class DashSampleSource implements SampleSource {
}
trackStreams = newTrackStreams;
sequenceableLoader = new CompositeSequenceableLoader(trackStreams);
return streamsToReturn;
}
@Override
public void continueBuffering(long positionUs) {
if (manifest.dynamic) {
long minUpdatePeriod = manifest.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 (!loader.isLoading()
&& SystemClock.elapsedRealtime() > manifestLoadStartTimestamp + minUpdatePeriod) {
startLoadingManifest();
}
}
for (ChunkTrackStream<DashChunkSource> trackStream : trackStreams) {
trackStream.continueBuffering(positionUs);
}
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
}
@Override
public long getNextLoadPositionUs() {
return sequenceableLoader.getNextLoadPositionUs();
}
@Override
......@@ -205,12 +203,23 @@ public final class DashSampleSource implements SampleSource {
@Override
public void release() {
if (manifestRefreshHandler != null) {
manifestRefreshHandler.removeCallbacksAndMessages(null);
manifestRefreshHandler = null;
}
loader.release();
for (ChunkTrackStream<DashChunkSource> trackStream : trackStreams) {
trackStream.release();
}
}
// SequenceableLoader.Callback implementation.
@Override
public void onContinueLoadingRequested(ChunkTrackStream<DashChunkSource> trackStream) {
callback.onContinueLoadingRequested(this);
}
// Loadable callbacks.
/* package */ void onManifestLoadCompleted(ParsingLoadable<MediaPresentationDescription> loadable,
......@@ -229,13 +238,14 @@ public final class DashSampleSource implements SampleSource {
if (manifest.utcTiming != null) {
resolveUtcTimingElement(manifest.utcTiming);
} else {
prepared = true;
callback.onSourcePrepared(this);
finishPrepare();
}
} else {
for (ChunkTrackStream<DashChunkSource> trackStream : trackStreams) {
trackStream.getChunkSource().updateManifest(manifest);
}
callback.onContinueLoadingRequested(this);
scheduleManifestRefresh();
}
}
......@@ -307,15 +317,41 @@ public final class DashSampleSource implements SampleSource {
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
this.elapsedRealtimeOffset = elapsedRealtimeOffsetMs;
prepared = true;
callback.onSourcePrepared(this);
finishPrepare();
}
private void onUtcTimestampResolutionError(IOException error) {
Log.e(TAG, "Failed to resolve UtcTiming element.", error);
// Be optimistic and continue in the hope that the device clock is correct.
finishPrepare();
}
private void finishPrepare() {
prepared = true;
callback.onSourcePrepared(this);
scheduleManifestRefresh();
}
private void scheduleManifestRefresh() {
if (!manifest.dynamic) {
return;
}
long minUpdatePeriod = manifest.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;
}
long nextLoadTimestamp = manifestLoadStartTimestamp + minUpdatePeriod;
long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime());
manifestRefreshHandler.postDelayed(new Runnable() {
@Override
public void run() {
startLoadingManifest();
}
}, delayUntilNextLoad);
}
private <T> void startLoading(ParsingLoadable<T> loadable,
......@@ -365,7 +401,7 @@ public final class DashSampleSource implements SampleSource {
DashChunkSource chunkSource = new DashChunkSource(loader, manifest, adaptationSetIndex,
trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator,
elapsedRealtimeOffset);
return new ChunkTrackStream<>(adaptationSetType, chunkSource, loadControl, positionUs,
return new ChunkTrackStream<>(adaptationSetType, chunkSource, this, allocator, positionUs,
MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
}
......
......@@ -17,8 +17,8 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.CompositeSequenceableLoader;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleSource;
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer.hls.playlist.Variant;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
......@@ -71,7 +72,7 @@ public final class HlsSampleSource implements SampleSource,
private final HlsPlaylistParser manifestParser;
private Callback callback;
private LoadControl loadControl;
private Allocator allocator;
private long preparePositionUs;
private int pendingPrepareCount;
......@@ -82,6 +83,7 @@ public final class HlsSampleSource implements SampleSource,
private int[] selectedTrackCounts;
private HlsTrackStreamWrapper[] trackStreamWrappers;
private HlsTrackStreamWrapper[] enabledTrackStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader;
public HlsSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
......@@ -100,9 +102,9 @@ public final class HlsSampleSource implements SampleSource,
}
@Override
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
public void prepare(Callback callback, Allocator allocator, long positionUs) {
this.callback = callback;
this.loadControl = loadControl;
this.allocator = allocator;
this.preparePositionUs = positionUs;
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(manifestDataSource,
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
......@@ -147,6 +149,7 @@ public final class HlsSampleSource implements SampleSource,
}
// Update the enabled wrappers.
enabledTrackStreamWrappers = new HlsTrackStreamWrapper[enabledTrackStreamWrapperCount];
sequenceableLoader = new CompositeSequenceableLoader(enabledTrackStreamWrappers);
enabledTrackStreamWrapperCount = 0;
for (int i = 0; i < trackStreamWrappers.length; i++) {
if (selectedTrackCounts[i] > 0) {
......@@ -161,10 +164,13 @@ public final class HlsSampleSource implements SampleSource,
}
@Override
public void continueBuffering(long positionUs) {
for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
trackStreamWrapper.continueBuffering(positionUs);
}
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
}
@Override
public long getNextLoadPositionUs() {
return sequenceableLoader.getNextLoadPositionUs();
}
@Override
......@@ -190,7 +196,7 @@ public final class HlsSampleSource implements SampleSource,
positionUs = isLive ? 0 : positionUs;
timestampAdjusterProvider.reset();
for (HlsTrackStreamWrapper trackStreamWrapper : enabledTrackStreamWrappers) {
trackStreamWrapper.restartFrom(positionUs);
trackStreamWrapper.seekTo(positionUs);
}
return positionUs;
}
......@@ -239,7 +245,7 @@ public final class HlsSampleSource implements SampleSource,
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
}
// HlsTrackStreamWrapper callback.
// HlsTrackStreamWrapper.Callback implementation.
@Override
public void onPrepared() {
......@@ -267,6 +273,11 @@ public final class HlsSampleSource implements SampleSource,
callback.onSourcePrepared(this);
}
@Override
public void onContinueLoadingRequested(HlsTrackStreamWrapper trackStreanWrapper) {
callback.onContinueLoadingRequested(this);
}
// Internal methods.
private List<HlsTrackStreamWrapper> buildTrackStreamWrappers(HlsPlaylist playlist) {
......@@ -343,7 +354,7 @@ public final class HlsSampleSource implements SampleSource,
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
timestampAdjusterProvider, formatEvaluator);
return new HlsTrackStreamWrapper(trackType, this, defaultChunkSource, loadControl,
return new HlsTrackStreamWrapper(trackType, this, defaultChunkSource, allocator,
preparePositionUs, muxedAudioFormat, muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT,
eventDispatcher);
}
......
......@@ -17,11 +17,12 @@ package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.AdaptiveSourceEventListener;
import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher;
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.CompositeSequenceableLoader;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SequenceableLoader;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
......@@ -32,6 +33,7 @@ import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
......@@ -52,6 +54,7 @@ import java.util.List;
* A {@link SampleSource} for SmoothStreaming media.
*/
public final class SmoothStreamingSampleSource implements SampleSource,
SequenceableLoader.Callback<ChunkTrackStream<SmoothStreamingChunkSource>>,
Loader.Callback<ParsingLoadable<SmoothStreamingManifest>> {
/**
......@@ -74,7 +77,8 @@ public final class SmoothStreamingSampleSource implements SampleSource,
private SmoothStreamingManifest manifest;
private Callback callback;
private LoadControl loadControl;
private Allocator allocator;
private Handler manifestRefreshHandler;
private boolean prepared;
private long durationUs;
private TrackEncryptionBox[] trackEncryptionBoxes;
......@@ -82,6 +86,7 @@ public final class SmoothStreamingSampleSource implements SampleSource,
private int[] trackGroupElementIndices;
private ChunkTrackStream<SmoothStreamingChunkSource>[] trackStreams;
private CompositeSequenceableLoader sequenceableLoader;
public SmoothStreamingSampleSource(Uri manifestUri, DataSourceFactory dataSourceFactory,
BandwidthMeter bandwidthMeter, Handler eventHandler,
......@@ -92,15 +97,17 @@ public final class SmoothStreamingSampleSource implements SampleSource,
this.bandwidthMeter = bandwidthMeter;
this.eventDispatcher = new EventDispatcher(eventHandler, eventListener);
trackStreams = newTrackStreamArray(0);
sequenceableLoader = new CompositeSequenceableLoader(trackStreams);
manifestDataSource = dataSourceFactory.createDataSource();
manifestParser = new SmoothStreamingManifestParser();
manifestLoader = new Loader("Loader:Manifest");
}
@Override
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
public void prepare(Callback callback, Allocator allocator, long positionUs) {
this.callback = callback;
this.loadControl = loadControl;
this.allocator = allocator;
manifestRefreshHandler = new Handler();
startLoadingManifest();
}
......@@ -145,25 +152,18 @@ public final class SmoothStreamingSampleSource implements SampleSource,
}
trackStreams = newTrackStreams;
sequenceableLoader = new CompositeSequenceableLoader(trackStreams);
return streamsToReturn;
}
@Override
public void continueBuffering(long positionUs) {
if (manifest.isLive) {
if (!manifestLoader.isLoading() && SystemClock.elapsedRealtime()
> manifestLoadStartTimestamp + MINIMUM_MANIFEST_REFRESH_PERIOD_MS) {
for (ChunkTrackStream<SmoothStreamingChunkSource> trackStream : trackStreams) {
if (trackStream.getChunkSource().needManifestRefresh()) {
startLoadingManifest();
break;
}
}
}
}
for (ChunkTrackStream<SmoothStreamingChunkSource> trackStream : trackStreams) {
trackStream.continueBuffering(positionUs);
}
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
}
@Override
public long getNextLoadPositionUs() {
return sequenceableLoader.getNextLoadPositionUs();
}
@Override
......@@ -193,12 +193,23 @@ public final class SmoothStreamingSampleSource implements SampleSource,
@Override
public void release() {
if (manifestRefreshHandler != null) {
manifestRefreshHandler.removeCallbacksAndMessages(null);
manifestRefreshHandler = null;
}
manifestLoader.release();
for (ChunkTrackStream<SmoothStreamingChunkSource> trackStream : trackStreams) {
trackStream.release();
}
}
// SequenceableLoader.Callback implementation
@Override
public void onContinueLoadingRequested(ChunkTrackStream<SmoothStreamingChunkSource> trackStream) {
callback.onContinueLoadingRequested(this);
}
// Loader.Callback implementation
@Override
......@@ -223,7 +234,9 @@ public final class SmoothStreamingSampleSource implements SampleSource,
for (ChunkTrackStream<SmoothStreamingChunkSource> trackStream : trackStreams) {
trackStream.getChunkSource().updateManifest(manifest);
}
callback.onContinueLoadingRequested(this);
}
scheduleManifestRefresh();
}
@Override
......@@ -244,6 +257,20 @@ public final class SmoothStreamingSampleSource implements SampleSource,
// Internal methods
private void scheduleManifestRefresh() {
if (!manifest.isLive) {
return;
}
long nextLoadTimestamp = manifestLoadStartTimestamp + MINIMUM_MANIFEST_REFRESH_PERIOD_MS;
long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime());
manifestRefreshHandler.postDelayed(new Runnable() {
@Override
public void run() {
startLoadingManifest();
}
}, delayUntilNextLoad);
}
private void startLoadingManifest() {
ParsingLoadable<SmoothStreamingManifest> loadable = new ParsingLoadable<>(manifestDataSource,
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
......@@ -285,7 +312,7 @@ public final class SmoothStreamingSampleSource implements SampleSource,
SmoothStreamingChunkSource chunkSource = new SmoothStreamingChunkSource(manifestLoader,
manifest, streamElementIndex, trackGroups.get(selection.group), selectedTracks, dataSource,
adaptiveEvaluator, trackEncryptionBoxes);
return new ChunkTrackStream<>(streamElementType, chunkSource, loadControl, positionUs,
return new ChunkTrackStream<>(streamElementType, chunkSource, this, allocator, positionUs,
MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
}
......
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.util;
/**
* A condition variable whose {@link #open()} and {@link #close()} methods return whether they
* resulted in a change of state.
*/
public final class ConditionVariable {
private boolean isOpen;
/**
* Opens the condition and releases all threads that are blocked.
*
* @return True if the condition variable was opened. False if it was already open.
*/
public synchronized boolean open() {
if (isOpen) {
return false;
}
isOpen = true;
notifyAll();
return true;
}
/**
* Closes the condition.
*
* @return True if the condition variable was closed. False if it was already closed.
*/
public synchronized boolean close() {
boolean wasOpen = isOpen;
isOpen = false;
return wasOpen;
}
/**
* Blocks until the condition is opened.
*
* @throws InterruptedException If the thread is interrupted.
*/
public synchronized void block() throws InterruptedException {
while (!isOpen) {
wait();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment