Fix start position for non-precise startOffset and user-set liveOffset

Also added test cases covering this.

PiperOrigin-RevId: 374218514
This commit is contained in:
tonihei 2021-05-17 17:52:12 +01:00 committed by Oliver Woodman
parent c80355c903
commit 6fd20ddace
3 changed files with 103 additions and 27 deletions

View File

@ -572,15 +572,12 @@ public final class HlsMediaSource extends BaseMediaSource
if (playlist.startOffsetUs == C.TIME_UNSET || playlist.segments.isEmpty()) {
windowDefaultStartPositionUs = 0;
} else {
// From RFC 8216, section 4.4.2.2: if playlist.startOffsetUs is negative, it indicates the
// beginning of the Playlist, whereas if it is beyond the playlist duration it indicates the
// end of the playlist.
long startOffsetUs = Util.constrainValue(playlist.startOffsetUs, 0, playlist.durationUs);
if (playlist.preciseStart || startOffsetUs == playlist.durationUs) {
windowDefaultStartPositionUs = startOffsetUs;
if (playlist.preciseStart || playlist.startOffsetUs == playlist.durationUs) {
windowDefaultStartPositionUs = playlist.startOffsetUs;
} else {
windowDefaultStartPositionUs =
findClosestPrecedingSegment(playlist.segments, startOffsetUs).relativeStartTimeUs;
findClosestPrecedingSegment(playlist.segments, playlist.startOffsetUs)
.relativeStartTimeUs;
}
}
return new SinglePeriodTimeline(
@ -606,17 +603,16 @@ public final class HlsMediaSource extends BaseMediaSource
private long getLiveWindowDefaultStartPositionUs(
HlsMediaPlaylist playlist, long liveEdgeOffsetUs) {
if (playlist.startOffsetUs != C.TIME_UNSET && playlist.preciseStart) {
// From RFC 8216, section 4.4.2.2: if playlist.startOffsetUs is negative, it indicates the
// beginning of the Playlist, whereas if it is beyond the playlist duration it indicates the
// end of the playlist.
return Util.constrainValue(playlist.startOffsetUs, 0, playlist.durationUs);
long startPositionUs =
playlist.startOffsetUs != C.TIME_UNSET
? playlist.startOffsetUs
: playlist.durationUs + liveEdgeOffsetUs - C.msToUs(liveConfiguration.targetOffsetMs);
if (playlist.preciseStart) {
return startPositionUs;
}
long maxStartPositionUs =
playlist.durationUs + liveEdgeOffsetUs - C.msToUs(liveConfiguration.targetOffsetMs);
@Nullable
HlsMediaPlaylist.Part part =
findClosestPrecedingIndependentPart(playlist.trailingParts, maxStartPositionUs);
findClosestPrecedingIndependentPart(playlist.trailingParts, startPositionUs);
if (part != null) {
return part.relativeStartTimeUs;
}
@ -624,8 +620,8 @@ public final class HlsMediaSource extends BaseMediaSource
return 0;
}
HlsMediaPlaylist.Segment segment =
findClosestPrecedingSegment(playlist.segments, maxStartPositionUs);
part = findClosestPrecedingIndependentPart(segment.parts, maxStartPositionUs);
findClosestPrecedingSegment(playlist.segments, startPositionUs);
part = findClosestPrecedingIndependentPart(segment.parts, startPositionUs);
if (part != null) {
return part.relativeStartTimeUs;
}
@ -660,11 +656,7 @@ public final class HlsMediaSource extends BaseMediaSource
HlsMediaPlaylist.ServerControl serverControl = playlist.serverControl;
long targetOffsetUs;
if (playlist.startOffsetUs != C.TIME_UNSET) {
// From RFC 8216, section 4.4.2.2: if playlist.startOffsetUs is negative, it indicates the
// beginning of the Playlist, whereas if it is beyond the playlist duration it indicates the
// end of the playlist.
long startOffsetUs = Util.constrainValue(playlist.startOffsetUs, 0, playlist.durationUs);
targetOffsetUs = playlist.durationUs - startOffsetUs;
targetOffsetUs = playlist.durationUs - playlist.startOffsetUs;
} else if (serverControl.partHoldBackUs != C.TIME_UNSET
&& playlist.partTargetDurationUs != C.TIME_UNSET) {
// Select part hold back only if the playlist has a part target duration.
@ -694,8 +686,8 @@ public final class HlsMediaSource extends BaseMediaSource
}
/**
* Gets the segment that contains {@code positionUs}, or the last sent if the position is beyond
* the segments list.
* Gets the segment that contains {@code positionUs}, or the last segment if the position is
* beyond the segments list.
*/
private static HlsMediaPlaylist.Segment findClosestPrecedingSegment(
List<HlsMediaPlaylist.Segment> segments, long positionUs) {

View File

@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.source.hls.playlist;
import static java.lang.Math.max;
import static java.lang.Math.min;
import android.net.Uri;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@ -392,8 +395,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
/** The type of the playlist. See {@link PlaylistType}. */
@PlaylistType public final int playlistType;
/**
* The start offset in microseconds, as defined by #EXT-X-START, or {@link C#TIME_UNSET} if
* undefined.
* The start offset in microseconds from the beginning of the playlist, as defined by
* #EXT-X-START, or {@link C#TIME_UNSET} if undefined. The value is guaranteed to be between 0 and
* {@link #durationUs}, inclusive.
*/
public final long startOffsetUs;
/** Whether the start position should be precise, as defined by #EXT-X-START. */
@ -513,10 +517,15 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} else {
durationUs = 0;
}
// From RFC 8216, section 4.4.2.2: If startOffsetUs is negative, it indicates the offset from
// the end of the playlist. If the absolute value exceeds the duration of the playlist, it
// indicates the beginning (if negative) or the end (if positive) of the playlist.
this.startOffsetUs =
startOffsetUs == C.TIME_UNSET
? C.TIME_UNSET
: startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs;
: startOffsetUs >= 0
? min(durationUs, startOffsetUs)
: max(0, durationUs + startOffsetUs);
this.serverControl = serverControl;
}

View File

@ -337,6 +337,44 @@ public class HlsMediaSourceTest {
assertThat(window.defaultPositionUs).isEqualTo(4000000);
}
@Test
public void
loadLivePlaylist_withNonPreciseStartTimeAndUserDefinedLiveOffset_startsFromPrecedingSegment()
throws TimeoutException, ParserException {
String playlistUri = "fake://foo.bar/media0/playlist.m3u8";
// The playlist has a duration of 16 seconds, and part hold back, hold back and start time
// defined.
String playlist =
"#EXTM3U\n"
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n"
+ "#EXT-X-TARGETDURATION:4\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-START:TIME-OFFSET=-10\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence0.ts\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence1.ts\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence2.ts\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence3.ts\n"
+ "#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3\n";
// The playlist finishes 1 second before the current time.
SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00"));
HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist);
MediaItem mediaItem =
new MediaItem.Builder().setUri(playlistUri).setLiveTargetOffsetMs(3000).build();
HlsMediaSource mediaSource = factory.createMediaSource(mediaItem);
Timeline timeline = prepareAndWaitForTimeline(mediaSource);
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(3000);
// The default position points to the segment containing the start time.
assertThat(window.defaultPositionUs).isEqualTo(4000000);
}
@Test
public void loadLivePlaylist_withPreciseStartTime_targetLiveOffsetFromStartTime()
throws TimeoutException, ParserException {
@ -375,6 +413,43 @@ public class HlsMediaSourceTest {
assertThat(window.defaultPositionUs).isEqualTo(6000000);
}
@Test
public void loadLivePlaylist_withPreciseStartTimeAndUserDefinedLiveOffset_startsFromStartTime()
throws TimeoutException, ParserException {
String playlistUri = "fake://foo.bar/media0/playlist.m3u8";
// The playlist has a duration of 16 seconds, and part hold back, hold back and start time
// defined.
String playlist =
"#EXTM3U\n"
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n"
+ "#EXT-X-TARGETDURATION:4\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-START:TIME-OFFSET=-10,PRECISE=YES\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence0.ts\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence1.ts\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence2.ts\n"
+ "#EXTINF:4.00000,\n"
+ "fileSequence3.ts\n"
+ "#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3";
// The playlist finishes 1 second before the current time.
SystemClock.setCurrentTimeMillis(Util.parseXsDateTime("2020-01-01T00:00:17.0+00:00"));
HlsMediaSource.Factory factory = createHlsMediaSourceFactory(playlistUri, playlist);
MediaItem mediaItem =
new MediaItem.Builder().setUri(playlistUri).setLiveTargetOffsetMs(3000).build();
HlsMediaSource mediaSource = factory.createMediaSource(mediaItem);
Timeline timeline = prepareAndWaitForTimeline(mediaSource);
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(3000);
// The default position points to the start time.
assertThat(window.defaultPositionUs).isEqualTo(6000000);
}
@Test
public void loadLivePlaylist_targetLiveOffsetInMediaItem_targetLiveOffsetPickedFromMediaItem()
throws TimeoutException, ParserException {