Fix end-of-stream for live streams.

Issue: #764
This commit is contained in:
Oliver Woodman 2015-09-10 18:21:21 +01:00
parent 89fcafec5b
commit e6ca2df514
13 changed files with 121 additions and 101 deletions

View file

@ -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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @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
* 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
@ -54,10 +53,8 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param parentId Identifier for a parent from which this chunk originates.
*/
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk,
boolean isMediaFormatFinal, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk,
parentId);
long startTimeUs, long endTimeUs, int chunkIndex, boolean isMediaFormatFinal, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
this.isMediaFormatFinal = isMediaFormatFinal;
}

View file

@ -16,8 +16,13 @@
package com.google.android.exoplayer.chunk;
/**
* Holds a chunk operation, which consists of a {@link Chunk} to load together with the number of
* {@link MediaChunk}s that should be retained on the queue.
* Holds a chunk operation, which consists of a either:
* <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 {
@ -31,4 +36,18 @@ public final class ChunkOperationHolder {
*/
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;
}
}

View file

@ -325,10 +325,9 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
Chunk currentLoadable = currentLoadableHolder.chunk;
chunkSource.onChunkLoadCompleted(currentLoadable);
if (isMediaChunk(currentLoadable)) {
MediaChunk mediaChunk = (MediaChunk) currentLoadable;
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
notifyLoadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, loadDurationMs);
loadingFinished = ((BaseMediaChunk) currentLoadable).isLastChunk;
} else {
notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
@ -408,9 +407,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
|| (now - lastPerformedBufferOperation > 2000))) {
// Perform the evaluation.
lastPerformedBufferOperation = now;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
doChunkOperation();
boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
// Update the next load position as appropriate.
if (currentLoadableHolder.chunk == null) {
@ -447,8 +444,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
if (isPendingReset()) {
return pendingResetPositionUs;
} else {
BaseMediaChunk lastMediaChunk = mediaChunks.getLast();
return lastMediaChunk.isLastChunk ? -1 : lastMediaChunk.endTimeUs;
return loadingFinished ? -1 : mediaChunks.getLast().endTimeUs;
}
}
@ -464,9 +460,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
Chunk backedOffChunk = currentLoadableHolder.chunk;
if (!isMediaChunk(backedOffChunk)) {
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs,
downstreamPositionUs, currentLoadableHolder);
doChunkOperation();
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading.
@ -491,9 +485,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
// and add it back again afterwards.
BaseMediaChunk removedChunk = mediaChunks.removeLast();
Assertions.checkState(backedOffChunk == removedChunk);
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
chunkSource.getChunkOperation(readOnlyMediaChunks, pendingResetPositionUs, downstreamPositionUs,
currentLoadableHolder);
doChunkOperation();
mediaChunks.add(removedChunk);
if (currentLoadableHolder.chunk == backedOffChunk) {
@ -533,6 +525,19 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
loader.startLoading(currentLoadable, this);
}
/**
* 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.
*

View file

@ -89,24 +89,19 @@ public interface ChunkSource {
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link ChunkSampleSource}.
* <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.
*
* @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
* the queue is non-empty then this parameter is ignored.
* @param playbackPositionUs The current playback position.
* @param out A holder for the next operation, whose {@link ChunkOperationHolder#queueSize} is
* initially equal to the length of the queue, and whose {@link ChunkOperationHolder#chunk} is
* initially equal to null or a {@link Chunk} previously supplied by the {@link ChunkSource}
* that the caller has not yet finished loading. In the latter case the chunk can either be
* replaced or left unchanged. Note that leaving the chunk unchanged is both preferred and
* more efficient than replacing it with a new but identical chunk.
* @param out A holder for the next operation, whose {@link ChunkOperationHolder#endOfStream} is
* initially set to false, whose {@link ChunkOperationHolder#queueSize} is initially equal to
* the length of the queue, and whose {@link ChunkOperationHolder#chunk} is initially equal to
* null or a {@link Chunk} previously supplied by the {@link ChunkSource} that the caller has
* not yet finished loading. In the latter case the chunk can either be replaced or left
* 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,
long playbackPositionUs, ChunkOperationHolder out);

View file

@ -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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @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 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
@ -71,10 +70,10 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
* @param parentId Identifier for a parent from which this chunk originates.
*/
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,
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);
this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs;

View file

