Commit 021291b3 by tonihei Committed by Oliver Woodman

Add server-client time offset to Window.

This offset allows to improve the calculated live offset because it
can take known client-server time offsets into account.

PiperOrigin-RevId: 285970738
parent a035c2e2
......@@ -130,6 +130,7 @@ import java.util.Arrays;
/* manifest= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* isSeekable= */ !isDynamic,
isDynamic,
isLive[windowIndex],
......
......@@ -153,7 +153,7 @@ public abstract class BasePlayer implements Player {
if (windowStartTimeMs == C.TIME_UNSET) {
return C.TIME_UNSET;
}
return System.currentTimeMillis() - window.windowStartTimeMs - getContentPosition();
return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();
}
@Override
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;
import android.os.SystemClock;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
......@@ -136,19 +137,28 @@ public abstract class Timeline {
/**
* The start time of the presentation to which this window belongs in milliseconds since the
* epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes only.
* Unix epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes
* only.
*/
public long presentationStartTimeMs;
/**
* The window's start time in milliseconds since the epoch, or {@link C#TIME_UNSET} if unknown
* or not applicable. For informational purposes only.
* The window's start time in milliseconds since the Unix epoch, or {@link C#TIME_UNSET} if
* unknown or not applicable. For informational purposes only.
*/
public long windowStartTimeMs;
/**
* Whether it's possible to seek within this window.
* The offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix epoch
* according to the clock of the media origin server, or {@link C#TIME_UNSET} if unknown or not
* applicable.
*
* <p>Note that the current Unix time can be retrieved using {@link #getCurrentUnixTimeMs()} and
* is calculated as {@code SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs}.
*/
public long elapsedRealtimeEpochOffsetMs;
/** Whether it's possible to seek within this window. */
public boolean isSeekable;
// TODO: Split this to better describe which parts of the window might change. For example it
......@@ -205,6 +215,7 @@ public abstract class Timeline {
@Nullable Object manifest,
long presentationStartTimeMs,
long windowStartTimeMs,
long elapsedRealtimeEpochOffsetMs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
......@@ -218,6 +229,7 @@ public abstract class Timeline {
this.manifest = manifest;
this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs;
this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
......@@ -279,6 +291,16 @@ public abstract class Timeline {
return positionInFirstPeriodUs;
}
/**
* Returns the current time in milliseconds since the Unix epoch.
*
* <p>This method applies {@link #elapsedRealtimeEpochOffsetMs known corrections} made available
* by the media such that this time corresponds to the clock of the media origin server.
*/
public long getCurrentUnixTimeMs() {
return Util.getNowUnixTimeMs(elapsedRealtimeEpochOffsetMs);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
......@@ -293,6 +315,7 @@ public abstract class Timeline {
&& Util.areEqual(manifest, that.manifest)
&& presentationStartTimeMs == that.presentationStartTimeMs
&& windowStartTimeMs == that.windowStartTimeMs
&& elapsedRealtimeEpochOffsetMs == that.elapsedRealtimeEpochOffsetMs
&& isSeekable == that.isSeekable
&& isDynamic == that.isDynamic
&& isLive == that.isLive
......@@ -311,6 +334,9 @@ public abstract class Timeline {
result = 31 * result + (manifest == null ? 0 : manifest.hashCode());
result = 31 * result + (int) (presentationStartTimeMs ^ (presentationStartTimeMs >>> 32));
result = 31 * result + (int) (windowStartTimeMs ^ (windowStartTimeMs >>> 32));
result =
31 * result
+ (int) (elapsedRealtimeEpochOffsetMs ^ (elapsedRealtimeEpochOffsetMs >>> 32));
result = 31 * result + (isSeekable ? 1 : 0);
result = 31 * result + (isDynamic ? 1 : 0);
result = 31 * result + (isLive ? 1 : 0);
......
......@@ -337,6 +337,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
/* manifest= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* isSeekable= */ false,
// Dynamic window to indicate pending timeline updates.
/* isDynamic= */ true,
......
......@@ -29,6 +29,7 @@ public final class SinglePeriodTimeline extends Timeline {
private final long presentationStartTimeMs;
private final long windowStartTimeMs;
private final long elapsedRealtimeEpochOffsetMs;
private final long periodDurationUs;
private final long windowDurationUs;
private final long windowPositionInPeriodUs;
......@@ -110,6 +111,7 @@ public final class SinglePeriodTimeline extends Timeline {
this(
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
periodDurationUs,
windowDurationUs,
windowPositionInPeriodUs,
......@@ -126,8 +128,12 @@ public final class SinglePeriodTimeline extends Timeline {
* position in the period.
*
* @param presentationStartTimeMs The start time of the presentation in milliseconds since the
* epoch.
* @param windowStartTimeMs The window's start time in milliseconds since the epoch.
* epoch, or {@link C#TIME_UNSET} if unknown or not applicable.
* @param windowStartTimeMs The window's start time in milliseconds since the epoch, or {@link
* C#TIME_UNSET} if unknown or not applicable.
* @param elapsedRealtimeEpochOffsetMs The offset between {@link
* android.os.SystemClock#elapsedRealtime()} and the time since the Unix epoch according to
* the clock of the media origin server, or {@link C#TIME_UNSET} if unknown or not applicable.
* @param periodDurationUs The duration of the period in microseconds.
* @param windowDurationUs The duration of the window in microseconds.
* @param windowPositionInPeriodUs The position of the start of the window in the period, in
......@@ -143,6 +149,7 @@ public final class SinglePeriodTimeline extends Timeline {
public SinglePeriodTimeline(
long presentationStartTimeMs,
long windowStartTimeMs,
long elapsedRealtimeEpochOffsetMs,
long periodDurationUs,
long windowDurationUs,
long windowPositionInPeriodUs,
......@@ -154,6 +161,7 @@ public final class SinglePeriodTimeline extends Timeline {
@Nullable Object tag) {
this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs;
this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
this.periodDurationUs = periodDurationUs;
this.windowDurationUs = windowDurationUs;
this.windowPositionInPeriodUs = windowPositionInPeriodUs;
......@@ -192,13 +200,14 @@ public final class SinglePeriodTimeline extends Timeline {
manifest,
presentationStartTimeMs,
windowStartTimeMs,
elapsedRealtimeEpochOffsetMs,
isSeekable,
isDynamic,
isLive,
windowDefaultStartPositionUs,
windowDurationUs,
0,
0,
/* firstPeriodIndex= */ 0,
/* lastPeriodIndex= */ 0,
windowPositionInPeriodUs);
}
......
......@@ -39,6 +39,7 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.SystemClock;
import android.security.NetworkSecurityPolicy;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
......@@ -2045,6 +2046,19 @@ public final class Util {
}
}
/**
* Returns the current time in milliseconds since the epoch.
*
* @param elapsedRealtimeEpochOffsetMs The offset between {@link SystemClock#elapsedRealtime()}
* and the time since the Unix epoch, or {@link C#TIME_UNSET} if unknown.
* @return The Unix time in milliseconds since the epoch.
*/
public static long getNowUnixTimeMs(long elapsedRealtimeEpochOffsetMs) {
return elapsedRealtimeEpochOffsetMs == C.TIME_UNSET
? System.currentTimeMillis()
: SystemClock.elapsedRealtime() + elapsedRealtimeEpochOffsetMs;
}
@Nullable
private static String getSystemProperty(String name) {
try {
......
......@@ -134,6 +134,7 @@ public class TimelineTest {
window.manifest,
window.presentationStartTimeMs,
window.windowStartTimeMs,
window.elapsedRealtimeEpochOffsetMs,
window.isSeekable,
window.isDynamic,
window.isLive,
......
......@@ -42,7 +42,8 @@ public interface DashChunkSource extends ChunkSource {
* @param trackSelection The track selection.
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds,
* specified as the server's unix time minus the local elapsed time. If unknown, set to 0.
* specified as the server's unix time minus the local elapsed time. Or {@link
* com.google.android.exoplayer2.C#TIME_UNSET} if unknown.
* @param enableEventMessageTrack Whether to output an event message track.
* @param closedCaptionFormats The {@link Format Formats} of closed caption tracks to be output.
* @param transferListener The transfer listener which should be informed of any data transfers.
......
......@@ -621,6 +621,7 @@ public final class DashMediaSource extends BaseMediaSource {
periodsById = new SparseArray<>();
playerEmsgCallback = new DefaultPlayerEmsgCallback();
expiredManifestPublishTimeUs = C.TIME_UNSET;
elapsedRealtimeOffsetMs = C.TIME_UNSET;
if (sideloadedManifest) {
Assertions.checkState(!manifest.dynamic);
manifestCallback = null;
......@@ -723,7 +724,7 @@ public final class DashMediaSource extends BaseMediaSource {
handler.removeCallbacksAndMessages(null);
handler = null;
}
elapsedRealtimeOffsetMs = 0;
elapsedRealtimeOffsetMs = C.TIME_UNSET;
staleManifestReloadAttempt = 0;
expiredManifestPublishTimeUs = C.TIME_UNSET;
firstPeriodId = 0;
......@@ -969,7 +970,8 @@ public final class DashMediaSource extends BaseMediaSource {
if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) {
// The manifest describes an incomplete live stream. Update the start/end times to reflect the
// live stream duration and the manifest's time shift buffer depth.
long liveStreamDurationUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTimeMs);
long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs));
long liveStreamDurationUs = nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs);
long liveStreamEndPositionInLastPeriodUs = liveStreamDurationUs
- C.msToUs(manifest.getPeriod(lastPeriodIndex).startMs);
currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs);
......@@ -1022,6 +1024,7 @@ public final class DashMediaSource extends BaseMediaSource {
new DashTimeline(
manifest.availabilityStartTimeMs,
windowStartTimeMs,
elapsedRealtimeOffsetMs,
firstPeriodId,
currentStartTimeUs,
windowDurationUs,
......@@ -1093,14 +1096,6 @@ public final class DashMediaSource extends BaseMediaSource {
manifestEventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
}
private long getNowUnixTimeUs() {
if (elapsedRealtimeOffsetMs != 0) {
return C.msToUs(SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs);
} else {
return C.msToUs(System.currentTimeMillis());
}
}
private static final class PeriodSeekInfo {
public static PeriodSeekInfo createPeriodSeekInfo(
......@@ -1170,6 +1165,7 @@ public final class DashMediaSource extends BaseMediaSource {
private final long presentationStartTimeMs;
private final long windowStartTimeMs;
private final long elapsedRealtimeEpochOffsetMs;
private final int firstPeriodId;
private final long offsetInFirstPeriodUs;
......@@ -1181,6 +1177,7 @@ public final class DashMediaSource extends BaseMediaSource {
public DashTimeline(
long presentationStartTimeMs,
long windowStartTimeMs,
long elapsedRealtimeEpochOffsetMs,
int firstPeriodId,
long offsetInFirstPeriodUs,
long windowDurationUs,
......@@ -1189,6 +1186,7 @@ public final class DashMediaSource extends BaseMediaSource {
@Nullable Object windowTag) {
this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs;
this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs;
this.firstPeriodId = firstPeriodId;
this.offsetInFirstPeriodUs = offsetInFirstPeriodUs;
this.windowDurationUs = windowDurationUs;
......@@ -1228,6 +1226,7 @@ public final class DashMediaSource extends BaseMediaSource {
manifest,
presentationStartTimeMs,
windowStartTimeMs,
elapsedRealtimeEpochOffsetMs,
/* isSeekable= */ true,
/* isDynamic= */ isMovingLiveWindow(manifest),
/* isLive= */ manifest.dynamic,
......
......@@ -136,7 +136,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* as the server's unix time minus the local elapsed time. If unknown, set to 0.
* as the server's unix time minus the local elapsed time. Or {@link C#TIME_UNSET} if unknown.
* @param maxSegmentsPerLoad The maximum number of segments to combine into a single request. Note
* that segments will only be combined if their {@link Uri}s are the same and if their data
* ranges are adjacent.
......@@ -267,7 +267,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
return;
}
long nowUnixTimeUs = getNowUnixTimeUs();
long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs));
MediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1);
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) {
......@@ -474,14 +474,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
? representationHolder.getSegmentEndTimeUs(lastAvailableSegmentNum) : C.TIME_UNSET;
}
private long getNowUnixTimeUs() {
if (elapsedRealtimeOffsetMs != 0) {
return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000;
} else {
return System.currentTimeMillis() * 1000;
}
}
private long resolveTimeToLiveEdgeUs(long playbackPositionUs) {
boolean resolveTimeToLiveEdgePossible = manifest.dynamic && liveEdgeTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible ? liveEdgeTimeUs - playbackPositionUs : C.TIME_UNSET;
......
......@@ -468,6 +468,7 @@ public final class HlsMediaSource extends BaseMediaSource
new SinglePeriodTimeline(
presentationStartTimeMs,
windowStartTimeMs,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
periodDurationUs,
/* windowDurationUs= */ playlist.durationUs,
/* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs,
......@@ -485,6 +486,7 @@ public final class HlsMediaSource extends BaseMediaSource
new SinglePeriodTimeline(
presentationStartTimeMs,
windowStartTimeMs,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* periodDurationUs= */ playlist.durationUs,
/* windowDurationUs= */ playlist.durationUs,
/* windowPositionInPeriodUs= */ 0,
......
......@@ -219,6 +219,7 @@ public final class FakeTimeline extends Timeline {
manifests[windowIndex],
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
windowDefinition.isSeekable,
windowDefinition.isDynamic,
windowDefinition.isLive,
......
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