Commit 4a29be49 by Oliver Woodman

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.
parent 143a4dee
...@@ -119,6 +119,7 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -119,6 +119,7 @@ public class DashChunkSource implements ChunkSource, Output {
private final long liveEdgeLatencyUs; private final long liveEdgeLatencyUs;
private final long elapsedRealtimeOffsetUs; private final long elapsedRealtimeOffsetUs;
private final long[] availableRangeValues; private final long[] availableRangeValues;
private final boolean live;
private MediaPresentationDescription currentManifest; private MediaPresentationDescription currentManifest;
private ExposedTrack enabledTrack; private ExposedTrack enabledTrack;
...@@ -260,7 +261,8 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -260,7 +261,8 @@ public class DashChunkSource implements ChunkSource, Output {
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
this.availableRangeValues = new long[2]; this.availableRangeValues = new long[2];
periodHolders = new SparseArray<>(); periodHolders = new SparseArray<>();
tracks = new ArrayList<ExposedTrack>(); tracks = new ArrayList<>();
live = initialManifest.dynamic;
} }
// ChunkSource implementation. // ChunkSource implementation.
...@@ -375,7 +377,7 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -375,7 +377,7 @@ public class DashChunkSource implements ChunkSource, Output {
availableRange.getCurrentBoundsUs(availableRangeValues); availableRange.getCurrentBoundsUs(availableRangeValues);
if (queue.isEmpty()) { if (queue.isEmpty()) {
if (currentManifest.dynamic) { if (live) {
if (startAtLiveEdge) { if (startAtLiveEdge) {
// We want live streams to start at the live edge instead of the beginning of the // We want live streams to start at the live edge instead of the beginning of the
// manifest // manifest
...@@ -403,19 +405,17 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -403,19 +405,17 @@ public class DashChunkSource implements ChunkSource, Output {
return; return;
} }
if (currentManifest.dynamic) {
long nextSegmentStartTimeUs = previous.endTimeUs; long nextSegmentStartTimeUs = previous.endTimeUs;
if (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.
fatalError = new BehindLiveWindowException(); fatalError = new BehindLiveWindowException();
return; return;
} else if (nextSegmentStartTimeUs >= availableRangeValues[1]) { } else if (currentManifest.dynamic && nextSegmentStartTimeUs >= availableRangeValues[1]) {
// This chunk is beyond the last chunk in the current manifest. If the index is bounded // 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 // 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;
} }
}
startingNewPeriod = false; startingNewPeriod = false;
periodHolder = periodHolders.get(previous.parentId); periodHolder = periodHolders.get(previous.parentId);
...@@ -545,7 +545,7 @@ public class DashChunkSource implements ChunkSource, Output { ...@@ -545,7 +545,7 @@ public class DashChunkSource implements ChunkSource, Output {
representationFormats[i] = format; representationFormats[i] = format;
} }
Arrays.sort(representationFormats, new DecreasingBandwidthComparator()); 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); String mediaMimeType = getMediaMimeType(maxHeightRepresentationFormat);
if (mediaMimeType == null) { if (mediaMimeType == null) {
Log.w(TAG, "Skipped adaptive track (unknown media mime type)"); Log.w(TAG, "Skipped adaptive track (unknown media mime type)");
......
...@@ -221,7 +221,7 @@ public class HlsChunkSource { ...@@ -221,7 +221,7 @@ public class HlsChunkSource {
} }
public long getDurationUs() { public long getDurationUs() {
return live ? C.UNKNOWN_TIME_US : durationUs; return durationUs;
} }
/** /**
...@@ -551,7 +551,7 @@ public class HlsChunkSource { ...@@ -551,7 +551,7 @@ public class HlsChunkSource {
variantLastPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime(); variantLastPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime();
variantPlaylists[variantIndex] = mediaPlaylist; variantPlaylists[variantIndex] = mediaPlaylist;
live |= mediaPlaylist.live; live |= mediaPlaylist.live;
durationUs = mediaPlaylist.durationUs; durationUs = live ? C.UNKNOWN_TIME_US : mediaPlaylist.durationUs;
} }
/** /**
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.smoothstreaming; package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.BehindLiveWindowException; import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
...@@ -69,6 +70,7 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -69,6 +70,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher; private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
private final DrmInitData.Mapped drmInitData; private final DrmInitData.Mapped drmInitData;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
private final boolean live;
// The tracks exposed by this source. // The tracks exposed by this source.
private final ArrayList<ExposedTrack> tracks; private final ArrayList<ExposedTrack> tracks;
...@@ -80,7 +82,7 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -80,7 +82,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private boolean prepareCalled; private boolean prepareCalled;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest currentManifest;
private int currentManifestChunkOffset; private int currentManifestChunkOffset;
private boolean currentManifestFinished; private boolean needManifestRefresh;
private ExposedTrack enabledTrack; private ExposedTrack enabledTrack;
private IOException fatalError; private IOException fatalError;
...@@ -135,6 +137,7 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -135,6 +137,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
tracks = new ArrayList<>(); tracks = new ArrayList<>();
extractorWrappers = new SparseArray<>(); extractorWrappers = new SparseArray<>();
mediaFormats = new SparseArray<>(); mediaFormats = new SparseArray<>();
live = initialManifest.isLive;
ProtectionElement protectionElement = initialManifest.protectionElement; ProtectionElement protectionElement = initialManifest.protectionElement;
if (protectionElement != null) { if (protectionElement != null) {
...@@ -221,10 +224,10 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -221,10 +224,10 @@ public class SmoothStreamingChunkSource implements ChunkSource,
} }
} }
currentManifest = newManifest; currentManifest = newManifest;
currentManifestFinished = false; needManifestRefresh = false;
} }
if (currentManifestFinished && (SystemClock.elapsedRealtime() if (needManifestRefresh && (SystemClock.elapsedRealtime()
> manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS)) { > manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS)) {
manifestFetcher.requestRefresh(); manifestFetcher.requestRefresh();
} }
...@@ -265,14 +268,15 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -265,14 +268,15 @@ public class SmoothStreamingChunkSource implements ChunkSource,
StreamElement streamElement = currentManifest.streamElements[enabledTrack.elementIndex]; StreamElement streamElement = currentManifest.streamElements[enabledTrack.elementIndex];
if (streamElement.chunkCount == 0) { if (streamElement.chunkCount == 0) {
// The manifest is currently empty for this stream. if (currentManifest.isLive) {
currentManifestFinished = true; needManifestRefresh = true;
}
return; return;
} }
int chunkIndex; int chunkIndex;
if (queue.isEmpty()) { if (queue.isEmpty()) {
if (currentManifest.isLive) { if (live) {
seekPositionUs = getLiveSeekPosition(currentManifest, liveEdgeLatencyUs); seekPositionUs = getLiveSeekPosition(currentManifest, liveEdgeLatencyUs);
} }
chunkIndex = streamElement.getChunkIndex(seekPositionUs); chunkIndex = streamElement.getChunkIndex(seekPositionUs);
...@@ -281,19 +285,19 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -281,19 +285,19 @@ public class SmoothStreamingChunkSource implements ChunkSource,
chunkIndex = previous.isLastChunk ? -1 : previous.chunkIndex + 1 - currentManifestChunkOffset; chunkIndex = previous.isLastChunk ? -1 : previous.chunkIndex + 1 - currentManifestChunkOffset;
} }
if (currentManifest.isLive) { if (live && chunkIndex < 0) {
if (chunkIndex < 0) {
// This is before the first chunk in the current manifest. // This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException(); fatalError = new BehindLiveWindowException();
return; return;
} else if (chunkIndex >= streamElement.chunkCount) { } else if (currentManifest.isLive) {
if (chunkIndex >= streamElement.chunkCount) {
// This is beyond the last chunk in the current manifest. // This is beyond the last chunk in the current manifest.
currentManifestFinished = true; needManifestRefresh = true;
return; return;
} else if (chunkIndex == streamElement.chunkCount - 1) { } else if (chunkIndex == streamElement.chunkCount - 1) {
// This is the last chunk in the current manifest. Mark the manifest as being finished, // This is the last chunk in the current manifest. Mark the manifest as being finished,
// but continue to return the final chunk. // but continue to return the final chunk.
currentManifestFinished = true; needManifestRefresh = true;
} }
} }
...@@ -388,7 +392,7 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -388,7 +392,7 @@ public class SmoothStreamingChunkSource implements ChunkSource,
} }
// Build the media format. // Build the media format.
long durationUs = manifest.durationUs; long durationUs = live ? C.UNKNOWN_TIME_US : manifest.durationUs;
StreamElement element = manifest.streamElements[elementIndex]; StreamElement element = manifest.streamElements[elementIndex];
Format format = element.tracks[trackIndex].format; Format format = element.tracks[trackIndex].format;
byte[][] csdArray = element.tracks[trackIndex].csd; byte[][] csdArray = element.tracks[trackIndex].csd;
......
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