Correctly use "current-manifest-live" v.s. "stream-is-live"

When a live stream ends, what typically happens is that the manifest
is refreshed and the refreshed version is not marked as live/dynamic.
When this happens we:

1. Don't want the duration of the track to change.
2. Still want to consider the possibility that we may have fallen behind
   the live window.
3. Don't want to allow futher manifest refreshes.

This change uses the right thing in the right place.
This commit is contained in:
Oliver Woodman 2015-09-10 18:15:15 +01:00
parent 143a4deee8
commit 4a29be498b
3 changed files with 36 additions and 32 deletions

View file

@ -119,6 +119,7 @@ public class DashChunkSource implements ChunkSource, Output {
private final long liveEdgeLatencyUs;
private final long elapsedRealtimeOffsetUs;
private final long[] availableRangeValues;
private final boolean live;
private MediaPresentationDescription currentManifest;
private ExposedTrack enabledTrack;
@ -260,7 +261,8 @@ public class DashChunkSource implements ChunkSource, Output {
this.evaluation = new Evaluation();
this.availableRangeValues = new long[2];
periodHolders = new SparseArray<>();
tracks = new ArrayList<ExposedTrack>();
tracks = new ArrayList<>();
live = initialManifest.dynamic;
}
// ChunkSource implementation.
@ -375,7 +377,7 @@ public class DashChunkSource implements ChunkSource, Output {
availableRange.getCurrentBoundsUs(availableRangeValues);
if (queue.isEmpty()) {
if (currentManifest.dynamic) {
if (live) {
if (startAtLiveEdge) {
// We want live streams to start at the live edge instead of the beginning of the
// manifest
@ -403,18 +405,16 @@ public class DashChunkSource implements ChunkSource, Output {
return;
}
if (currentManifest.dynamic) {
long nextSegmentStartTimeUs = previous.endTimeUs;
if (nextSegmentStartTimeUs < availableRangeValues[0]) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
} else if (nextSegmentStartTimeUs >= availableRangeValues[1]) {
// This chunk is beyond the last chunk in the current manifest. If the index is bounded
// 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;
}
long nextSegmentStartTimeUs = previous.endTimeUs;
if (live && nextSegmentStartTimeUs < availableRangeValues[0]) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
} else if (currentManifest.dynamic && nextSegmentStartTimeUs >= availableRangeValues[1]) {
// This chunk is beyond the last chunk in the current manifest. If the index is bounded
// 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;
}
startingNewPeriod = false;
@ -545,7 +545,7 @@ public class DashChunkSource implements ChunkSource, Output {
representationFormats[i] = format;
}
Arrays.sort(representationFormats, new DecreasingBandwidthComparator());
long trackDurationUs = manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000;
long trackDurationUs = live ? C.UNKNOWN_TIME_US : manifest.duration * 1000;
String mediaMimeType = getMediaMimeType(maxHeightRepresentationFormat);
if (mediaMimeType == null) {
Log.w(TAG, "Skipped adaptive track (unknown media mime type)");

View file

@ -221,7 +221,7 @@ public class HlsChunkSource {
}
public long getDurationUs() {
return live ? C.UNKNOWN_TIME_US : durationUs;
return durationUs;
}
/**
@ -551,7 +551,7 @@ public class HlsChunkSource {
variantLastPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime();
variantPlaylists[variantIndex] = mediaPlaylist;
live |= mediaPlaylist.live;
durationUs = mediaPlaylist.durationUs;
durationUs = live ? C.UNKNOWN_TIME_US : mediaPlaylist.durationUs;
}
/**

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
@ -69,6 +70,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
private final DrmInitData.Mapped drmInitData;
private final FormatEvaluator adaptiveFormatEvaluator;
private final boolean live;
// The tracks exposed by this source.
private final ArrayList<ExposedTrack> tracks;
@ -80,7 +82,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private boolean prepareCalled;
private SmoothStreamingManifest currentManifest;
private int currentManifestChunkOffset;
private boolean currentManifestFinished;
private boolean needManifestRefresh;
private ExposedTrack enabledTrack;
private IOException fatalError;
@ -135,6 +137,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
tracks = new ArrayList<>();
extractorWrappers = new SparseArray<>();
mediaFormats = new SparseArray<>();
live = initialManifest.isLive;
ProtectionElement protectionElement = initialManifest.protectionElement;
if (protectionElement != null) {
@ -221,10 +224,10 @@ public class SmoothStreamingChunkSource implements ChunkSource,
}
}
currentManifest = newManifest;
currentManifestFinished = false;
needManifestRefresh = false;
}
if (currentManifestFinished && (SystemClock.elapsedRealtime()
if (needManifestRefresh && (SystemClock.elapsedRealtime()
> manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS)) {
manifestFetcher.requestRefresh();
}
@ -265,14 +268,15 @@ public class SmoothStreamingChunkSource implements ChunkSource,
StreamElement streamElement = currentManifest.streamElements[enabledTrack.elementIndex];
if (streamElement.chunkCount == 0) {
// The manifest is currently empty for this stream.
currentManifestFinished = true;
if (currentManifest.isLive) {
needManifestRefresh = true;
}
return;
}
int chunkIndex;
if (queue.isEmpty()) {
if (currentManifest.isLive) {
if (live) {
seekPositionUs = getLiveSeekPosition(currentManifest, liveEdgeLatencyUs);
}
chunkIndex = streamElement.getChunkIndex(seekPositionUs);
@ -281,19 +285,19 @@ public class SmoothStreamingChunkSource implements ChunkSource,
chunkIndex = previous.isLastChunk ? -1 : previous.chunkIndex + 1 - currentManifestChunkOffset;
}
if (currentManifest.isLive) {
if (chunkIndex < 0) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
} else if (chunkIndex >= streamElement.chunkCount) {
if (live && chunkIndex < 0) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
} else if (currentManifest.isLive) {
if (chunkIndex >= streamElement.chunkCount) {
// This is beyond the last chunk in the current manifest.
currentManifestFinished = true;
needManifestRefresh = true;
return;
} else if (chunkIndex == streamElement.chunkCount - 1) {
// This is the last chunk in the current manifest. Mark the manifest as being finished,
// but continue to return the final chunk.
currentManifestFinished = true;
needManifestRefresh = true;
}
}
@ -388,7 +392,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
}
// Build the media format.
long durationUs = manifest.durationUs;
long durationUs = live ? C.UNKNOWN_TIME_US : manifest.durationUs;
StreamElement element = manifest.streamElements[elementIndex];
Format format = element.tracks[trackIndex].format;
byte[][] csdArray = element.tracks[trackIndex].csd;