From ff7dcbd6f2d6a6e0cb3386fae303854acb97e224 Mon Sep 17 00:00:00 2001 From: claincly Date: Mon, 13 Sep 2021 09:34:57 +0100 Subject: [PATCH] Handle RTSP 301/302 redirection. PiperOrigin-RevId: 396303242 --- .../exoplayer2/source/rtsp/RtspClient.java | 34 +++++++++---- .../exoplayer2/source/rtsp/RtspHeaders.java | 3 ++ .../source/rtsp/RtspMessageUtil.java | 4 ++ .../source/rtsp/RtspClientTest.java | 50 +++++++++++++++++++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java index a800336531..665a387466 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java @@ -101,8 +101,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final SessionInfoListener sessionInfoListener; private final PlaybackEventListener playbackEventListener; - private final Uri uri; - @Nullable private final RtspAuthUserInfo rtspAuthUserInfo; private final String userAgent; private final boolean debugLoggingEnabled; private final ArrayDeque pendingSetupRtpLoadInfos; @@ -110,7 +108,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final SparseArray pendingRequests; private final MessageSender messageSender; + /** RTSP session URI. */ + private Uri uri; + private RtspMessageChannel messageChannel; + @Nullable private RtspAuthUserInfo rtspAuthUserInfo; @Nullable private String sessionId; @Nullable private KeepAliveMonitor keepAliveMonitor; @Nullable private RtspAuthenticationInfo rtspAuthenticationInfo; @@ -140,15 +142,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; boolean debugLoggingEnabled) { this.sessionInfoListener = sessionInfoListener; this.playbackEventListener = playbackEventListener; - this.uri = RtspMessageUtil.removeUserInfo(uri); - this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri); this.userAgent = userAgent; this.debugLoggingEnabled = debugLoggingEnabled; - pendingSetupRtpLoadInfos = new ArrayDeque<>(); - pendingRequests = new SparseArray<>(); - messageSender = new MessageSender(); - pendingSeekPositionUs = C.TIME_UNSET; - messageChannel = new RtspMessageChannel(new MessageListener()); + this.pendingSetupRtpLoadInfos = new ArrayDeque<>(); + this.pendingRequests = new SparseArray<>(); + this.messageSender = new MessageSender(); + this.uri = RtspMessageUtil.removeUserInfo(uri); + this.messageChannel = new RtspMessageChannel(new MessageListener()); + this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri); + this.pendingSeekPositionUs = C.TIME_UNSET; } /** @@ -482,6 +484,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; switch (response.status) { case 200: break; + case 301: + case 302: + // Redirection request. + @Nullable String redirectionUriString = response.headers.get(RtspHeaders.LOCATION); + if (redirectionUriString == null) { + sessionInfoListener.onSessionTimelineRequestFailed( + "Redirection without new location.", /* cause= */ null); + } else { + Uri redirectionUri = Uri.parse(redirectionUriString); + RtspClient.this.uri = RtspMessageUtil.removeUserInfo(redirectionUri); + RtspClient.this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(redirectionUri); + messageSender.sendDescribeRequest(RtspClient.this.uri, RtspClient.this.sessionId); + } + return; case 401: if (rtspAuthUserInfo != null && !receivedAuthorizationRequest) { // Unauthorized. diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java index be228a414f..29fa0affb4 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java @@ -50,6 +50,7 @@ import java.util.Map; public static final String CSEQ = "CSeq"; public static final String DATE = "Date"; public static final String EXPIRES = "Expires"; + public static final String LOCATION = "Location"; public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; public static final String PROXY_REQUIRE = "Proxy-Require"; public static final String PUBLIC = "Public"; @@ -251,6 +252,8 @@ import java.util.Map; return DATE; } else if (Ascii.equalsIgnoreCase(messageHeaderName, EXPIRES)) { return EXPIRES; + } else if (Ascii.equalsIgnoreCase(messageHeaderName, LOCATION)) { + return LOCATION; } else if (Ascii.equalsIgnoreCase(messageHeaderName, PROXY_AUTHENTICATE)) { return PROXY_AUTHENTICATE; } else if (Ascii.equalsIgnoreCase(messageHeaderName, PROXY_REQUIRE)) { diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java index ece8b77653..4e8b7a08d0 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtil.java @@ -462,6 +462,10 @@ import java.util.regex.Pattern; switch (statusCode) { case 200: return "OK"; + case 301: + return "Move Permanently"; + case 302: + return "Move Temporarily"; case 400: return "Bad Request"; case 401: diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java index 15237d1003..2ab0154b63 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java @@ -120,6 +120,56 @@ public final class RtspClientTest { assertThat(tracksInSession.get()).hasSize(2); } + @Test + public void connectServerAndClient_describeRedirects_updatesSessionTimeline() throws Exception { + class ResponseProvider implements RtspServer.ResponseProvider { + @Override + public RtspResponse getOptionsResponse() { + return new RtspResponse(/* status= */ 200, RtspHeaders.EMPTY); + } + + @Override + public RtspResponse getDescribeResponse(Uri requestedUri) { + if (!requestedUri.getPath().contains("redirect")) { + return new RtspResponse( + 301, + new RtspHeaders.Builder() + .add( + RtspHeaders.LOCATION, + requestedUri.buildUpon().appendEncodedPath("redirect").build().toString()) + .build()); + } + + return RtspTestUtils.newDescribeResponseWithSdpMessage( + SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); + } + } + rtspServer = new RtspServer(new ResponseProvider()); + + AtomicReference> tracksInSession = new AtomicReference<>(); + rtspClient = + new RtspClient( + new SessionInfoListener() { + @Override + public void onSessionTimelineUpdated( + RtspSessionTiming timing, ImmutableList tracks) { + tracksInSession.set(tracks); + } + + @Override + public void onSessionTimelineRequestFailed( + String message, @Nullable Throwable cause) {} + }, + EMPTY_PLAYBACK_LISTENER, + /* userAgent= */ "ExoPlayer:RtspClientTest", + RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + /* debugLoggingEnabled= */ false); + rtspClient.start(); + RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null); + + assertThat(tracksInSession.get()).hasSize(2); + } + @Test public void connectServerAndClient_serverSupportsDescribeNoHeaderInOptions_updatesSessionTimeline()