Commit 42a2b923 by christosts Committed by Oliver Woodman

HLS: populate targetLiveOffset in MediaItem from server control

Issue: #5011
PiperOrigin-RevId: 340260636
parent e1211f92
...@@ -16,12 +16,13 @@ ...@@ -16,12 +16,13 @@
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.net.Uri; import android.net.Uri;
import android.os.SystemClock;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
...@@ -52,6 +53,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource; ...@@ -52,6 +53,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -87,7 +89,6 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -87,7 +89,6 @@ public final class HlsMediaSource extends BaseMediaSource
public static final int METADATA_TYPE_ID3 = 1; public static final int METADATA_TYPE_ID3 = 1;
/** Type for ESMG metadata in HLS streams. */ /** Type for ESMG metadata in HLS streams. */
public static final int METADATA_TYPE_EMSG = 3; public static final int METADATA_TYPE_EMSG = 3;
/** Factory for {@link HlsMediaSource}s. */ /** Factory for {@link HlsMediaSource}s. */
public static final class Factory implements MediaSourceFactory { public static final class Factory implements MediaSourceFactory {
...@@ -105,6 +106,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -105,6 +106,7 @@ public final class HlsMediaSource extends BaseMediaSource
private boolean useSessionKeys; private boolean useSessionKeys;
private List<StreamKey> streamKeys; private List<StreamKey> streamKeys;
@Nullable private Object tag; @Nullable private Object tag;
private long elapsedRealTimeOffsetMs;
/** /**
* Creates a new factory for {@link HlsMediaSource}s. * Creates a new factory for {@link HlsMediaSource}s.
...@@ -133,6 +135,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -133,6 +135,7 @@ public final class HlsMediaSource extends BaseMediaSource
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
metadataType = METADATA_TYPE_ID3; metadataType = METADATA_TYPE_ID3;
streamKeys = Collections.emptyList(); streamKeys = Collections.emptyList();
elapsedRealTimeOffsetMs = C.TIME_UNSET;
} }
/** /**
...@@ -316,6 +319,20 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -316,6 +319,20 @@ public final class HlsMediaSource extends BaseMediaSource
return this; return this;
} }
/**
* Sets the offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix
* epoch. By default, is it set to {@link C#TIME_UNSET}.
*
* @param elapsedRealTimeOffsetMs The offset between {@link SystemClock#elapsedRealtime()} and
* the time since the Unix epoch, in milliseconds.
* @return This factory, for convenience.
*/
@VisibleForTesting
/* package */ Factory setElapsedRealTimeOffsetMs(long elapsedRealTimeOffsetMs) {
this.elapsedRealTimeOffsetMs = elapsedRealTimeOffsetMs;
return this;
}
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */ /** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
...@@ -364,6 +381,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -364,6 +381,7 @@ public final class HlsMediaSource extends BaseMediaSource
loadErrorHandlingPolicy, loadErrorHandlingPolicy,
playlistTrackerFactory.createTracker( playlistTrackerFactory.createTracker(
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory), hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
elapsedRealTimeOffsetMs,
allowChunklessPreparation, allowChunklessPreparation,
metadataType, metadataType,
useSessionKeys); useSessionKeys);
...@@ -376,7 +394,6 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -376,7 +394,6 @@ public final class HlsMediaSource extends BaseMediaSource
} }
private final HlsExtractorFactory extractorFactory; private final HlsExtractorFactory extractorFactory;
private final MediaItem mediaItem;
private final MediaItem.PlaybackProperties playbackProperties; private final MediaItem.PlaybackProperties playbackProperties;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
...@@ -386,7 +403,9 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -386,7 +403,9 @@ public final class HlsMediaSource extends BaseMediaSource
private final @MetadataType int metadataType; private final @MetadataType int metadataType;
private final boolean useSessionKeys; private final boolean useSessionKeys;
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
private final long elapsedRealTimeOffsetMs;
private MediaItem mediaItem;
@Nullable private TransferListener mediaTransferListener; @Nullable private TransferListener mediaTransferListener;
private HlsMediaSource( private HlsMediaSource(
...@@ -397,6 +416,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -397,6 +416,7 @@ public final class HlsMediaSource extends BaseMediaSource
DrmSessionManager drmSessionManager, DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy, LoadErrorHandlingPolicy loadErrorHandlingPolicy,
HlsPlaylistTracker playlistTracker, HlsPlaylistTracker playlistTracker,
long elapsedRealTimeOffsetMs,
boolean allowChunklessPreparation, boolean allowChunklessPreparation,
@MetadataType int metadataType, @MetadataType int metadataType,
boolean useSessionKeys) { boolean useSessionKeys) {
...@@ -408,6 +428,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -408,6 +428,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.drmSessionManager = drmSessionManager; this.drmSessionManager = drmSessionManager;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.elapsedRealTimeOffsetMs = elapsedRealTimeOffsetMs;
this.allowChunklessPreparation = allowChunklessPreparation; this.allowChunklessPreparation = allowChunklessPreparation;
this.metadataType = metadataType; this.metadataType = metadataType;
this.useSessionKeys = useSessionKeys; this.useSessionKeys = useSessionKeys;
...@@ -491,25 +512,28 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -491,25 +512,28 @@ public final class HlsMediaSource extends BaseMediaSource
HlsManifest manifest = HlsManifest manifest =
new HlsManifest(checkNotNull(playlistTracker.getMasterPlaylist()), playlist); new HlsManifest(checkNotNull(playlistTracker.getMasterPlaylist()), playlist);
if (playlistTracker.isLive()) { if (playlistTracker.isLive()) {
long liveEdgeOffsetUs = getLiveEdgeOffsetUs(playlist);
long targetLiveOffsetUs =
mediaItem.liveConfiguration.targetLiveOffsetMs != C.TIME_UNSET
? C.msToUs(mediaItem.liveConfiguration.targetLiveOffsetMs)
: getTargetLiveOffsetUs(playlist, liveEdgeOffsetUs);
// Ensure target live offset is within the live window and greater than the live edge offset.
targetLiveOffsetUs =
Util.constrainValue(
targetLiveOffsetUs, liveEdgeOffsetUs, playlist.durationUs + liveEdgeOffsetUs);
maybeUpdateMediaItem(targetLiveOffsetUs);
long offsetFromInitialStartTimeUs = long offsetFromInitialStartTimeUs =
playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
long periodDurationUs = long periodDurationUs =
playlist.hasEndTag ? offsetFromInitialStartTimeUs + playlist.durationUs : C.TIME_UNSET; playlist.hasEndTag ? offsetFromInitialStartTimeUs + playlist.durationUs : C.TIME_UNSET;
List<HlsMediaPlaylist.Segment> segments = playlist.segments; List<HlsMediaPlaylist.Segment> segments = playlist.segments;
if (windowDefaultStartPositionUs == C.TIME_UNSET) {
windowDefaultStartPositionUs = 0;
if (!segments.isEmpty()) { if (!segments.isEmpty()) {
int defaultStartSegmentIndex = max(0, segments.size() - 3); windowDefaultStartPositionUs = getWindowDefaultStartPosition(playlist, liveEdgeOffsetUs);
// We attempt to set the default start position to be at least twice the target duration } else if (windowDefaultStartPositionUs == C.TIME_UNSET) {
// behind the live edge. windowDefaultStartPositionUs = 0;
long minStartPositionUs = playlist.durationUs - playlist.targetDurationUs * 2;
while (defaultStartSegmentIndex > 0
&& segments.get(defaultStartSegmentIndex).relativeStartTimeUs > minStartPositionUs) {
defaultStartSegmentIndex--;
}
windowDefaultStartPositionUs = segments.get(defaultStartSegmentIndex).relativeStartTimeUs;
}
} }
timeline = timeline =
new SinglePeriodTimeline( new SinglePeriodTimeline(
presentationStartTimeMs, presentationStartTimeMs,
...@@ -545,4 +569,47 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -545,4 +569,47 @@ public final class HlsMediaSource extends BaseMediaSource
} }
refreshSourceInfo(timeline); refreshSourceInfo(timeline);
} }
private long getLiveEdgeOffsetUs(HlsMediaPlaylist playlist) {
return playlist.hasProgramDateTime
? C.msToUs(Util.getNowUnixTimeMs(elapsedRealTimeOffsetMs)) - playlist.getEndTimeUs()
: 0;
}
private long getWindowDefaultStartPosition(HlsMediaPlaylist playlist, long liveEdgeOffsetUs) {
List<HlsMediaPlaylist.Segment> segments = playlist.segments;
int segmentIndex = segments.size() - 1;
long minStartPositionUs =
playlist.durationUs
+ liveEdgeOffsetUs
- C.msToUs(mediaItem.liveConfiguration.targetLiveOffsetMs);
while (segmentIndex > 0
&& segments.get(segmentIndex).relativeStartTimeUs > minStartPositionUs) {
segmentIndex--;
}
return segments.get(segmentIndex).relativeStartTimeUs;
}
private void maybeUpdateMediaItem(long targetLiveOffsetUs) {
long targetLiveOffsetMs = C.usToMs(targetLiveOffsetUs);
if (targetLiveOffsetMs != mediaItem.liveConfiguration.targetLiveOffsetMs) {
mediaItem = mediaItem.buildUpon().setLiveTargetOffsetMs(targetLiveOffsetMs).build();
}
}
private static long getTargetLiveOffsetUs(HlsMediaPlaylist playlist, long liveEdgeOffsetUs) {
HlsMediaPlaylist.ServerControl serverControl = playlist.serverControl;
// Select part hold back only if the playlist has a part target duration.
long offsetToEndOfPlaylistUs;
if (serverControl.partHoldBackUs != C.TIME_UNSET
&& playlist.partTargetDurationUs != C.TIME_UNSET) {
offsetToEndOfPlaylistUs = serverControl.partHoldBackUs;
} else if (serverControl.holdBackUs != C.TIME_UNSET) {
offsetToEndOfPlaylistUs = serverControl.holdBackUs;
} else {
// Fallback, see RFC 8216, Section 4.4.3.8.
offsetToEndOfPlaylistUs = 3 * playlist.targetDurationUs;
}
return offsetToEndOfPlaylistUs + liveEdgeOffsetUs;
}
} }
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