mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Fix position jank after pausing and seeking
Issue: #6901 PiperOrigin-RevId: 314418536
This commit is contained in:
parent
fb011e66a6
commit
a818049143
3 changed files with 47 additions and 12 deletions
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
### 2.11.5 (2020-06-03) ###
|
### 2.11.5 (2020-06-03) ###
|
||||||
|
|
||||||
|
* Improve the smoothness of video playback immediately after starting, seeking
|
||||||
|
or resuming a playback
|
||||||
|
([#6901](https://github.com/google/ExoPlayer/issues/6901)).
|
||||||
* Add `SilenceMediaSource.Factory` to support tags.
|
* Add `SilenceMediaSource.Factory` to support tags.
|
||||||
* Enable the configuration of `SilenceSkippingAudioProcessor`
|
* Enable the configuration of `SilenceSkippingAudioProcessor`
|
||||||
([#6705](https://github.com/google/ExoPlayer/issues/6705)).
|
([#6705](https://github.com/google/ExoPlayer/issues/6705)).
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
*
|
*
|
||||||
* <p>If {@link #hasTimestamp()} returns {@code true}, call {@link #getTimestampSystemTimeUs()} to
|
* <p>If {@link #hasTimestamp()} returns {@code true}, call {@link #getTimestampSystemTimeUs()} to
|
||||||
* get the system time at which the latest timestamp was sampled and {@link
|
* get the system time at which the latest timestamp was sampled and {@link
|
||||||
* #getTimestampPositionFrames()} to get its position in frames. If {@link #isTimestampAdvancing()}
|
* #getTimestampPositionFrames()} to get its position in frames. If {@link #hasAdvancingTimestamp()}
|
||||||
* returns {@code true}, the caller should assume that the timestamp has been increasing in real
|
* returns {@code true}, the caller should assume that the timestamp has been increasing in real
|
||||||
* time since it was sampled. Otherwise, it may be stationary.
|
* time since it was sampled. Otherwise, it may be stationary.
|
||||||
*
|
*
|
||||||
|
|
@ -68,7 +68,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
private static final int STATE_ERROR = 4;
|
private static final int STATE_ERROR = 4;
|
||||||
|
|
||||||
/** The polling interval for {@link #STATE_INITIALIZING} and {@link #STATE_TIMESTAMP}. */
|
/** The polling interval for {@link #STATE_INITIALIZING} and {@link #STATE_TIMESTAMP}. */
|
||||||
private static final int FAST_POLL_INTERVAL_US = 5_000;
|
private static final int FAST_POLL_INTERVAL_US = 10_000;
|
||||||
/**
|
/**
|
||||||
* The polling interval for {@link #STATE_TIMESTAMP_ADVANCING} and {@link #STATE_NO_TIMESTAMP}.
|
* The polling interval for {@link #STATE_TIMESTAMP_ADVANCING} and {@link #STATE_NO_TIMESTAMP}.
|
||||||
*/
|
*/
|
||||||
|
|
@ -110,7 +110,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
* timestamp is available via {@link #getTimestampSystemTimeUs()} and {@link
|
* timestamp is available via {@link #getTimestampSystemTimeUs()} and {@link
|
||||||
* #getTimestampPositionFrames()}, and the caller should call {@link #acceptTimestamp()} if the
|
* #getTimestampPositionFrames()}, and the caller should call {@link #acceptTimestamp()} if the
|
||||||
* timestamp was valid, or {@link #rejectTimestamp()} otherwise. The values returned by {@link
|
* timestamp was valid, or {@link #rejectTimestamp()} otherwise. The values returned by {@link
|
||||||
* #hasTimestamp()} and {@link #isTimestampAdvancing()} may be updated.
|
* #hasTimestamp()} and {@link #hasAdvancingTimestamp()} may be updated.
|
||||||
*
|
*
|
||||||
* @param systemTimeUs The current system time, in microseconds.
|
* @param systemTimeUs The current system time, in microseconds.
|
||||||
* @return Whether the timestamp was updated.
|
* @return Whether the timestamp was updated.
|
||||||
|
|
@ -200,12 +200,12 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the timestamp appears to be advancing. If {@code true}, call {@link
|
* Returns whether this instance has an advancing timestamp. If {@code true}, call {@link
|
||||||
* #getTimestampSystemTimeUs()} and {@link #getTimestampSystemTimeUs()} to access the timestamp. A
|
* #getTimestampSystemTimeUs()} and {@link #getTimestampSystemTimeUs()} to access the timestamp. A
|
||||||
* current position for the track can be extrapolated based on elapsed real time since the system
|
* current position for the track can be extrapolated based on elapsed real time since the system
|
||||||
* time at which the timestamp was sampled.
|
* time at which the timestamp was sampled.
|
||||||
*/
|
*/
|
||||||
public boolean isTimestampAdvancing() {
|
public boolean hasAdvancingTimestamp() {
|
||||||
return state == STATE_TIMESTAMP_ADVANCING;
|
return state == STATE_TIMESTAMP_ADVANCING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,8 @@ import java.lang.reflect.Method;
|
||||||
* <p>This is a fail safe that should not be required on correctly functioning devices.
|
* <p>This is a fail safe that should not be required on correctly functioning devices.
|
||||||
*/
|
*/
|
||||||
private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND;
|
private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND;
|
||||||
|
/** The duration of time used to smooth over an adjustment between position sampling modes. */
|
||||||
|
private static final long MODE_SWITCH_SMOOTHING_DURATION_US = C.MICROS_PER_SECOND;
|
||||||
|
|
||||||
private static final long FORCE_RESET_WORKAROUND_TIMEOUT_MS = 200;
|
private static final long FORCE_RESET_WORKAROUND_TIMEOUT_MS = 200;
|
||||||
|
|
||||||
|
|
@ -160,6 +162,15 @@ import java.lang.reflect.Method;
|
||||||
private long stopPlaybackHeadPosition;
|
private long stopPlaybackHeadPosition;
|
||||||
private long endPlaybackHeadPosition;
|
private long endPlaybackHeadPosition;
|
||||||
|
|
||||||
|
// Results from the previous call to getCurrentPositionUs.
|
||||||
|
private long lastPositionUs;
|
||||||
|
private long lastSystemTimeUs;
|
||||||
|
private boolean lastSampleUsedGetTimestampMode;
|
||||||
|
|
||||||
|
// Results from the last call to getCurrentPositionUs that used a different sample mode.
|
||||||
|
private long previousModePositionUs;
|
||||||
|
private long previousModeSystemTimeUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new audio track position tracker.
|
* Creates a new audio track position tracker.
|
||||||
*
|
*
|
||||||
|
|
@ -218,18 +229,16 @@ import java.lang.reflect.Method;
|
||||||
// If the device supports it, use the playback timestamp from AudioTrack.getTimestamp.
|
// If the device supports it, use the playback timestamp from AudioTrack.getTimestamp.
|
||||||
// Otherwise, derive a smoothed position by sampling the track's frame position.
|
// Otherwise, derive a smoothed position by sampling the track's frame position.
|
||||||
long systemTimeUs = System.nanoTime() / 1000;
|
long systemTimeUs = System.nanoTime() / 1000;
|
||||||
|
long positionUs;
|
||||||
AudioTimestampPoller audioTimestampPoller = Assertions.checkNotNull(this.audioTimestampPoller);
|
AudioTimestampPoller audioTimestampPoller = Assertions.checkNotNull(this.audioTimestampPoller);
|
||||||
if (audioTimestampPoller.hasTimestamp()) {
|
boolean useGetTimestampMode = audioTimestampPoller.hasAdvancingTimestamp();
|
||||||
|
if (useGetTimestampMode) {
|
||||||
// Calculate the speed-adjusted position using the timestamp (which may be in the future).
|
// Calculate the speed-adjusted position using the timestamp (which may be in the future).
|
||||||
long timestampPositionFrames = audioTimestampPoller.getTimestampPositionFrames();
|
long timestampPositionFrames = audioTimestampPoller.getTimestampPositionFrames();
|
||||||
long timestampPositionUs = framesToDurationUs(timestampPositionFrames);
|
long timestampPositionUs = framesToDurationUs(timestampPositionFrames);
|
||||||
if (!audioTimestampPoller.isTimestampAdvancing()) {
|
|
||||||
return timestampPositionUs;
|
|
||||||
}
|
|
||||||
long elapsedSinceTimestampUs = systemTimeUs - audioTimestampPoller.getTimestampSystemTimeUs();
|
long elapsedSinceTimestampUs = systemTimeUs - audioTimestampPoller.getTimestampSystemTimeUs();
|
||||||
return timestampPositionUs + elapsedSinceTimestampUs;
|
positionUs = timestampPositionUs + elapsedSinceTimestampUs;
|
||||||
} else {
|
} else {
|
||||||
long positionUs;
|
|
||||||
if (playheadOffsetCount == 0) {
|
if (playheadOffsetCount == 0) {
|
||||||
// The AudioTrack has started, but we don't have any samples to compute a smoothed position.
|
// The AudioTrack has started, but we don't have any samples to compute a smoothed position.
|
||||||
positionUs = getPlaybackHeadPositionUs();
|
positionUs = getPlaybackHeadPositionUs();
|
||||||
|
|
@ -242,8 +251,29 @@ import java.lang.reflect.Method;
|
||||||
if (!sourceEnded) {
|
if (!sourceEnded) {
|
||||||
positionUs = Math.max(0, positionUs - latencyUs);
|
positionUs = Math.max(0, positionUs - latencyUs);
|
||||||
}
|
}
|
||||||
return positionUs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastSampleUsedGetTimestampMode != useGetTimestampMode) {
|
||||||
|
// We've switched sampling mode.
|
||||||
|
previousModeSystemTimeUs = lastSystemTimeUs;
|
||||||
|
previousModePositionUs = lastPositionUs;
|
||||||
|
}
|
||||||
|
long elapsedSincePreviousModeUs = systemTimeUs - previousModeSystemTimeUs;
|
||||||
|
if (elapsedSincePreviousModeUs < MODE_SWITCH_SMOOTHING_DURATION_US) {
|
||||||
|
// Use a ramp to smooth between the old mode and the new one to avoid introducing a sudden
|
||||||
|
// jump if the two modes disagree.
|
||||||
|
long previousModeProjectedPositionUs = previousModePositionUs + elapsedSincePreviousModeUs;
|
||||||
|
// A ramp consisting of 1000 points distributed over MODE_SWITCH_SMOOTHING_DURATION_US.
|
||||||
|
long rampPoint = (elapsedSincePreviousModeUs * 1000) / MODE_SWITCH_SMOOTHING_DURATION_US;
|
||||||
|
positionUs *= rampPoint;
|
||||||
|
positionUs += (1000 - rampPoint) * previousModeProjectedPositionUs;
|
||||||
|
positionUs /= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSystemTimeUs = systemTimeUs;
|
||||||
|
lastPositionUs = positionUs;
|
||||||
|
lastSampleUsedGetTimestampMode = useGetTimestampMode;
|
||||||
|
return positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Starts position tracking. Must be called immediately before {@link AudioTrack#play()}. */
|
/** Starts position tracking. Must be called immediately before {@link AudioTrack#play()}. */
|
||||||
|
|
@ -458,6 +488,8 @@ import java.lang.reflect.Method;
|
||||||
playheadOffsetCount = 0;
|
playheadOffsetCount = 0;
|
||||||
nextPlayheadOffsetIndex = 0;
|
nextPlayheadOffsetIndex = 0;
|
||||||
lastPlayheadSampleTimeUs = 0;
|
lastPlayheadSampleTimeUs = 0;
|
||||||
|
lastSystemTimeUs = 0;
|
||||||
|
previousModeSystemTimeUs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue