Fix available end time calculation for multi-period DASH live streams

The available end time was accidentally substracted by the start time
of the last period.

To avoid similar time reference confusion in the future, also renaming
many variables and methods to clearly reflect the time reference point.
And to avoid constant conversion, the processManifest method also
attempts to converge to time relative to the start of the window as
quickly as possible.

Issue: #8537
PiperOrigin-RevId: 357001624
This commit is contained in:
tonihei 2021-02-11 18:10:39 +00:00 committed by Oliver Woodman
parent 6b5b3d8141
commit d700990198
3 changed files with 62 additions and 81 deletions

View File

@ -8,6 +8,9 @@
* Fix a bug where setting playback parameters while using video tunneling * Fix a bug where setting playback parameters while using video tunneling
would cause an error to be thrown would cause an error to be thrown
([#8570](https://github.com/google/ExoPlayer/issues/8570)). ([#8570](https://github.com/google/ExoPlayer/issues/8570)).
* DASH:
* Fix playback issue for multi-period DASH live streams
([#8537](https://github.com/google/ExoPlayer/issues/8537)).
* UI: * UI:
* Add builder for `PlayerNotificationManager`. * Add builder for `PlayerNotificationManager`.
* IMA extension: * IMA extension:

View File

@ -901,77 +901,54 @@ public final class DashMediaSource extends BaseMediaSource {
} }
} }
// Update the window. // Update the window.
boolean windowChangingImplicitly = false; Period firstPeriod = manifest.getPeriod(0);
int lastPeriodIndex = manifest.getPeriodCount() - 1; int lastPeriodIndex = manifest.getPeriodCount() - 1;
Period lastPeriod = manifest.getPeriod(lastPeriodIndex); Period lastPeriod = manifest.getPeriod(lastPeriodIndex);
long lastPeriodDurationUs = manifest.getPeriodDurationUs(lastPeriodIndex); long lastPeriodDurationUs = manifest.getPeriodDurationUs(lastPeriodIndex);
long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs));
// Get the period-relative start/end times. long windowStartTimeInManifestUs =
long currentStartTimeUs = getAvailableStartTimeInManifestUs(
getAvailableStartTimeUs( firstPeriod, manifest.getPeriodDurationUs(0), nowUnixTimeUs);
manifest.getPeriod(0), manifest.getPeriodDurationUs(0), nowUnixTimeUs); long windowEndTimeInManifestUs =
long currentEndTimeUs = getAvailableEndTimeUs(lastPeriod, lastPeriodDurationUs, nowUnixTimeUs); getAvailableEndTimeInManifestUs(lastPeriod, lastPeriodDurationUs, nowUnixTimeUs);
if (manifest.dynamic && !isIndexExplicit(lastPeriod)) { boolean windowChangingImplicitly = manifest.dynamic && !isIndexExplicit(lastPeriod);
// The manifest describes an incomplete live stream. Update the start/end times to reflect the if (windowChangingImplicitly && manifest.timeShiftBufferDepthMs != C.TIME_UNSET) {
// live stream duration and the manifest's time shift buffer depth. // Update the available start time to reflect the manifest's time shift buffer depth.
long liveStreamEndPositionInLastPeriodUs = currentEndTimeUs - C.msToUs(lastPeriod.startMs); long timeShiftBufferStartTimeInManifestUs =
currentEndTimeUs = min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); windowEndTimeInManifestUs - C.msToUs(manifest.timeShiftBufferDepthMs);
if (manifest.timeShiftBufferDepthMs != C.TIME_UNSET) { windowStartTimeInManifestUs =
long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepthMs); max(windowStartTimeInManifestUs, timeShiftBufferStartTimeInManifestUs);
long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs;
int periodIndex = lastPeriodIndex;
while (offsetInPeriodUs < 0 && periodIndex > 0) {
offsetInPeriodUs += manifest.getPeriodDurationUs(--periodIndex);
} }
if (periodIndex == 0) { long windowDurationUs = windowEndTimeInManifestUs - windowStartTimeInManifestUs;
currentStartTimeUs = max(currentStartTimeUs, offsetInPeriodUs); long windowStartUnixTimeMs = C.TIME_UNSET;
} else { long windowDefaultPositionUs = 0;
// The time shift buffer starts after the earliest period.
// TODO: Does this ever happen?
currentStartTimeUs = manifest.getPeriodDurationUs(0);
}
}
windowChangingImplicitly = true;
}
long windowDurationUs = currentEndTimeUs - currentStartTimeUs;
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
windowDurationUs += manifest.getPeriodDurationUs(i);
}
long windowStartTimeMs = C.TIME_UNSET;
if (manifest.availabilityStartTimeMs != C.TIME_UNSET) {
windowStartTimeMs =
manifest.availabilityStartTimeMs
+ manifest.getPeriod(0).startMs
+ C.usToMs(currentStartTimeUs);
}
long windowDefaultStartPositionUs = 0;
if (manifest.dynamic) { if (manifest.dynamic) {
updateMediaItemLiveConfiguration( checkState(manifest.availabilityStartTimeMs != C.TIME_UNSET);
/* nowPeriodTimeUs= */ currentStartTimeUs + nowUnixTimeUs - C.msToUs(windowStartTimeMs), long nowInWindowUs =
/* windowStartPeriodTimeUs= */ currentStartTimeUs, nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs) - windowStartTimeInManifestUs;
/* windowEndPeriodTimeUs= */ currentEndTimeUs); updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs);
windowDefaultStartPositionUs = windowStartUnixTimeMs =
nowUnixTimeUs - C.msToUs(windowStartTimeMs + liveConfiguration.targetOffsetMs); manifest.availabilityStartTimeMs + C.usToMs(windowStartTimeInManifestUs);
long minimumDefaultStartPositionUs = windowDefaultPositionUs = nowInWindowUs - C.msToUs(liveConfiguration.targetOffsetMs);
long minimumWindowDefaultPositionUs =
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
if (windowDefaultStartPositionUs < minimumDefaultStartPositionUs) { if (windowDefaultPositionUs < minimumWindowDefaultPositionUs) {
// The default start position is too close to the start of the live window. Set it to the // The default position is too close to the start of the live window. Set it to the minimum
// minimum default start position provided the window is at least twice as big. Else set // default position provided the window is at least twice as big. Else set it to the middle
// it to the middle of the window. // of the window.
windowDefaultStartPositionUs = minimumDefaultStartPositionUs; windowDefaultPositionUs = minimumWindowDefaultPositionUs;
} }
} }
long offsetInFirstPeriodUs = windowStartTimeInManifestUs - C.msToUs(firstPeriod.startMs);
DashTimeline timeline = DashTimeline timeline =
new DashTimeline( new DashTimeline(
manifest.availabilityStartTimeMs, manifest.availabilityStartTimeMs,
windowStartTimeMs, windowStartUnixTimeMs,
elapsedRealtimeOffsetMs, elapsedRealtimeOffsetMs,
firstPeriodId, firstPeriodId,
/* offsetInFirstPeriodUs= */ currentStartTimeUs, offsetInFirstPeriodUs,
windowDurationUs, windowDurationUs,
windowDefaultStartPositionUs, windowDefaultPositionUs,
manifest, manifest,
mediaItem, mediaItem,
manifest.dynamic ? liveConfiguration : null); manifest.dynamic ? liveConfiguration : null);
@ -1008,8 +985,7 @@ public final class DashMediaSource extends BaseMediaSource {
} }
} }
private void updateMediaItemLiveConfiguration( private void updateMediaItemLiveConfiguration(long nowInWindowUs, long windowDurationUs) {
long nowPeriodTimeUs, long windowStartPeriodTimeUs, long windowEndPeriodTimeUs) {
long maxLiveOffsetMs; long maxLiveOffsetMs;
if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) { if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) {
maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs; maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs;
@ -1017,7 +993,7 @@ public final class DashMediaSource extends BaseMediaSource {
&& manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) { && manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) {
maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs; maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs;
} else { } else {
maxLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowStartPeriodTimeUs); maxLiveOffsetMs = C.usToMs(nowInWindowUs);
} }
long minLiveOffsetMs; long minLiveOffsetMs;
if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) { if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) {
@ -1026,7 +1002,7 @@ public final class DashMediaSource extends BaseMediaSource {
&& manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) { && manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) {
minLiveOffsetMs = manifest.serviceDescription.minOffsetMs; minLiveOffsetMs = manifest.serviceDescription.minOffsetMs;
} else { } else {
minLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowEndPeriodTimeUs); minLiveOffsetMs = C.usToMs(nowInWindowUs - windowDurationUs);
if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) { if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) {
// The current time is in the window, so assume all clocks are synchronized and set the // The current time is in the window, so assume all clocks are synchronized and set the
// minimum to a live offset of zero. // minimum to a live offset of zero.
@ -1052,12 +1028,10 @@ public final class DashMediaSource extends BaseMediaSource {
targetOffsetMs = minLiveOffsetMs; targetOffsetMs = minLiveOffsetMs;
} }
if (targetOffsetMs > maxLiveOffsetMs) { if (targetOffsetMs > maxLiveOffsetMs) {
long windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs;
long liveOffsetAtWindowStartUs = nowPeriodTimeUs - windowStartPeriodTimeUs;
long safeDistanceFromWindowStartUs = long safeDistanceFromWindowStartUs =
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
long maxTargetOffsetForSafeDistanceToWindowStartMs = long maxTargetOffsetForSafeDistanceToWindowStartMs =
C.usToMs(liveOffsetAtWindowStartUs - safeDistanceFromWindowStartUs); C.usToMs(nowInWindowUs - safeDistanceFromWindowStartUs);
targetOffsetMs = targetOffsetMs =
Util.constrainValue( Util.constrainValue(
maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs); maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs);
@ -1147,9 +1121,10 @@ public final class DashMediaSource extends BaseMediaSource {
return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING); return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING);
} }
private static long getAvailableStartTimeUs( private static long getAvailableStartTimeInManifestUs(
Period period, long periodDurationUs, long nowUnixTimeUs) { Period period, long periodDurationUs, long nowUnixTimeUs) {
long availableStartTimeUs = 0; long periodStartTimeInManifestUs = C.msToUs(period.startMs);
long availableStartTimeInManifestUs = periodStartTimeInManifestUs;
boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period);
for (int i = 0; i < period.adaptationSets.size(); i++) { for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i); AdaptationSet adaptationSet = period.adaptationSets.get(i);
@ -1162,23 +1137,26 @@ public final class DashMediaSource extends BaseMediaSource {
} }
@Nullable DashSegmentIndex index = representations.get(0).getIndex(); @Nullable DashSegmentIndex index = representations.get(0).getIndex();
if (index == null) { if (index == null) {
return 0; return periodStartTimeInManifestUs;
} }
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) { if (availableSegmentCount == 0) {
return 0; return periodStartTimeInManifestUs;
} }
long firstAvailableSegmentNum = long firstAvailableSegmentNum =
index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs); index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstAvailableSegmentNum); long adaptationSetAvailableStartTimeInManifestUs =
availableStartTimeUs = max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); periodStartTimeInManifestUs + index.getTimeUs(firstAvailableSegmentNum);
availableStartTimeInManifestUs =
max(availableStartTimeInManifestUs, adaptationSetAvailableStartTimeInManifestUs);
} }
return availableStartTimeUs; return availableStartTimeInManifestUs;
} }
private static long getAvailableEndTimeUs( private static long getAvailableEndTimeInManifestUs(
Period period, long periodDurationUs, long nowUnixTimeUs) { Period period, long periodDurationUs, long nowUnixTimeUs) {
long availableEndTimeUs = Long.MAX_VALUE; long periodStartTimeInManifestUs = C.msToUs(period.startMs);
long availableEndTimeInManifestUs = Long.MAX_VALUE;
boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period);
for (int i = 0; i < period.adaptationSets.size(); i++) { for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i); AdaptationSet adaptationSet = period.adaptationSets.get(i);
@ -1191,21 +1169,23 @@ public final class DashMediaSource extends BaseMediaSource {
} }
@Nullable DashSegmentIndex index = representations.get(0).getIndex(); @Nullable DashSegmentIndex index = representations.get(0).getIndex();
if (index == null) { if (index == null) {
return periodDurationUs; return periodStartTimeInManifestUs + periodDurationUs;
} }
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) { if (availableSegmentCount == 0) {
return 0; return periodStartTimeInManifestUs;
} }
long firstAvailableSegmentNum = long firstAvailableSegmentNum =
index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs); index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
long lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1; long lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
long adaptationSetAvailableEndTimeUs = long adaptationSetAvailableEndTimeInManifestUs =
index.getTimeUs(lastAvailableSegmentNum) periodStartTimeInManifestUs
+ index.getTimeUs(lastAvailableSegmentNum)
+ index.getDurationUs(lastAvailableSegmentNum, periodDurationUs); + index.getDurationUs(lastAvailableSegmentNum, periodDurationUs);
availableEndTimeUs = min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); availableEndTimeInManifestUs =
min(availableEndTimeInManifestUs, adaptationSetAvailableEndTimeInManifestUs);
} }
return availableEndTimeUs; return availableEndTimeInManifestUs;
} }
private static boolean isIndexExplicit(Period period) { private static boolean isIndexExplicit(Period period) {

View File

@ -30,9 +30,7 @@ public class Period {
*/ */
@Nullable public final String id; @Nullable public final String id;
/** /** The start time of the period in milliseconds, relative to the start of the manifest. */
* The start time of the period in milliseconds.
*/
public final long startMs; public final long startMs;
/** /**