Allow continuous seeking.

PiperOrigin-RevId: 419629912
This commit is contained in:
claincly 2022-01-04 18:36:53 +00:00 committed by tonihei
parent 34ed8e2b5f
commit 90912b0710
2 changed files with 72 additions and 13 deletions

View File

@ -141,6 +141,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@RtspState private int rtspState; @RtspState private int rtspState;
private boolean hasUpdatedTimelineAndTracks; private boolean hasUpdatedTimelineAndTracks;
private boolean receivedAuthorizationRequest; private boolean receivedAuthorizationRequest;
private boolean hasPendingPauseRequest;
private long pendingSeekPositionUs; private long pendingSeekPositionUs;
/** /**
@ -235,7 +236,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param positionUs The seek time measured in microseconds. * @param positionUs The seek time measured in microseconds.
*/ */
public void seekToUs(long positionUs) { public void seekToUs(long positionUs) {
messageSender.sendPauseRequest(uri, checkNotNull(sessionId)); // RTSP state is PLAYING after sending out a PAUSE, before receiving the PAUSE response. Sends
// out PAUSE only when state PLAYING and no PAUSE is sent.
if (rtspState == RTSP_STATE_PLAYING && !hasPendingPauseRequest) {
messageSender.sendPauseRequest(uri, checkNotNull(sessionId));
}
pendingSeekPositionUs = positionUs; pendingSeekPositionUs = positionUs;
} }
@ -399,6 +404,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
sendRequest( sendRequest(
getRequestWithCommonHeaders( getRequestWithCommonHeaders(
METHOD_PAUSE, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri)); METHOD_PAUSE, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
hasPendingPauseRequest = true;
} }
public void retryLastRequest() { public void retryLastRequest() {
@ -690,15 +696,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
keepAliveMonitor.start(); keepAliveMonitor.start();
} }
pendingSeekPositionUs = C.TIME_UNSET;
// onPlaybackStarted could initiate another seek request, which will set
// pendingSeekPositionUs.
playbackEventListener.onPlaybackStarted( playbackEventListener.onPlaybackStarted(
Util.msToUs(response.sessionTiming.startTimeMs), response.trackTimingList); Util.msToUs(response.sessionTiming.startTimeMs), response.trackTimingList);
pendingSeekPositionUs = C.TIME_UNSET;
} }
private void onPauseResponseReceived() { private void onPauseResponseReceived() {
checkState(rtspState == RTSP_STATE_PLAYING); checkState(rtspState == RTSP_STATE_PLAYING);
rtspState = RTSP_STATE_READY; rtspState = RTSP_STATE_READY;
hasPendingPauseRequest = false;
if (pendingSeekPositionUs != C.TIME_UNSET) { if (pendingSeekPositionUs != C.TIME_UNSET) {
startPlayback(Util.usToMs(pendingSeekPositionUs)); startPlayback(Util.usToMs(pendingSeekPositionUs));
} }

View File

@ -86,8 +86,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable private IOException preparationError; @Nullable private IOException preparationError;
@Nullable private RtspPlaybackException playbackException; @Nullable private RtspPlaybackException playbackException;
private long lastSeekPositionUs; private long requestedSeekPositionUs;
private long pendingSeekPositionUs; private long pendingSeekPositionUs;
private long pendingSeekPositionUsForTcpRetry;
private boolean loadingFinished; private boolean loadingFinished;
private boolean released; private boolean released;
private boolean prepared; private boolean prepared;
@ -132,6 +133,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
selectedLoadInfos = new ArrayList<>(); selectedLoadInfos = new ArrayList<>();
pendingSeekPositionUs = C.TIME_UNSET; pendingSeekPositionUs = C.TIME_UNSET;
requestedSeekPositionUs = C.TIME_UNSET;
pendingSeekPositionUsForTcpRetry = C.TIME_UNSET;
} }
/** Releases the {@link RtspMediaPeriod}. */ /** Releases the {@link RtspMediaPeriod}. */
@ -245,17 +248,52 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long seekToUs(long positionUs) { public long seekToUs(long positionUs) {
// Handles all RTSP seeking cases:
// 1. Seek before the first RTP/UDP packet is received. The seek position is cached to be used
// after retrying playback with RTP/TCP.
// 2a. Normal RTSP seek: if no additional seek is requested after the first seek. Request RTSP
// PAUSE and then PLAY at the seek position.
// 2b. If additional seek is requested after the first seek, records the new seek position,
// 2b.1. If RTSP PLAY (for the first seek) is already sent, the new seek position is used to
// initiate another seek upon receiving PLAY response by invoking this method again.
// 2b.2. If RTSP PLAY (for the first seek) has not been sent, the new seek position will be
// used in the following PLAY request.
// TODO(internal: b/198620566) Handle initial seek.
// TODO(internal: b/213153670) Handle dropped seek position.
if (getBufferedPositionUs() == 0 && !isUsingRtpTcp) {
// Stores the seek position for later, if no RTP packet is received when using UDP.
pendingSeekPositionUsForTcpRetry = positionUs;
return positionUs;
}
discardBuffer(positionUs, /* toKeyframe= */ false);
requestedSeekPositionUs = positionUs;
if (isSeekPending()) { if (isSeekPending()) {
// TODO(internal b/172331505) Allow seek when a seek is pending. switch (rtspClient.getState()) {
// Does not allow another seek if a seek is pending. case RtspClient.RTSP_STATE_READY:
return pendingSeekPositionUs; // PLAY request is sent, yet to receive the response. requestedSeekPositionUs stores the
// new position to do another seek upon receiving the PLAY response.
return positionUs;
case RtspClient.RTSP_STATE_PLAYING:
// Pending PAUSE response, updates client with the newest seek position for the following
// PLAY request.
pendingSeekPositionUs = positionUs;
rtspClient.seekToUs(pendingSeekPositionUs);
return positionUs;
case RtspClient.RTSP_STATE_UNINITIALIZED:
case RtspClient.RTSP_STATE_INIT:
default:
// Never happens.
throw new IllegalStateException();
}
} }
if (seekInsideBufferUs(positionUs)) { if (seekInsideBufferUs(positionUs)) {
return positionUs; return positionUs;
} }
lastSeekPositionUs = positionUs;
pendingSeekPositionUs = positionUs; pendingSeekPositionUs = positionUs;
rtspClient.seekToUs(positionUs); rtspClient.seekToUs(positionUs);
for (int i = 0; i < rtspLoaderWrappers.size(); i++) { for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
@ -275,8 +313,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return C.TIME_END_OF_SOURCE; return C.TIME_END_OF_SOURCE;
} }
if (isSeekPending()) { if (requestedSeekPositionUs != C.TIME_UNSET) {
return pendingSeekPositionUs; return requestedSeekPositionUs;
} }
boolean allLoaderWrappersAreCanceled = true; boolean allLoaderWrappersAreCanceled = true;
@ -290,7 +328,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
return allLoaderWrappersAreCanceled || bufferedPositionUs == Long.MIN_VALUE return allLoaderWrappersAreCanceled || bufferedPositionUs == Long.MIN_VALUE
? lastSeekPositionUs ? 0
: bufferedPositionUs; : bufferedPositionUs;
} }
@ -441,7 +479,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void onLoadCompleted( public void onLoadCompleted(
RtpDataLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) { RtpDataLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
// TODO(b/172331505) Allow for retry when loading is not ending.
if (getBufferedPositionUs() == 0) { if (getBufferedPositionUs() == 0) {
if (!isUsingRtpTcp) { if (!isUsingRtpTcp) {
// Retry playback with TCP if no sample has been received so far, and we are not already // Retry playback with TCP if no sample has been received so far, and we are not already
@ -537,13 +574,26 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
dataLoadable.setTimestamp(trackTiming.rtpTimestamp); dataLoadable.setTimestamp(trackTiming.rtpTimestamp);
dataLoadable.setSequenceNumber(trackTiming.sequenceNumber); dataLoadable.setSequenceNumber(trackTiming.sequenceNumber);
if (isSeekPending()) { if (isSeekPending() && pendingSeekPositionUs == requestedSeekPositionUs) {
// Seek loadable only when all pending seeks are processed, or SampleQueues will report
// inconsistent bufferedPosition.
dataLoadable.seekToUs(startPositionUs, trackTiming.rtpTimestamp); dataLoadable.seekToUs(startPositionUs, trackTiming.rtpTimestamp);
} }
} }
if (isSeekPending()) { if (isSeekPending()) {
pendingSeekPositionUs = C.TIME_UNSET; if (pendingSeekPositionUs == requestedSeekPositionUs) {
// No seek request was made after the current pending seek.
pendingSeekPositionUs = C.TIME_UNSET;
requestedSeekPositionUs = C.TIME_UNSET;
} else {
// Resets pendingSeekPositionUs to perform a fresh RTSP seek.
pendingSeekPositionUs = C.TIME_UNSET;
seekToUs(requestedSeekPositionUs);
}
} else if (pendingSeekPositionUsForTcpRetry != C.TIME_UNSET) {
seekToUs(pendingSeekPositionUsForTcpRetry);
pendingSeekPositionUsForTcpRetry = C.TIME_UNSET;
} }
} }