Add live media playlist refresh requests when live edge is reached

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=131931868
This commit is contained in:
aquilescanta 2016-09-01 04:05:58 -07:00 committed by Oliver Woodman
parent 860c6588c0
commit a671ebd019
3 changed files with 90 additions and 18 deletions

View file

@ -29,7 +29,6 @@ import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.source.chunk.DataChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
@ -49,9 +48,44 @@ import java.util.Arrays;
import java.util.Locale;
/**
* A temporary test source of HLS chunks.
* Source of Hls(possibly adaptive) chunks.
*/
public class HlsChunkSource {
/* package */ class HlsChunkSource {
/**
* Chunk holder that allows the scheduling of retries.
*/
public static final class HlsChunkHolder {
public HlsChunkHolder() {
clear();
}
/**
* The chunk.
*/
public Chunk chunk;
/**
* Indicates that the end of the stream has been reached.
*/
public boolean endOfStream;
/**
* Milliseconds to wait before retrying.
*/
public long retryInMs;
/**
* Clears the holder.
*/
public void clear() {
chunk = null;
endOfStream = false;
retryInMs = C.TIME_UNSET;
}
}
/**
* The default time for which a media playlist should be blacklisted.
@ -173,9 +207,10 @@ public class HlsChunkSource {
/**
* Returns the next chunk to load.
* <p>
* If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has
* been reached then {@link ChunkHolder#endOfStream} is set. If a chunk is not available but the
* end of the stream has not been reached, the {@link ChunkHolder} is not modified.
* If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream has
* been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available but
* the end of the stream has not been reached, {@link HlsChunkHolder#retryInMs} is set to contain
* the amount of milliseconds to wait before retrying.
*
* @param previous The most recently loaded media chunk.
* @param playbackPositionUs The current playback position. If {@code previous} is null then this
@ -183,7 +218,7 @@ public class HlsChunkSource {
* should be interpreted as a seek position.
* @param out A holder to populate.
*/
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) {
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) {
int oldVariantIndex = previous == null ? C.INDEX_UNSET
: trackGroup.indexOf(previous.trackFormat);
@ -206,8 +241,13 @@ public class HlsChunkSource {
int chunkMediaSequence;
if (live) {
if (previous == null) {
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs,
true, true) + mediaPlaylist.mediaSequence;
// When playling a live stream, the starting chunk will be the third counting from the live
// edge.
chunkMediaSequence = Math.max(0, mediaPlaylist.segments.size() - 3)
+ mediaPlaylist.mediaSequence;
// TODO: Bring this back for live window seeking.
// chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs,
// true, true) + mediaPlaylist.mediaSequence;
} else {
chunkMediaSequence = getLiveNextChunkSequenceNumber(previous.chunkIndex, oldVariantIndex,
newVariantIndex);
@ -233,9 +273,16 @@ public class HlsChunkSource {
if (chunkIndex >= mediaPlaylist.segments.size()) {
if (!mediaPlaylist.live) {
out.endOfStream = true;
} else if (shouldRerequestLiveMediaPlaylist(newVariantIndex)) {
out.chunk = newMediaPlaylistChunk(newVariantIndex,
trackSelection.getSelectionReason(), trackSelection.getSelectionData());
} else /* Live */ {
long msToRerequestLiveMediaPlaylist = msToRerequestLiveMediaPlaylist(newVariantIndex);
if (msToRerequestLiveMediaPlaylist <= 0) {
out.chunk = newMediaPlaylistChunk(newVariantIndex,
trackSelection.getSelectionReason(), trackSelection.getSelectionData());
} else {
// 10 milliseconds are added to the wait to make sure the playlist is refreshed when
// getNextChunk() is called.
out.retryInMs = msToRerequestLiveMediaPlaylist + 10;
}
}
return;
}
@ -417,12 +464,12 @@ public class HlsChunkSource {
// Private methods.
private boolean shouldRerequestLiveMediaPlaylist(int variantIndex) {
private long msToRerequestLiveMediaPlaylist(int variantIndex) {
HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
long timeSinceLastMediaPlaylistLoadMs =
SystemClock.elapsedRealtime() - variantLastPlaylistLoadTimesMs[variantIndex];
// Don't re-request media playlist more often than one-half of the target duration.
return timeSinceLastMediaPlaylistLoadMs >= (mediaPlaylist.targetDurationSecs * 1000) / 2;
return (mediaPlaylist.targetDurationSecs * 1000) / 2 - timeSinceLastMediaPlaylistLoadMs;
}
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex, int trackSelectionReason,

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source.hls;
import android.net.Uri;
import android.os.Handler;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@ -60,6 +61,7 @@ import java.util.List;
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
private final HlsPlaylistParser manifestParser;
private final Handler continueLoadingHandler;
private final Loader manifestFetcher;
private final long preparePositionUs;
@ -72,10 +74,11 @@ import java.util.List;
private HlsSampleStreamWrapper[] sampleStreamWrappers;
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader;
private Runnable continueLoadingRunnable;
public HlsMediaPeriod(Uri manifestUri, DataSource.Factory dataSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher,
MediaSource.Listener sourceListener, Callback callback, Allocator allocator,
MediaSource.Listener sourceListener, final Callback callback, Allocator allocator,
long positionUs) {
this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
@ -86,8 +89,15 @@ import java.util.List;
streamWrapperIndices = new IdentityHashMap<>();
timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
manifestParser = new HlsPlaylistParser();
continueLoadingHandler = new Handler();
manifestFetcher = new Loader("Loader:ManifestFetcher");
preparePositionUs = positionUs;
continueLoadingRunnable = new Runnable() {
@Override
public void run() {
callback.onContinueLoadingRequested(HlsMediaPeriod.this);
}
};
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(
dataSourceFactory.createDataSource(), manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
@ -96,6 +106,7 @@ import java.util.List;
}
public void release() {
continueLoadingHandler.removeCallbacksAndMessages(null);
manifestFetcher.release();
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
sampleStreamWrapper.release();
@ -286,6 +297,12 @@ import java.util.List;
sourceListener.onSourceInfoRefreshed(timeline, playlist);
}
@Override
public void onContinueLoadingRequiredInMs(final HlsSampleStreamWrapper sampleStreamWrapper,
long delayMs) {
continueLoadingHandler.postDelayed(continueLoadingRunnable, delayMs);
}
@Override
public void onContinueLoadingRequested(HlsSampleStreamWrapper sampleStreamWrapper) {
if (trackGroups == null) {

View file

@ -30,7 +30,6 @@ import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.Loader;
@ -56,6 +55,12 @@ import java.util.LinkedList;
*/
void onPrepared();
/**
* Called to schedule a {@link #continueLoading(long)} call.
*/
void onContinueLoadingRequiredInMs(HlsSampleStreamWrapper sampleStreamSource,
long delayMs);
}
private static final int PRIMARY_TYPE_NONE = 0;
@ -72,7 +77,7 @@ import java.util.LinkedList;
private final int minLoadableRetryCount;
private final Loader loader;
private final EventDispatcher eventDispatcher;
private final ChunkHolder nextChunkHolder;
private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final LinkedList<HlsMediaChunk> mediaChunks;
@ -121,7 +126,7 @@ import java.util.LinkedList;
this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher;
loader = new Loader("Loader:HlsSampleStreamWrapper");
nextChunkHolder = new ChunkHolder();
nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>();
lastSeekPositionUs = positionUs;
@ -310,6 +315,7 @@ import java.util.LinkedList;
nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream;
Chunk loadable = nextChunkHolder.chunk;
long retryInMs = nextChunkHolder.retryInMs;
nextChunkHolder.clear();
if (endOfStream) {
@ -318,6 +324,8 @@ import java.util.LinkedList;
}
if (loadable == null) {
Assertions.checkState(retryInMs != C.TIME_UNSET && chunkSource.isLive());
callback.onContinueLoadingRequiredInMs(this, retryInMs);
return false;
}