@ -36,14 +36,10 @@ public abstract class MediaChunk extends Chunk {
* The chunk index.
*/
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,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) {
this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk,
long startTimeUs, long endTimeUs, int chunkIndex) {
this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
Chunk.NO_PARENT_ID);
}
@ -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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @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.
*/
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);
Assertions.checkNotNull(format);
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.chunkIndex = chunkIndex;
this.isLastChunk = isLastChunk;
}
}

View file

@ -84,6 +84,7 @@ public final class SingleSampleChunkSource implements ChunkSource {
long playbackPositionUs, ChunkOperationHolder out) {
if (!queue.isEmpty()) {
// We've already provided the single sample.
out.endOfStream = true;
return;
}
out.chunk = initChunk();
@ -111,7 +112,7 @@ public final class SingleSampleChunkSource implements ChunkSource {
private SingleSampleMediaChunk initChunk() {
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);
}
}

View file

@ -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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @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 sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
* protected.
* @param parentId Identifier for a parent from which this chunk originates.
*/
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk,
MediaFormat sampleFormat, DrmInitData sampleDrmInitData, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk,
true, parentId);
Format format, long startTimeUs, long endTimeUs, int chunkIndex, MediaFormat sampleFormat,
DrmInitData sampleDrmInitData, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
parentId);
this.sampleFormat = sampleFormat;
this.sampleDrmInitData = sampleDrmInitData;
}

View file

@ -400,11 +400,6 @@ public class DashChunkSource implements ChunkSource, Output {
}
MediaChunk previous = queue.get(out.queueSize - 1);
if (previous.isLastChunk) {
// We've reached the end of the stream.
return;
}
long nextSegmentStartTimeUs = previous.endTimeUs;
if (live && nextSegmentStartTimeUs < availableRangeValues[0]) {
// This is before the first chunk in the current manifest.
@ -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
// while before attempting to load the chunk.
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;
@ -701,13 +707,8 @@ public class DashChunkSource implements ChunkSource, Output {
DataSource dataSource, MediaFormat mediaFormat, int segmentNum, int trigger) {
Representation representation = representationHolder.representation;
Format format = representation.format;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
boolean isLastSegment = !currentManifest.dynamic
&& periodHolders.valueAt(periodHolders.size() - 1) == periodHolder
&& representationHolder.isLastSegment(segmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
representation.getCacheKey());
@ -715,15 +716,15 @@ public class DashChunkSource implements ChunkSource, Output {
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
if (mimeTypeIsRawText(format.mimeType)) {
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),
null, periodHolder.localIndex);
} else {
boolean isMediaFormatFinal = (mediaFormat != null);
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
segmentNum, isLastSegment, sampleOffsetUs, representationHolder.extractorWrapper,
mediaFormat, enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight,
periodHolder.drmInitData, isMediaFormatFinal, periodHolder.localIndex);
segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat,
enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight, periodHolder.drmInitData,
isMediaFormatFinal, periodHolder.localIndex);
}
}

View file

@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
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.Format;
import com.google.android.exoplayer.extractor.Extractor;
@ -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 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.
* @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,
long playbackPositionUs) {
public void getChunkOperation(TsChunk previousTsChunk, long seekPositionUs,
long playbackPositionUs, ChunkOperationHolder out) {
int nextVariantIndex;
boolean switchingVariantSpliced;
if (adaptiveMode == ADAPTIVE_MODE_NONE) {
@ -262,7 +265,8 @@ public class HlsChunkSource {
HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now.
return newMediaPlaylistChunk(nextVariantIndex);
out.chunk = newMediaPlaylistChunk(nextVariantIndex);
return;
}
selectedVariantIndex = nextVariantIndex;
@ -299,11 +303,12 @@ public class HlsChunkSource {
int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
if (chunkIndex >= mediaPlaylist.segments.size()) {
if (mediaPlaylist.live && shouldRerequestMediaPlaylist(nextVariantIndex)) {
return newMediaPlaylistChunk(nextVariantIndex);
} else {
return null;
if (!mediaPlaylist.live) {
out.endOfStream = true;
} else if (shouldRerequestLiveMediaPlaylist(nextVariantIndex)) {
out.chunk = newMediaPlaylistChunk(nextVariantIndex);
}
return;
}
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
@ -314,8 +319,8 @@ public class HlsChunkSource {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed.
Chunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex);
return toReturn;
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex);
return;
}
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
@ -342,7 +347,6 @@ public class HlsChunkSource {
startTimeUs = segment.startTimeUs;
}
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1;
int trigger = Chunk.TRIGGER_UNSPECIFIED;
Format format = variants[selectedVariantIndex].format;
@ -358,9 +362,8 @@ public class HlsChunkSource {
extractorWrapper = previousTsChunk.extractorWrapper;
}
return new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
chunkMediaSequence, isLastChunk, extractorWrapper, encryptionKey,
encryptionIv);
out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
chunkMediaSequence, extractorWrapper, encryptionKey, encryptionIv);
}
/**
@ -492,7 +495,7 @@ public class HlsChunkSource {
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.
HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
long timeSinceLastMediaPlaylistLoadMs =

View file

@ -25,6 +25,7 @@ import com.google.android.exoplayer.SampleSource.SampleSourceReader;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
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.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
@ -58,6 +59,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
private final LinkedList<HlsExtractorWrapper> extractors;
private final int minLoadableRetryCount;
private final int bufferSizeContribution;
private final ChunkOperationHolder chunkOperationHolder;
private final int eventSourceId;
private final LoadControl loadControl;
@ -114,6 +116,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
this.eventSourceId = eventSourceId;
this.pendingResetPositionUs = NO_RESET_PENDING;
extractors = new LinkedList<>();
chunkOperationHolder = new ChunkOperationHolder();
}
@Override
@ -383,7 +386,6 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
chunkSource.onChunkLoadCompleted(currentLoadable);
if (isTsChunk(currentLoadable)) {
Assertions.checkState(currentLoadable == currentTsLoadable);
loadingFinished = currentTsLoadable.isLastChunk;
previousTsLoadable = currentTsLoadable;
notifyLoadCompleted(currentLoadable.bytesLoaded(), currentTsLoadable.type,
currentTsLoadable.trigger, currentTsLoadable.format, currentTsLoadable.startTimeUs,
@ -521,8 +523,16 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
return;
}
Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs,
downstreamPositionUs);
chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs,
downstreamPositionUs, chunkOperationHolder);
boolean endOfStream = chunkOperationHolder.endOfStream;
Chunk nextLoadable = chunkOperationHolder.chunk;
chunkOperationHolder.clear();
if (endOfStream) {
loadingFinished = true;
return;
}
if (nextLoadable == null) {
return;
}
@ -557,9 +567,8 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
if (isPendingReset()) {
return pendingResetPositionUs;
} else {
return currentTsLoadable != null
? (currentTsLoadable.isLastChunk ? -1 : currentTsLoadable.endTimeUs)
: (previousTsLoadable.isLastChunk ? -1 : previousTsLoadable.endTimeUs);
return loadingFinished ? -1
: currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
}
}

View file

@ -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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @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 encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk,
HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) {
long startTimeUs, long endTimeUs, int chunkIndex, HlsExtractorWrapper extractorWrapper,
byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format,
startTimeUs, endTimeUs, chunkIndex, isLastChunk);
startTimeUs, endTimeUs, chunkIndex);
this.extractorWrapper = extractorWrapper;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;

View file

@ -270,6 +270,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
if (streamElement.chunkCount == 0) {
if (currentManifest.isLive) {
needManifestRefresh = true;
} else {
out.endOfStream = true;
}
return;
}
@ -282,7 +284,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
chunkIndex = streamElement.getChunkIndex(seekPositionUs);
} else {
MediaChunk previous = queue.get(out.queueSize - 1);
chunkIndex = previous.isLastChunk ? -1 : previous.chunkIndex + 1 - currentManifestChunkOffset;
chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset;
}
if (live && chunkIndex < 0) {
@ -299,10 +301,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
// but continue to return the final chunk.
needManifestRefresh = true;
}
}
if (chunkIndex == -1) {
// We've reached the end of the stream.
} else if (chunkIndex >= streamElement.chunkCount) {
out.endOfStream = true;
return;
}
@ -317,9 +317,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex,
isLastChunk, chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger,
mediaFormats.get(manifestTrackKey), enabledTrack.adaptiveMaxWidth,
enabledTrack.adaptiveMaxHeight);
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, mediaFormats.get(manifestTrackKey),
enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight);
out.chunk = mediaChunk;
}
@ -474,14 +473,14 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource,
int chunkIndex, boolean isLast, long chunkStartTimeUs, long chunkEndTimeUs,
int trigger, MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) {
int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) {
long offset = 0;
DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey);
// 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.
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);
}