mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Add RTSP state machine.
Please reference RFC2326 Section A.1 for the state transitions. PiperOrigin-RevId: 396799104
This commit is contained in:
parent
416ec75b94
commit
5f0395ee2d
2 changed files with 56 additions and 0 deletions
|
|
@ -40,6 +40,7 @@ import android.net.Uri;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
|
|
@ -57,6 +58,9 @@ import com.google.common.collect.Iterables;
|
|||
import com.google.common.collect.Multimap;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -68,6 +72,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
/** The RTSP client. */
|
||||
/* package */ final class RtspClient implements Closeable {
|
||||
|
||||
/**
|
||||
* The RTSP session state (RFC2326, Section A.1). One of {@link #RTSP_STATE_UNINITIALIZED}, {@link
|
||||
* #RTSP_STATE_INIT}, {@link #RTSP_STATE_READY}, or {@link #RTSP_STATE_PLAYING}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({RTSP_STATE_UNINITIALIZED, RTSP_STATE_INIT, RTSP_STATE_READY, RTSP_STATE_PLAYING})
|
||||
public @interface RtspState {}
|
||||
/** RTSP uninitialized state, the state before sending any SETUP request. */
|
||||
public static final int RTSP_STATE_UNINITIALIZED = -1;
|
||||
/** RTSP initial state, the state after sending SETUP REQUEST. */
|
||||
public static final int RTSP_STATE_INIT = 0;
|
||||
/** RTSP ready state, the state after receiving SETUP, or PAUSE response. */
|
||||
public static final int RTSP_STATE_READY = 1;
|
||||
/** RTSP playing state, the state after receiving PLAY response. */
|
||||
public static final int RTSP_STATE_PLAYING = 2;
|
||||
|
||||
private static final String TAG = "RtspClient";
|
||||
private static final long DEFAULT_RTSP_KEEP_ALIVE_INTERVAL_MS = 30_000;
|
||||
|
||||
|
|
@ -116,6 +137,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
@Nullable private String sessionId;
|
||||
@Nullable private KeepAliveMonitor keepAliveMonitor;
|
||||
@Nullable private RtspAuthenticationInfo rtspAuthenticationInfo;
|
||||
@RtspState private int rtspState;
|
||||
private boolean hasUpdatedTimelineAndTracks;
|
||||
private boolean receivedAuthorizationRequest;
|
||||
private long pendingSeekPositionUs;
|
||||
|
|
@ -151,6 +173,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
this.messageChannel = new RtspMessageChannel(new MessageListener());
|
||||
this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri);
|
||||
this.pendingSeekPositionUs = C.TIME_UNSET;
|
||||
this.rtspState = RTSP_STATE_UNINITIALIZED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -171,6 +194,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
messageSender.sendOptionsRequest(uri, sessionId);
|
||||
}
|
||||
|
||||
/** Returns the current {@link RtspState RTSP state}. */
|
||||
@RtspState
|
||||
public int getState() {
|
||||
return rtspState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers RTSP SETUP requests after track selection.
|
||||
*
|
||||
|
|
@ -327,6 +356,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
}
|
||||
|
||||
public void sendSetupRequest(Uri trackUri, String transport, @Nullable String sessionId) {
|
||||
rtspState = RTSP_STATE_INIT;
|
||||
sendRequest(
|
||||
getRequestWithCommonHeaders(
|
||||
METHOD_SETUP,
|
||||
|
|
@ -336,6 +366,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
}
|
||||
|
||||
public void sendPlayRequest(Uri uri, long offsetMs, String sessionId) {
|
||||
checkState(rtspState == RTSP_STATE_READY || rtspState == RTSP_STATE_PLAYING);
|
||||
sendRequest(
|
||||
getRequestWithCommonHeaders(
|
||||
METHOD_PLAY,
|
||||
|
|
@ -346,12 +377,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
}
|
||||
|
||||
public void sendTeardownRequest(Uri uri, String sessionId) {
|
||||
if (rtspState == RTSP_STATE_UNINITIALIZED || rtspState == RTSP_STATE_INIT) {
|
||||
// No need to perform session teardown before a session is set up, where the state is
|
||||
// RTSP_STATE_READY or RTSP_STATE_PLAYING.
|
||||
return;
|
||||
}
|
||||
|
||||
rtspState = RTSP_STATE_INIT;
|
||||
sendRequest(
|
||||
getRequestWithCommonHeaders(
|
||||
METHOD_TEARDOWN, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
|
||||
}
|
||||
|
||||
public void sendPauseRequest(Uri uri, String sessionId) {
|
||||
checkState(rtspState == RTSP_STATE_PLAYING);
|
||||
sendRequest(
|
||||
getRequestWithCommonHeaders(
|
||||
METHOD_PAUSE, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
|
||||
|
|
@ -487,6 +526,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
case 301:
|
||||
case 302:
|
||||
// Redirection request.
|
||||
if (rtspState != RTSP_STATE_UNINITIALIZED) {
|
||||
rtspState = RTSP_STATE_INIT;
|
||||
}
|
||||
@Nullable String redirectionUriString = response.headers.get(RtspHeaders.LOCATION);
|
||||
if (redirectionUriString == null) {
|
||||
sessionInfoListener.onSessionTimelineRequestFailed(
|
||||
|
|
@ -627,11 +669,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
}
|
||||
|
||||
private void onSetupResponseReceived(RtspSetupResponse response) {
|
||||
checkState(rtspState != RTSP_STATE_UNINITIALIZED);
|
||||
|
||||
rtspState = RTSP_STATE_READY;
|
||||
sessionId = response.sessionHeader.sessionId;
|
||||
continueSetupRtspTrack();
|
||||
}
|
||||
|
||||
private void onPlayResponseReceived(RtspPlayResponse response) {
|
||||
checkState(rtspState == RTSP_STATE_READY);
|
||||
|
||||
rtspState = RTSP_STATE_PLAYING;
|
||||
if (keepAliveMonitor == null) {
|
||||
keepAliveMonitor = new KeepAliveMonitor(DEFAULT_RTSP_KEEP_ALIVE_INTERVAL_MS);
|
||||
keepAliveMonitor.start();
|
||||
|
|
@ -643,6 +691,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
}
|
||||
|
||||
private void onPauseResponseReceived() {
|
||||
checkState(rtspState == RTSP_STATE_PLAYING);
|
||||
|
||||
rtspState = RTSP_STATE_READY;
|
||||
if (pendingSeekPositionUs != C.TIME_UNSET) {
|
||||
startPlayback(C.usToMs(pendingSeekPositionUs));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ public final class RtspClientTest {
|
|||
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
||||
|
||||
assertThat(tracksInSession.get()).hasSize(2);
|
||||
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -168,6 +169,7 @@ public final class RtspClientTest {
|
|||
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
||||
|
||||
assertThat(tracksInSession.get()).hasSize(2);
|
||||
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -210,6 +212,7 @@ public final class RtspClientTest {
|
|||
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
||||
|
||||
assertThat(tracksInSession.get()).hasSize(2);
|
||||
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -256,6 +259,7 @@ public final class RtspClientTest {
|
|||
|
||||
assertThat(failureMessage.get()).contains("DESCRIBE not supported.");
|
||||
assertThat(clientHasSentDescribeRequest.get()).isFalse();
|
||||
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -300,5 +304,6 @@ public final class RtspClientTest {
|
|||
|
||||
RobolectricUtil.runMainLooperUntil(() -> failureCause.get() != null);
|
||||
assertThat(failureCause.get()).hasCauseThat().isInstanceOf(ParserException.class);
|
||||
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue