Commit e6ca2df5 by Oliver Woodman

Fix end-of-stream for live streams.

Issue: #764
parent 89fcafec
...@@ -46,7 +46,6 @@ public abstract class BaseMediaChunk extends MediaChunk { ...@@ -46,7 +46,6 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param isLastChunk True if this is the last chunk in the media. False otherwise.
* @param isMediaFormatFinal True if {@link #getMediaFormat()} and {@link #getDrmInitData()} can * @param isMediaFormatFinal True if {@link #getMediaFormat()} and {@link #getDrmInitData()} can
* be called at any time to obtain the media format and drm initialization data. False if * be called at any time to obtain the media format and drm initialization data. False if
* these methods are only guaranteed to return correct data after the first sample data has * these methods are only guaranteed to return correct data after the first sample data has
...@@ -54,10 +53,8 @@ public abstract class BaseMediaChunk extends MediaChunk { ...@@ -54,10 +53,8 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param parentId Identifier for a parent from which this chunk originates. * @param parentId Identifier for a parent from which this chunk originates.
*/ */
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk, long startTimeUs, long endTimeUs, int chunkIndex, boolean isMediaFormatFinal, int parentId) {
boolean isMediaFormatFinal, int parentId) { super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk,
parentId);
this.isMediaFormatFinal = isMediaFormatFinal; this.isMediaFormatFinal = isMediaFormatFinal;
} }
......
...@@ -16,8 +16,13 @@ ...@@ -16,8 +16,13 @@
package com.google.android.exoplayer.chunk; package com.google.android.exoplayer.chunk;
/** /**
* Holds a chunk operation, which consists of a {@link Chunk} to load together with the number of * Holds a chunk operation, which consists of a either:
* {@link MediaChunk}s that should be retained on the queue. * <ul>
* <li>The number of {@link MediaChunk}s that should be retained on the queue ({@link #queueSize})
* together with the next {@link Chunk} to load ({@link #chunk}). {@link #chunk} may be null if the
* next chunk cannot be provided yet.</li>
* <li>A flag indicating that the end of the stream has been reached ({@link #endOfStream}).</li>
* </ul>
*/ */
public final class ChunkOperationHolder { public final class ChunkOperationHolder {
...@@ -31,4 +36,18 @@ public final class ChunkOperationHolder { ...@@ -31,4 +36,18 @@ public final class ChunkOperationHolder {
*/ */
public Chunk chunk; public Chunk chunk;
/**
* Indicates that the end of the stream has been reached.
*/
public boolean endOfStream;
/**
* Clears the holder.
*/
public void clear() {
queueSize = 0;
chunk = null;
endOfStream = false;
}
} }
...@@ -325,10 +325,9 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load ...@@ -325,10 +325,9 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
Chunk currentLoadable = currentLoadableHolder.chunk; Chunk currentLoadable = currentLoadableHolder.chunk;
chunkSource.onChunkLoadCompleted(currentLoadable); chunkSource.onChunkLoadCompleted(currentLoadable);
if (isMediaChunk(currentLoadable)) { if (isMediaChunk(currentLoadable)) {
MediaChunk mediaChunk = (MediaChunk) currentLoadable; BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
notifyLoadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger, notifyLoadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, loadDurationMs); mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, loadDurationMs);
loadingFinished = ((BaseMediaChunk) currentLoadable).isLastChunk;
} else { } else {
notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type, notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs); currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
...@@ -408,9 +407,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load ...@@ -408,9 +407,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
|| (now - lastPerformedBufferOperation > 2000))) { || (now - lastPerformedBufferOperation > 2000))) {
// Perform the evaluation. // Perform the evaluation.
lastPerformedBufferOperation = now; lastPerformedBufferOperation = now;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size(); doChunkOperation();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize); boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
// Update the next load position as appropriate. // Update the next load position as appropriate.
if (currentLoadableHolder.chunk == null) { if (currentLoadableHolder.chunk == null) {
...@@ -447,8 +444,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load ...@@ -447,8 +444,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
if (isPendingReset()) { if (isPendingReset()) {
return pendingResetPositionUs; return pendingResetPositionUs;
} else { } else {
BaseMediaChunk lastMediaChunk = mediaChunks.getLast(); return loadingFinished ? -1 : mediaChunks.getLast().endTimeUs;
return lastMediaChunk.isLastChunk ? -1 : lastMediaChunk.endTimeUs;
} }
} }
...@@ -464,9 +460,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load ...@@ -464,9 +460,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
Chunk backedOffChunk = currentLoadableHolder.chunk; Chunk backedOffChunk = currentLoadableHolder.chunk;
if (!isMediaChunk(backedOffChunk)) { if (!isMediaChunk(backedOffChunk)) {
currentLoadableHolder.queueSize = readOnlyMediaChunks.size(); doChunkOperation();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
discardUpstreamMediaChunks(currentLoadableHolder.queueSize); discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
if (currentLoadableHolder.chunk == backedOffChunk) { if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading. // Chunk was unchanged. Resume loading.
...@@ -491,9 +485,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load ...@@ -491,9 +485,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
// and add it back again afterwards. // and add it back again afterwards.
BaseMediaChunk removedChunk = mediaChunks.removeLast(); BaseMediaChunk removedChunk = mediaChunks.removeLast();
Assertions.checkState(backedOffChunk == removedChunk); Assertions.checkState(backedOffChunk == removedChunk);
currentLoadableHolder.queueSize = readOnlyMediaChunks.size(); doChunkOperation();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs, downstreamPositionUs,
currentLoadableHolder);
mediaChunks.add(removedChunk); mediaChunks.add(removedChunk);
if (currentLoadableHolder.chunk == backedOffChunk) { if (currentLoadableHolder.chunk == backedOffChunk) {
...@@ -534,6 +526,19 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load ...@@ -534,6 +526,19 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
} }
/** /**
* Sets up the {@link #currentLoadableHolder}, passes it to the chunk source to cause it to be
* updated with the next operation, and updates {@link #loadingFinished} if the end of the stream
* is reached.
*/
private void doChunkOperation() {
currentLoadableHolder.endOfStream = false;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs, downstreamPositionUs,
currentLoadableHolder);
loadingFinished = currentLoadableHolder.endOfStream;
}
/**
* Discard upstream media chunks until the queue length is equal to the length specified. * Discard upstream media chunks until the queue length is equal to the length specified.
* *
* @param queueLength The desired length of the queue. * @param queueLength The desired length of the queue.
......
...@@ -89,24 +89,19 @@ public interface ChunkSource { ...@@ -89,24 +89,19 @@ public interface ChunkSource {
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should * Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link ChunkSampleSource}. * be performed by the calling {@link ChunkSampleSource}.
* <p> * <p>
* The next operation comprises of a possibly shortened queue length (shortened if the
* implementation wishes for the caller to discard {@link MediaChunk}s from the queue), together
* with the next {@link Chunk} to load. The next chunk may be a {@link MediaChunk} to be added to
* the queue, or another {@link Chunk} type (e.g. to load initialization data), or null if the
* source is not able to provide a chunk in its current state.
* <p>
* This method should only be called when the source is enabled. * This method should only be called when the source is enabled.
* *
* @param queue A representation of the currently buffered {@link MediaChunk}s. * @param queue A representation of the currently buffered {@link MediaChunk}s.
* @param seekPositionUs If the queue is empty, this parameter must specify the seek position. If * @param seekPositionUs If the queue is empty, this parameter must specify the seek position. If
* the queue is non-empty then this parameter is ignored. * the queue is non-empty then this parameter is ignored.
* @param playbackPositionUs The current playback position. * @param playbackPositionUs The current playback position.
* @param out A holder for the next operation, whose {@link ChunkOperationHolder#queueSize} is * @param out A holder for the next operation, whose {@link ChunkOperationHolder#endOfStream} is
* initially equal to the length of the queue, and whose {@link ChunkOperationHolder#chunk} is * initially set to false, whose {@link ChunkOperationHolder#queueSize} is initially equal to
* initially equal to null or a {@link Chunk} previously supplied by the {@link ChunkSource} * the length of the queue, and whose {@link ChunkOperationHolder#chunk} is initially equal to
* that the caller has not yet finished loading. In the latter case the chunk can either be * null or a {@link Chunk} previously supplied by the {@link ChunkSource} that the caller has
* replaced or left unchanged. Note that leaving the chunk unchanged is both preferred and * not yet finished loading. In the latter case the chunk can either be replaced or left
* more efficient than replacing it with a new but identical chunk. * unchanged. Note that leaving the chunk unchanged is both preferred and more efficient than
* replacing it with a new but identical chunk.
*/ */
void getChunkOperation(List<? extends MediaChunk> queue, long seekPositionUs, void getChunkOperation(List<? extends MediaChunk> queue, long seekPositionUs,
long playbackPositionUs, ChunkOperationHolder out); long playbackPositionUs, ChunkOperationHolder out);
......
...@@ -53,7 +53,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu ...@@ -53,7 +53,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param isLastChunk True if this is the last chunk in the media. False otherwise.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor. * @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
* @param extractorWrapper A wrapped extractor to use for parsing the data. * @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param mediaFormat The {@link MediaFormat} of the chunk, if known. May be null if the data is * @param mediaFormat The {@link MediaFormat} of the chunk, if known. May be null if the data is
...@@ -71,10 +70,10 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu ...@@ -71,10 +70,10 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
* @param parentId Identifier for a parent from which this chunk originates. * @param parentId Identifier for a parent from which this chunk originates.
*/ */
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk, long sampleOffsetUs, long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth, ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth,
int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) { int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk, super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
isMediaFormatFinal, parentId); isMediaFormatFinal, parentId);
this.extractorWrapper = extractorWrapper; this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs; this.sampleOffsetUs = sampleOffsetUs;
......
...@@ -36,14 +36,10 @@ public abstract class MediaChunk extends Chunk { ...@@ -36,14 +36,10 @@ public abstract class MediaChunk extends Chunk {
* The chunk index. * The chunk index.
*/ */
public final int chunkIndex; public final int chunkIndex;
/**
* True if this is the last chunk in the media. False otherwise.
*/
public final boolean isLastChunk;
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) { long startTimeUs, long endTimeUs, int chunkIndex) {
this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk, this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
Chunk.NO_PARENT_ID); Chunk.NO_PARENT_ID);
} }
...@@ -55,17 +51,15 @@ public abstract class MediaChunk extends Chunk { ...@@ -55,17 +51,15 @@ public abstract class MediaChunk extends Chunk {
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param isLastChunk True if this is the last chunk in the media. False otherwise.
* @param parentId Identifier for a parent from which this chunk originates. * @param parentId Identifier for a parent from which this chunk originates.
*/ */
public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk, int parentId) { long startTimeUs, long endTimeUs, int chunkIndex, int parentId) {
super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format, parentId); super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format, parentId);
Assertions.checkNotNull(format); Assertions.checkNotNull(format);
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs; this.endTimeUs = endTimeUs;
this.chunkIndex = chunkIndex; this.chunkIndex = chunkIndex;
this.isLastChunk = isLastChunk;
} }
} }
...@@ -84,6 +84,7 @@ public final class SingleSampleChunkSource implements ChunkSource { ...@@ -84,6 +84,7 @@ public final class SingleSampleChunkSource implements ChunkSource {
long playbackPositionUs, ChunkOperationHolder out) { long playbackPositionUs, ChunkOperationHolder out) {
if (!queue.isEmpty()) { if (!queue.isEmpty()) {
// We've already provided the single sample. // We've already provided the single sample.
out.endOfStream = true;
return; return;
} }
out.chunk = initChunk(); out.chunk = initChunk();
...@@ -111,7 +112,7 @@ public final class SingleSampleChunkSource implements ChunkSource { ...@@ -111,7 +112,7 @@ public final class SingleSampleChunkSource implements ChunkSource {
private SingleSampleMediaChunk initChunk() { private SingleSampleMediaChunk initChunk() {
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_UNSPECIFIED, format, 0, return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_UNSPECIFIED, format, 0,
durationUs, 0, true, mediaFormat, null, Chunk.NO_PARENT_ID); durationUs, 0, mediaFormat, null, Chunk.NO_PARENT_ID);
} }
} }
...@@ -43,17 +43,16 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk { ...@@ -43,17 +43,16 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param isLastChunk True if this is the last chunk in the media. False otherwise.
* @param sampleFormat The format of the sample. * @param sampleFormat The format of the sample.
* @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm * @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
* protected. * protected.
* @param parentId Identifier for a parent from which this chunk originates. * @param parentId Identifier for a parent from which this chunk originates.
*/ */
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk, Format format, long startTimeUs, long endTimeUs, int chunkIndex, MediaFormat sampleFormat,
MediaFormat sampleFormat, DrmInitData sampleDrmInitData, int parentId) { DrmInitData sampleDrmInitData, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk, super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
true, parentId); parentId);
this.sampleFormat = sampleFormat; this.sampleFormat = sampleFormat;
this.sampleDrmInitData = sampleDrmInitData; this.sampleDrmInitData = sampleDrmInitData;
} }
......
...@@ -400,11 +400,6 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -400,11 +400,6 @@ public class DashChunkSource implements ChunkSource, Output {
} }
MediaChunk previous = queue.get(out.queueSize - 1); MediaChunk previous = queue.get(out.queueSize - 1);
if (previous.isLastChunk) {
// We've reached the end of the stream.
return;
}
long nextSegmentStartTimeUs = previous.endTimeUs; long nextSegmentStartTimeUs = previous.endTimeUs;
if (live && nextSegmentStartTimeUs < availableRangeValues[0]) { if (live && nextSegmentStartTimeUs < availableRangeValues[0]) {
// This is before the first chunk in the current manifest. // This is before the first chunk in the current manifest.
...@@ -415,6 +410,17 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -415,6 +410,17 @@ public class DashChunkSource implements ChunkSource, Output {
// we'll need to wait until it's refreshed. If it's unbounded we just need to wait for a // we'll need to wait until it's refreshed. If it's unbounded we just need to wait for a
// while before attempting to load the chunk. // while before attempting to load the chunk.
return; return;
} else if (!currentManifest.dynamic) {
// The current manifest isn't dynamic, so check whether we've reached the end of the stream.
PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1);
if (previous.parentId == lastPeriodHolder.localIndex) {
RepresentationHolder representationHolder =
lastPeriodHolder.representationHolders.get(previous.format.id);
if (representationHolder.isLastSegment(previous.chunkIndex)) {
out.endOfStream = true;
return;
}
}
} }
startingNewPeriod = false; startingNewPeriod = false;
...@@ -701,13 +707,8 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -701,13 +707,8 @@ public class DashChunkSource implements ChunkSource, Output {
DataSource dataSource, MediaFormat mediaFormat, int segmentNum, int trigger) { DataSource dataSource, MediaFormat mediaFormat, int segmentNum, int trigger) {
Representation representation = representationHolder.representation; Representation representation = representationHolder.representation;
Format format = representation.format; Format format = representation.format;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum); long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
boolean isLastSegment = !currentManifest.dynamic
&& periodHolders.valueAt(periodHolders.size() - 1) == periodHolder
&& representationHolder.isLastSegment(segmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
representation.getCacheKey()); representation.getCacheKey());
...@@ -715,15 +716,15 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -715,15 +716,15 @@ public class DashChunkSource implements ChunkSource, Output {
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs; long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
if (mimeTypeIsRawText(format.mimeType)) { if (mimeTypeIsRawText(format.mimeType)) {
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format, return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format,
startTimeUs, endTimeUs, segmentNum, isLastSegment, startTimeUs, endTimeUs, segmentNum,
MediaFormat.createTextFormat(format.mimeType, MediaFormat.NO_VALUE, format.language), MediaFormat.createTextFormat(format.mimeType, MediaFormat.NO_VALUE, format.language),
null, periodHolder.localIndex); null, periodHolder.localIndex);
} else { } else {
boolean isMediaFormatFinal = (mediaFormat != null); boolean isMediaFormatFinal = (mediaFormat != null);
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
segmentNum, isLastSegment, sampleOffsetUs, representationHolder.extractorWrapper, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat,
mediaFormat, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight, periodHolder.drmInitData,
periodHolder.drmInitData, isMediaFormatFinal, periodHolder.localIndex); isMediaFormatFinal, periodHolder.localIndex);
} }
} }
......
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.DataChunk; import com.google.android.exoplayer.chunk.DataChunk;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
...@@ -237,16 +238,18 @@ public class HlsChunkSource { ...@@ -237,16 +238,18 @@ public class HlsChunkSource {
} }
/** /**
* Returns the next {@link Chunk} that should be loaded. * Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link HlsSampleSource}.
* *
* @param previousTsChunk The previously loaded chunk that the next chunk should follow. * @param previousTsChunk The previously loaded chunk that the next chunk should follow.
* @param seekPositionUs If there is no previous chunk, this parameter must specify the seek * @param seekPositionUs If there is no previous chunk, this parameter must specify the seek
* position. If there is a previous chunk then this parameter is ignored. * position. If there is a previous chunk then this parameter is ignored.
* @param playbackPositionUs The current playback position. * @param playbackPositionUs The current playback position.
* @return The next chunk to load. * @param out The holder to populate with the result. {@link ChunkOperationHolder#queueSize} is
* unused.
*/ */
public Chunk getChunkOperation(TsChunk previousTsChunk, long seekPositionUs, public void getChunkOperation(TsChunk previousTsChunk, long seekPositionUs,
long playbackPositionUs) { long playbackPositionUs, ChunkOperationHolder out) {
int nextVariantIndex; int nextVariantIndex;
boolean switchingVariantSpliced; boolean switchingVariantSpliced;
if (adaptiveMode == ADAPTIVE_MODE_NONE) { if (adaptiveMode == ADAPTIVE_MODE_NONE) {
...@@ -262,7 +265,8 @@ public class HlsChunkSource { ...@@ -262,7 +265,8 @@ public class HlsChunkSource {
HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex]; HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
if (mediaPlaylist == null) { if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now. // We don't have the media playlist for the next variant. Request it now.
return newMediaPlaylistChunk(nextVariantIndex); out.chunk = newMediaPlaylistChunk(nextVariantIndex);
return;
} }
selectedVariantIndex = nextVariantIndex; selectedVariantIndex = nextVariantIndex;
...@@ -299,11 +303,12 @@ public class HlsChunkSource { ...@@ -299,11 +303,12 @@ public class HlsChunkSource {
int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence; int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
if (chunkIndex >= mediaPlaylist.segments.size()) { if (chunkIndex >= mediaPlaylist.segments.size()) {
if (mediaPlaylist.live && shouldRerequestMediaPlaylist(nextVariantIndex)) { if (!mediaPlaylist.live) {
return newMediaPlaylistChunk(nextVariantIndex); out.endOfStream = true;
} else { } else if (shouldRerequestLiveMediaPlaylist(nextVariantIndex)) {
return null; out.chunk = newMediaPlaylistChunk(nextVariantIndex);
} }
return;
} }
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
...@@ -314,8 +319,8 @@ public class HlsChunkSource { ...@@ -314,8 +319,8 @@ public class HlsChunkSource {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) { if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed. // Encryption is specified and the key has changed.
Chunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex); out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex);
return toReturn; return;
} }
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) { if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
setEncryptionData(keyUri, segment.encryptionIV, encryptionKey); setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
...@@ -342,7 +347,6 @@ public class HlsChunkSource { ...@@ -342,7 +347,6 @@ public class HlsChunkSource {
startTimeUs = segment.startTimeUs; startTimeUs = segment.startTimeUs;
} }
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1;
int trigger = Chunk.TRIGGER_UNSPECIFIED; int trigger = Chunk.TRIGGER_UNSPECIFIED;
Format format = variants[selectedVariantIndex].format; Format format = variants[selectedVariantIndex].format;
...@@ -358,9 +362,8 @@ public class HlsChunkSource { ...@@ -358,9 +362,8 @@ public class HlsChunkSource {
extractorWrapper = previousTsChunk.extractorWrapper; extractorWrapper = previousTsChunk.extractorWrapper;
} }
return new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
chunkMediaSequence, isLastChunk, extractorWrapper, encryptionKey, chunkMediaSequence, extractorWrapper, encryptionKey, encryptionIv);
encryptionIv);
} }
/** /**
...@@ -492,7 +495,7 @@ public class HlsChunkSource { ...@@ -492,7 +495,7 @@ public class HlsChunkSource {
return lowestQualityEnabledVariantIndex; return lowestQualityEnabledVariantIndex;
} }
private boolean shouldRerequestMediaPlaylist(int nextVariantIndex) { private boolean shouldRerequestLiveMediaPlaylist(int nextVariantIndex) {
// Don't re-request media playlist more often than one-half of the target duration. // Don't re-request media playlist more often than one-half of the target duration.
HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex]; HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
long timeSinceLastMediaPlaylistLoadMs = long timeSinceLastMediaPlaylistLoadMs =
......
...@@ -25,6 +25,7 @@ import com.google.android.exoplayer.SampleSource.SampleSourceReader; ...@@ -25,6 +25,7 @@ import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
...@@ -58,6 +59,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -58,6 +59,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
private final LinkedList<HlsExtractorWrapper> extractors; private final LinkedList<HlsExtractorWrapper> extractors;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final int bufferSizeContribution; private final int bufferSizeContribution;
private final ChunkOperationHolder chunkOperationHolder;
private final int eventSourceId; private final int eventSourceId;
private final LoadControl loadControl; private final LoadControl loadControl;
...@@ -114,6 +116,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -114,6 +116,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
this.eventSourceId = eventSourceId; this.eventSourceId = eventSourceId;
this.pendingResetPositionUs = NO_RESET_PENDING; this.pendingResetPositionUs = NO_RESET_PENDING;
extractors = new LinkedList<>(); extractors = new LinkedList<>();
chunkOperationHolder = new ChunkOperationHolder();
} }
@Override @Override
...@@ -383,7 +386,6 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -383,7 +386,6 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
chunkSource.onChunkLoadCompleted(currentLoadable); chunkSource.onChunkLoadCompleted(currentLoadable);
if (isTsChunk(currentLoadable)) { if (isTsChunk(currentLoadable)) {
Assertions.checkState(currentLoadable == currentTsLoadable); Assertions.checkState(currentLoadable == currentTsLoadable);
loadingFinished = currentTsLoadable.isLastChunk;
previousTsLoadable = currentTsLoadable; previousTsLoadable = currentTsLoadable;
notifyLoadCompleted(currentLoadable.bytesLoaded(), currentTsLoadable.type, notifyLoadCompleted(currentLoadable.bytesLoaded(), currentTsLoadable.type,
currentTsLoadable.trigger, currentTsLoadable.format, currentTsLoadable.startTimeUs, currentTsLoadable.trigger, currentTsLoadable.format, currentTsLoadable.startTimeUs,
...@@ -521,8 +523,16 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -521,8 +523,16 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
return; return;
} }
Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs, chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs,
downstreamPositionUs); downstreamPositionUs, chunkOperationHolder);
boolean endOfStream = chunkOperationHolder.endOfStream;
Chunk nextLoadable = chunkOperationHolder.chunk;
chunkOperationHolder.clear();
if (endOfStream) {
loadingFinished = true;
return;
}
if (nextLoadable == null) { if (nextLoadable == null) {
return; return;
} }
...@@ -557,9 +567,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, ...@@ -557,9 +567,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
if (isPendingReset()) { if (isPendingReset()) {
return pendingResetPositionUs; return pendingResetPositionUs;
} else { } else {
return currentTsLoadable != null return loadingFinished ? -1
? (currentTsLoadable.isLastChunk ? -1 : currentTsLoadable.endTimeUs) : currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
: (previousTsLoadable.isLastChunk ? -1 : previousTsLoadable.endTimeUs);
} }
} }
......
...@@ -49,16 +49,15 @@ public final class TsChunk extends MediaChunk { ...@@ -49,16 +49,15 @@ public final class TsChunk extends MediaChunk {
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param isLastChunk True if this is the last chunk in the media. False otherwise.
* @param extractorWrapper A wrapped extractor to parse samples from the data. * @param extractorWrapper A wrapped extractor to parse samples from the data.
* @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector. * @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/ */
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk, long startTimeUs, long endTimeUs, int chunkIndex, HlsExtractorWrapper extractorWrapper,
HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) { byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format, super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format,
startTimeUs, endTimeUs, chunkIndex, isLastChunk); startTimeUs, endTimeUs, chunkIndex);
this.extractorWrapper = extractorWrapper; this.extractorWrapper = extractorWrapper;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;
......
...@@ -270,6 +270,8 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -270,6 +270,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
if (streamElement.chunkCount == 0) { if (streamElement.chunkCount == 0) {
if (currentManifest.isLive) { if (currentManifest.isLive) {
needManifestRefresh = true; needManifestRefresh = true;
} else {
out.endOfStream = true;
} }
return; return;
} }
...@@ -282,7 +284,7 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -282,7 +284,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
chunkIndex = streamElement.getChunkIndex(seekPositionUs); chunkIndex = streamElement.getChunkIndex(seekPositionUs);
} else { } else {
MediaChunk previous = queue.get(out.queueSize - 1); MediaChunk previous = queue.get(out.queueSize - 1);
chunkIndex = previous.isLastChunk ? -1 : previous.chunkIndex + 1 - currentManifestChunkOffset; chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset;
} }
if (live && chunkIndex < 0) { if (live && chunkIndex < 0) {
...@@ -299,10 +301,8 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -299,10 +301,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
// but continue to return the final chunk. // but continue to return the final chunk.
needManifestRefresh = true; needManifestRefresh = true;
} }
} } else if (chunkIndex >= streamElement.chunkCount) {
out.endOfStream = true;
if (chunkIndex == -1) {
// We've reached the end of the stream.
return; return;
} }
...@@ -317,9 +317,8 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -317,9 +317,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex); Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex, extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex,
isLastChunk, chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, mediaFormats.get(manifestTrackKey),
mediaFormats.get(manifestTrackKey), enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight);
enabledTrack.adaptiveMaxHeight);
out.chunk = mediaChunk; out.chunk = mediaChunk;
} }
...@@ -474,14 +473,14 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -474,14 +473,14 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource, ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource,
int chunkIndex, boolean isLast, long chunkStartTimeUs, long chunkEndTimeUs, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
int trigger, MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) { MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) {
long offset = 0; long offset = 0;
DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey); DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs. // To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs.
return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs, return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs,
chunkEndTimeUs, chunkIndex, isLast, chunkStartTimeUs, extractorWrapper, mediaFormat, chunkEndTimeUs, chunkIndex, chunkStartTimeUs, extractorWrapper, mediaFormat,
adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID); adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID);
} }
......
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