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;
private boolean hasUpdatedTimelineAndTracks;
private boolean receivedAuthorizationRequest;
private boolean hasPendingPauseRequest;
private long pendingSeekPositionUs;
/**
@ -235,7 +236,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param positionUs The seek time measured in microseconds.
*/
public void seekToUs(long positionUs) {
// 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;
}
@ -399,6 +404,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
sendRequest(
getRequestWithCommonHeaders(
METHOD_PAUSE, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
hasPendingPauseRequest = true;
}
public void retryLastRequest() {
@ -690,15 +696,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
keepAliveMonitor.start();
}
pendingSeekPositionUs = C.TIME_UNSET;
// onPlaybackStarted could initiate another seek request, which will set
// pendingSeekPositionUs.
playbackEventListener.onPlaybackStarted(
Util.msToUs(response.sessionTiming.startTimeMs), response.trackTimingList);
pendingSeekPositionUs = C.TIME_UNSET;
}
private void onPauseResponseReceived() {
checkState(rtspState == RTSP_STATE_PLAYING);
rtspState = RTSP_STATE_READY;
hasPendingPauseRequest = false;
if (pendingSeekPositionUs != C.TIME_UNSET) {
startPlayback(Util.usToMs(pendingSeekPositionUs));
}

View File

@ -86,8 +86,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable private IOException preparationError;
@Nullable private RtspPlaybackException playbackException;
private long lastSeekPositionUs;
private long requestedSeekPositionUs;
private long pendingSeekPositionUs;
private long pendingSeekPositionUsForTcpRetry;
private boolean loadingFinished;
private boolean released;
private boolean prepared;
@ -132,6 +133,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
selectedLoadInfos = new ArrayList<>();
pendingSeekPositionUs = C.TIME_UNSET;
requestedSeekPositionUs = C.TIME_UNSET;
pendingSeekPositionUsForTcpRetry = C.TIME_UNSET;
}
/** Releases the {@link RtspMediaPeriod}. */
@ -245,17 +248,52 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
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()) {
// TODO(internal b/172331505) Allow seek when a seek is pending.
// Does not allow another seek if a seek is pending.
return pendingSeekPositionUs;
switch (rtspClient.getState()) {
case RtspClient.RTSP_STATE_READY:
// 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)) {
return positionUs;
}
lastSeekPositionUs = positionUs;
pendingSeekPositionUs = positionUs;
rtspClient.seekToUs(positionUs);
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;
}
if (isSeekPending()) {
return pendingSeekPositionUs;
if (requestedSeekPositionUs != C.TIME_UNSET) {
return requestedSeekPositionUs;
}
boolean allLoaderWrappersAreCanceled = true;
@ -290,7 +328,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
return allLoaderWrappersAreCanceled || bufferedPositionUs == Long.MIN_VALUE
? lastSeekPositionUs
? 0
: bufferedPositionUs;
}
@ -441,7 +479,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public void onLoadCompleted(
RtpDataLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
// TODO(b/172331505) Allow for retry when loading is not ending.
if (getBufferedPositionUs() == 0) {
if (!isUsingRtpTcp) {
// 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.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);
}
}
if (isSeekPending()) {
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;
}
}