diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java index 462a9f30ba..b86a2f8fe2 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkOperationHolder.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkOperationHolder.java index c59bce9733..1e2087178b 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkOperationHolder.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkOperationHolder.java @@ -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: + *
- * 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. - *
* 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);
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
index 26efddebd5..162bcba1cb 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java
@@ -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;
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
index 865dbef85d..02167d3838 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java
@@ -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;
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleChunkSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleChunkSource.java
index c600504350..c8662cc12c 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleChunkSource.java
@@ -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);
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
index e0f8a57ed2..d8d35acdc7 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java
@@ -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;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
index 4cb8faef31..f45edfe568 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
@@ -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);
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
index c12a1044de..6d78470caa 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
@@ -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 =
diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
index a3cd88fd63..c8a0be37b1 100644
--- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
@@ -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