mirror of
https://github.com/samsonjs/media.git
synced 2026-04-02 10:45:51 +00:00
parent
89fcafec5b
commit
e6ca2df514
13 changed files with 121 additions and 101 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue