mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
|
|
@ -57,6 +58,9 @@ import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
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.net.Socket;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -68,6 +72,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
/** The RTSP client. */
|
/** The RTSP client. */
|
||||||
/* package */ final class RtspClient implements Closeable {
|
/* 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 String TAG = "RtspClient";
|
||||||
private static final long DEFAULT_RTSP_KEEP_ALIVE_INTERVAL_MS = 30_000;
|
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 String sessionId;
|
||||||
@Nullable private KeepAliveMonitor keepAliveMonitor;
|
@Nullable private KeepAliveMonitor keepAliveMonitor;
|
||||||
@Nullable private RtspAuthenticationInfo rtspAuthenticationInfo;
|
@Nullable private RtspAuthenticationInfo rtspAuthenticationInfo;
|
||||||
|
@RtspState private int rtspState;
|
||||||
private boolean hasUpdatedTimelineAndTracks;
|
private boolean hasUpdatedTimelineAndTracks;
|
||||||
private boolean receivedAuthorizationRequest;
|
private boolean receivedAuthorizationRequest;
|
||||||
private long pendingSeekPositionUs;
|
private long pendingSeekPositionUs;
|
||||||
|
|
@ -151,6 +173,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
this.messageChannel = new RtspMessageChannel(new MessageListener());
|
this.messageChannel = new RtspMessageChannel(new MessageListener());
|
||||||
this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri);
|
this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri);
|
||||||
this.pendingSeekPositionUs = C.TIME_UNSET;
|
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);
|
messageSender.sendOptionsRequest(uri, sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the current {@link RtspState RTSP state}. */
|
||||||
|
@RtspState
|
||||||
|
public int getState() {
|
||||||
|
return rtspState;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers RTSP SETUP requests after track selection.
|
* 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) {
|
public void sendSetupRequest(Uri trackUri, String transport, @Nullable String sessionId) {
|
||||||
|
rtspState = RTSP_STATE_INIT;
|
||||||
sendRequest(
|
sendRequest(
|
||||||
getRequestWithCommonHeaders(
|
getRequestWithCommonHeaders(
|
||||||
METHOD_SETUP,
|
METHOD_SETUP,
|
||||||
|
|
@ -336,6 +366,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPlayRequest(Uri uri, long offsetMs, String sessionId) {
|
public void sendPlayRequest(Uri uri, long offsetMs, String sessionId) {
|
||||||
|
checkState(rtspState == RTSP_STATE_READY || rtspState == RTSP_STATE_PLAYING);
|
||||||
sendRequest(
|
sendRequest(
|
||||||
getRequestWithCommonHeaders(
|
getRequestWithCommonHeaders(
|
||||||
METHOD_PLAY,
|
METHOD_PLAY,
|
||||||
|
|
@ -346,12 +377,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendTeardownRequest(Uri uri, String sessionId) {
|
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(
|
sendRequest(
|
||||||
getRequestWithCommonHeaders(
|
getRequestWithCommonHeaders(
|
||||||
METHOD_TEARDOWN, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
|
METHOD_TEARDOWN, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPauseRequest(Uri uri, String sessionId) {
|
public void sendPauseRequest(Uri uri, String sessionId) {
|
||||||
|
checkState(rtspState == RTSP_STATE_PLAYING);
|
||||||
sendRequest(
|
sendRequest(
|
||||||
getRequestWithCommonHeaders(
|
getRequestWithCommonHeaders(
|
||||||
METHOD_PAUSE, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
|
METHOD_PAUSE, sessionId, /* additionalHeaders= */ ImmutableMap.of(), uri));
|
||||||
|
|
@ -487,6 +526,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
case 301:
|
case 301:
|
||||||
case 302:
|
case 302:
|
||||||
// Redirection request.
|
// Redirection request.
|
||||||
|
if (rtspState != RTSP_STATE_UNINITIALIZED) {
|
||||||
|
rtspState = RTSP_STATE_INIT;
|
||||||
|
}
|
||||||
@Nullable String redirectionUriString = response.headers.get(RtspHeaders.LOCATION);
|
@Nullable String redirectionUriString = response.headers.get(RtspHeaders.LOCATION);
|
||||||
if (redirectionUriString == null) {
|
if (redirectionUriString == null) {
|
||||||
sessionInfoListener.onSessionTimelineRequestFailed(
|
sessionInfoListener.onSessionTimelineRequestFailed(
|
||||||
|
|
@ -627,11 +669,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSetupResponseReceived(RtspSetupResponse response) {
|
private void onSetupResponseReceived(RtspSetupResponse response) {
|
||||||
|
checkState(rtspState != RTSP_STATE_UNINITIALIZED);
|
||||||
|
|
||||||
|
rtspState = RTSP_STATE_READY;
|
||||||
sessionId = response.sessionHeader.sessionId;
|
sessionId = response.sessionHeader.sessionId;
|
||||||
continueSetupRtspTrack();
|
continueSetupRtspTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPlayResponseReceived(RtspPlayResponse response) {
|
private void onPlayResponseReceived(RtspPlayResponse response) {
|
||||||
|
checkState(rtspState == RTSP_STATE_READY);
|
||||||
|
|
||||||
|
rtspState = RTSP_STATE_PLAYING;
|
||||||
if (keepAliveMonitor == null) {
|
if (keepAliveMonitor == null) {
|
||||||
keepAliveMonitor = new KeepAliveMonitor(DEFAULT_RTSP_KEEP_ALIVE_INTERVAL_MS);
|
keepAliveMonitor = new KeepAliveMonitor(DEFAULT_RTSP_KEEP_ALIVE_INTERVAL_MS);
|
||||||
keepAliveMonitor.start();
|
keepAliveMonitor.start();
|
||||||
|
|
@ -643,6 +691,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPauseResponseReceived() {
|
private void onPauseResponseReceived() {
|
||||||
|
checkState(rtspState == RTSP_STATE_PLAYING);
|
||||||
|
|
||||||
|
rtspState = RTSP_STATE_READY;
|
||||||
if (pendingSeekPositionUs != C.TIME_UNSET) {
|
if (pendingSeekPositionUs != C.TIME_UNSET) {
|
||||||
startPlayback(C.usToMs(pendingSeekPositionUs));
|
startPlayback(C.usToMs(pendingSeekPositionUs));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ public final class RtspClientTest {
|
||||||
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
||||||
|
|
||||||
assertThat(tracksInSession.get()).hasSize(2);
|
assertThat(tracksInSession.get()).hasSize(2);
|
||||||
|
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -168,6 +169,7 @@ public final class RtspClientTest {
|
||||||
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
||||||
|
|
||||||
assertThat(tracksInSession.get()).hasSize(2);
|
assertThat(tracksInSession.get()).hasSize(2);
|
||||||
|
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -210,6 +212,7 @@ public final class RtspClientTest {
|
||||||
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
|
||||||
|
|
||||||
assertThat(tracksInSession.get()).hasSize(2);
|
assertThat(tracksInSession.get()).hasSize(2);
|
||||||
|
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -256,6 +259,7 @@ public final class RtspClientTest {
|
||||||
|
|
||||||
assertThat(failureMessage.get()).contains("DESCRIBE not supported.");
|
assertThat(failureMessage.get()).contains("DESCRIBE not supported.");
|
||||||
assertThat(clientHasSentDescribeRequest.get()).isFalse();
|
assertThat(clientHasSentDescribeRequest.get()).isFalse();
|
||||||
|
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -300,5 +304,6 @@ public final class RtspClientTest {
|
||||||
|
|
||||||
RobolectricUtil.runMainLooperUntil(() -> failureCause.get() != null);
|
RobolectricUtil.runMainLooperUntil(() -> failureCause.get() != null);
|
||||||
assertThat(failureCause.get()).hasCauseThat().isInstanceOf(ParserException.class);
|
assertThat(failureCause.get()).hasCauseThat().isInstanceOf(ParserException.class);
|
||||||
|
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue