Commit bbd45c8e by michaelkatz Committed by Tianyi Feng

Retry RTSP Setup with TCP if response with UDP is UnsupportedTransport

If RTSP Setup Request with UDP receives HTTP Error Status 461 UnsupportedTransport, then client will retry with TCP.

Issue: google/ExoPlayer#11069
PiperOrigin-RevId: 518807829
parent d1d85cb0
...@@ -47,6 +47,7 @@ import com.google.android.exoplayer2.C; ...@@ -47,6 +47,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.rtsp.RtspMediaPeriod.RtpLoadInfo; import com.google.android.exoplayer2.source.rtsp.RtspMediaPeriod.RtpLoadInfo;
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException; import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException;
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspUdpUnsupportedTransportException;
import com.google.android.exoplayer2.source.rtsp.RtspMessageChannel.InterleavedBinaryDataListener; import com.google.android.exoplayer2.source.rtsp.RtspMessageChannel.InterleavedBinaryDataListener;
import com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.RtspAuthUserInfo; import com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.RtspAuthUserInfo;
import com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.RtspSessionHeader; import com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.RtspSessionHeader;
...@@ -577,8 +578,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -577,8 +578,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
receivedAuthorizationRequest = true; receivedAuthorizationRequest = true;
return; return;
} }
// fall through: if unauthorized and no userInfo present, or previous authentication // if unauthorized and no userInfo present, or previous authentication
// unsuccessful. // unsuccessful, then dispatch RtspPlaybackException
dispatchRtspError(
new RtspPlaybackException(
RtspMessageUtil.toMethodString(requestMethod) + " " + response.status));
return;
case 461:
String exceptionMessage =
RtspMessageUtil.toMethodString(requestMethod) + " " + response.status;
// If request was SETUP with UDP transport protocol, then throw
// RtspUdpUnsupportedTransportException.
String transportHeaderValue =
checkNotNull(matchingRequest.headers.get(RtspHeaders.TRANSPORT));
dispatchRtspError(
requestMethod == METHOD_SETUP && !transportHeaderValue.contains("TCP")
? new RtspUdpUnsupportedTransportException(exceptionMessage)
: new RtspPlaybackException(exceptionMessage));
return;
default: default:
dispatchRtspError( dispatchRtspError(
new RtspPlaybackException( new RtspPlaybackException(
......
...@@ -516,7 +516,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -516,7 +516,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// using TCP. Retrying will setup new loadables, so will not retry with the current // using TCP. Retrying will setup new loadables, so will not retry with the current
// loadables. // loadables.
retryWithRtpTcp(); retryWithRtpTcp();
isUsingRtpTcp = true;
} }
return; return;
} }
...@@ -642,7 +641,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -642,7 +641,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void onPlaybackError(RtspPlaybackException error) { public void onPlaybackError(RtspPlaybackException error) {
playbackException = error; if (error instanceof RtspMediaSource.RtspUdpUnsupportedTransportException && !isUsingRtpTcp) {
// Retry playback with TCP if we receive RtspUdpUnsupportedTransportException, and we are
// not already using TCP. Retrying will setup new loadables.
retryWithRtpTcp();
} else {
playbackException = error;
}
} }
@Override @Override
...@@ -666,6 +671,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -666,6 +671,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void retryWithRtpTcp() { private void retryWithRtpTcp() {
// Retry should only run once.
isUsingRtpTcp = true;
rtspClient.retryWithRtpTcp(); rtspClient.retryWithRtpTcp();
@Nullable @Nullable
......
...@@ -190,7 +190,7 @@ public final class RtspMediaSource extends BaseMediaSource { ...@@ -190,7 +190,7 @@ public final class RtspMediaSource extends BaseMediaSource {
} }
/** Thrown when an exception or error is encountered during loading an RTSP stream. */ /** Thrown when an exception or error is encountered during loading an RTSP stream. */
public static final class RtspPlaybackException extends IOException { public static class RtspPlaybackException extends IOException {
public RtspPlaybackException(String message) { public RtspPlaybackException(String message) {
super(message); super(message);
} }
...@@ -204,6 +204,13 @@ public final class RtspMediaSource extends BaseMediaSource { ...@@ -204,6 +204,13 @@ public final class RtspMediaSource extends BaseMediaSource {
} }
} }
/** Thrown when an RTSP Unsupported Transport error (461) is encountered during RTSP Setup. */
public static final class RtspUdpUnsupportedTransportException extends RtspPlaybackException {
public RtspUdpUnsupportedTransportException(String message) {
super(message);
}
}
private final MediaItem mediaItem; private final MediaItem mediaItem;
private final RtpDataChannel.Factory rtpDataChannelFactory; private final RtpDataChannel.Factory rtpDataChannelFactory;
private final String userAgent; private final String userAgent;
......
...@@ -453,4 +453,77 @@ public final class RtspClientTest { ...@@ -453,4 +453,77 @@ public final class RtspClientTest {
RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get); RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get);
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED); assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
} }
@Test
public void connectServerAndClient_describeResponseRequiresAuthentication_doesNotUpdateTimeline()
throws Exception {
class ResponseProvider implements RtspServer.ResponseProvider {
@Override
public RtspResponse getOptionsResponse() {
return new RtspResponse(
/* status= */ 200,
new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE").build());
}
@Override
public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
String authorizationHeader = headers.get(RtspHeaders.AUTHORIZATION);
if (authorizationHeader == null) {
return new RtspResponse(
/* status= */ 401,
new RtspHeaders.Builder()
.add(RtspHeaders.CSEQ, headers.get(RtspHeaders.CSEQ))
.add(
RtspHeaders.WWW_AUTHENTICATE,
"Digest realm=\"RTSP server\","
+ " nonce=\"0cdfe9719e7373b7d5bb2913e2115f3f\","
+ " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")
.add(RtspHeaders.WWW_AUTHENTICATE, "BASIC realm=\"WallyWorld\"")
.build());
}
if (!authorizationHeader.contains("Digest")) {
return new RtspResponse(
401,
new RtspHeaders.Builder()
.add(RtspHeaders.CSEQ, headers.get(RtspHeaders.CSEQ))
.build());
}
return RtspTestUtils.newDescribeResponseWithSdpMessage(
"v=0\r\n"
+ "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
+ "s=Exoplayer test\r\n"
+ "t=0 0\r\n"
// The session is 50.46s long.
+ "a=range:npt=0-50.46\r\n",
rtpPacketStreamDumps,
requestedUri);
}
}
rtspServer = new RtspServer(new ResponseProvider());
AtomicBoolean timelineRequestFailed = new AtomicBoolean();
rtspClient =
new RtspClient(
new SessionInfoListener() {
@Override
public void onSessionTimelineUpdated(
RtspSessionTiming timing, ImmutableList<RtspMediaTrack> tracks) {}
@Override
public void onSessionTimelineRequestFailed(
String message, @Nullable Throwable cause) {
timelineRequestFailed.set(true);
}
},
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();
RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get);
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
}
} }
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.rtsp; package com.google.android.exoplayer2.source.rtsp;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.lang.Math.min; import static java.lang.Math.min;
...@@ -42,11 +43,13 @@ import com.google.android.exoplayer2.upstream.DataSpec; ...@@ -42,11 +43,13 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
...@@ -58,30 +61,20 @@ import org.robolectric.annotation.Config; ...@@ -58,30 +61,20 @@ import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class RtspPlaybackTest { public final class RtspPlaybackTest {
private static final long DEFAULT_TIMEOUT_MS = 8000;
private static final String SESSION_DESCRIPTION = private static final String SESSION_DESCRIPTION =
"v=0\r\n" "v=0\r\n"
+ "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n" + "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
+ "s=Exoplayer test\r\n" + "s=Exoplayer test\r\n"
+ "t=0 0\r\n"; + "t=0 0\r\n";
private final Context applicationContext; private Context applicationContext;
private final CapturingRenderersFactory capturingRenderersFactory; private CapturingRenderersFactory capturingRenderersFactory;
private final Clock clock; private Clock clock;
private final FakeUdpDataSourceRtpDataChannel fakeRtpDataChannel;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private RtpPacketStreamDump aacRtpPacketStreamDump; private RtpPacketStreamDump aacRtpPacketStreamDump;
// ExoPlayer does not support extracting MP4A-LATM RTP payload at the moment. // ExoPlayer does not support extracting MP4A-LATM RTP payload at the moment.
private RtpPacketStreamDump mpeg2tsRtpPacketStreamDump; private RtpPacketStreamDump mpeg2tsRtpPacketStreamDump;
private RtspServer rtspServer;
/** Creates a new instance. */
public RtspPlaybackTest() {
applicationContext = ApplicationProvider.getApplicationContext();
capturingRenderersFactory = new CapturingRenderersFactory(applicationContext);
clock = new FakeClock(/* isAutoAdvancing= */ true);
fakeRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
rtpDataChannelFactory = (trackId) -> fakeRtpDataChannel;
}
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
...@@ -89,61 +82,162 @@ public final class RtspPlaybackTest { ...@@ -89,61 +82,162 @@ public final class RtspPlaybackTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
applicationContext = ApplicationProvider.getApplicationContext();
capturingRenderersFactory = new CapturingRenderersFactory(applicationContext);
clock = new FakeClock(/* isAutoAdvancing= */ true);
aacRtpPacketStreamDump = RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json"); aacRtpPacketStreamDump = RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json");
mpeg2tsRtpPacketStreamDump = mpeg2tsRtpPacketStreamDump =
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/mpeg2ts-dump.json"); RtspTestUtils.readRtpPacketStreamDump("media/rtsp/mpeg2ts-dump.json");
} }
@After
public void tearDown() {
Util.closeQuietly(rtspServer);
}
@Test @Test
public void prepare_withSupportedTrack_playsTrackUntilEnded() throws Exception { public void prepare_withSupportedTrack_playsTrackUntilEnded() throws Exception {
FakeUdpDataSourceRtpDataChannel fakeRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
RtpDataChannel.Factory rtpDataChannelFactory = (trackId) -> fakeRtpDataChannel;
ResponseProvider responseProvider = ResponseProvider responseProvider =
new ResponseProvider( new ResponseProvider(
clock, clock,
ImmutableList.of(aacRtpPacketStreamDump, mpeg2tsRtpPacketStreamDump), ImmutableList.of(aacRtpPacketStreamDump, mpeg2tsRtpPacketStreamDump),
fakeRtpDataChannel); fakeRtpDataChannel);
rtspServer = new RtspServer(responseProvider);
try (RtspServer rtspServer = new RtspServer(responseProvider)) { ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); player.prepare();
player.prepare(); player.play();
player.play(); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); player.release();
player.release();
// Only setup the supported track (aac).
// Only setup the supported track (aac). assertThat(responseProvider.getDumpsForSetUpTracks()).containsExactly(aacRtpPacketStreamDump);
assertThat(responseProvider.getDumpsForSetUpTracks()).containsExactly(aacRtpPacketStreamDump); DumpFileAsserts.assertOutput(applicationContext, playbackOutput, "playbackdumps/rtsp/aac.dump");
DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/rtsp/aac.dump");
}
} }
@Test @Test
public void prepare_noSupportedTrack_throwsPreparationError() throws Exception { public void prepare_noSupportedTrack_throwsPreparationError() throws Exception {
FakeUdpDataSourceRtpDataChannel fakeRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
try (RtspServer rtspServer = RtpDataChannel.Factory rtpDataChannelFactory = (trackId) -> fakeRtpDataChannel;
rtspServer =
new RtspServer( new RtspServer(
new ResponseProvider( new ResponseProvider(
clock, ImmutableList.of(mpeg2tsRtpPacketStreamDump), fakeRtpDataChannel))) { clock, ImmutableList.of(mpeg2tsRtpPacketStreamDump), fakeRtpDataChannel));
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory); ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
AtomicReference<Throwable> playbackError = new AtomicReference<>(); AtomicReference<Throwable> playbackError = new AtomicReference<>();
player.prepare(); player.prepare();
player.addListener( player.addListener(
new Listener() { new Listener() {
@Override @Override
public void onPlayerError(PlaybackException error) { public void onPlayerError(PlaybackException error) {
playbackError.set(error); playbackError.set(error);
} }
}); });
RobolectricUtil.runMainLooperUntil(() -> playbackError.get() != null); RobolectricUtil.runMainLooperUntil(() -> playbackError.get() != null);
player.release(); player.release();
assertThat(playbackError.get()) assertThat(playbackError.get()).hasCauseThat().hasMessageThat().contains("No playable track.");
.hasCauseThat() }
.hasMessageThat()
.contains("No playable track."); @Test
} public void prepare_withUdpUnsupportedWithFallback_fallsbackToTcpAndPlaysUntilEnd()
throws Exception {
FakeTcpDataSourceRtpDataChannel fakeTcpRtpDataChannel = new FakeTcpDataSourceRtpDataChannel();
RtpDataChannel.Factory rtpTcpDataChannelFactory = (trackId) -> fakeTcpRtpDataChannel;
ResponseProviderSupportingOnlyTcp responseProviderSupportingOnlyTcp =
new ResponseProviderSupportingOnlyTcp(
clock,
ImmutableList.of(aacRtpPacketStreamDump, mpeg2tsRtpPacketStreamDump),
fakeTcpRtpDataChannel);
ForwardingRtpDataChannelFactory forwardingRtpDataChannelFactory =
new ForwardingRtpDataChannelFactory(
new UdpDataSourceRtpDataChannelFactory(DEFAULT_TIMEOUT_MS), rtpTcpDataChannelFactory);
rtspServer = new RtspServer(responseProviderSupportingOnlyTcp);
ExoPlayer player =
createExoPlayer(rtspServer.startAndGetPortNumber(), forwardingRtpDataChannelFactory);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
// Only setup the supported track (aac).
assertThat(responseProviderSupportingOnlyTcp.getDumpsForSetUpTracks())
.containsExactly(aacRtpPacketStreamDump);
DumpFileAsserts.assertOutput(applicationContext, playbackOutput, "playbackdumps/rtsp/aac.dump");
}
@Test
public void prepare_withUdpUnsupportedWithoutFallback_throwsRtspPlaybackException()
throws Exception {
FakeUdpDataSourceRtpDataChannel fakeUdpRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
RtpDataChannel.Factory rtpDataChannelFactory = (trackId) -> fakeUdpRtpDataChannel;
ResponseProviderSupportingOnlyTcp responseProvider =
new ResponseProviderSupportingOnlyTcp(
clock,
ImmutableList.of(aacRtpPacketStreamDump, mpeg2tsRtpPacketStreamDump),
fakeUdpRtpDataChannel);
rtspServer = new RtspServer(responseProvider);
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
AtomicReference<PlaybackException> playbackError = new AtomicReference<>();
player.prepare();
player.addListener(
new Listener() {
@Override
public void onPlayerError(PlaybackException error) {
playbackError.set(error);
}
});
RobolectricUtil.runMainLooperUntil(() -> playbackError.get() != null);
player.release();
assertThat(playbackError.get())
.hasCauseThat()
.isInstanceOf(RtspMediaSource.RtspPlaybackException.class);
assertThat(playbackError.get())
.hasCauseThat()
.hasMessageThat()
.contains("No fallback data channel factory for TCP retry");
}
@Test
public void prepare_withUdpUnsupportedWithUdpFallback_throwsRtspUdpUnsupportedTransportException()
throws Exception {
FakeUdpDataSourceRtpDataChannel fakeUdpRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
RtpDataChannel.Factory rtpDataChannelFactory = (trackId) -> fakeUdpRtpDataChannel;
ResponseProviderSupportingOnlyTcp responseProviderSupportingOnlyTcp =
new ResponseProviderSupportingOnlyTcp(
clock,
ImmutableList.of(aacRtpPacketStreamDump, mpeg2tsRtpPacketStreamDump),
fakeUdpRtpDataChannel);
ForwardingRtpDataChannelFactory forwardingRtpDataChannelFactory =
new ForwardingRtpDataChannelFactory(rtpDataChannelFactory, rtpDataChannelFactory);
rtspServer = new RtspServer(responseProviderSupportingOnlyTcp);
ExoPlayer player =
createExoPlayer(rtspServer.startAndGetPortNumber(), forwardingRtpDataChannelFactory);
AtomicReference<PlaybackException> playbackError = new AtomicReference<>();
player.prepare();
player.addListener(
new Listener() {
@Override
public void onPlayerError(PlaybackException error) {
playbackError.set(error);
}
});
RobolectricUtil.runMainLooperUntil(() -> playbackError.get() != null);
player.release();
assertThat(playbackError.get())
.hasCauseThat()
.isInstanceOf(RtspMediaSource.RtspUdpUnsupportedTransportException.class);
assertThat(playbackError.get()).hasCauseThat().hasMessageThat().isEqualTo("SETUP 461");
} }
private ExoPlayer createExoPlayer( private ExoPlayer createExoPlayer(
...@@ -163,16 +257,16 @@ public final class RtspPlaybackTest { ...@@ -163,16 +257,16 @@ public final class RtspPlaybackTest {
return player; return player;
} }
private static final class ResponseProvider implements RtspServer.ResponseProvider { private static class ResponseProvider implements RtspServer.ResponseProvider {
private static final String SESSION_ID = "00000000"; protected static final String SESSION_ID = "00000000";
private final Clock clock; protected final Clock clock;
private final ArrayList<RtpPacketStreamDump> dumpsForSetUpTracks; protected final ArrayList<RtpPacketStreamDump> dumpsForSetUpTracks;
private final ImmutableList<RtpPacketStreamDump> rtpPacketStreamDumps; protected final ImmutableList<RtpPacketStreamDump> rtpPacketStreamDumps;
private final RtspMessageChannel.InterleavedBinaryDataListener binaryDataListener; private final RtspMessageChannel.InterleavedBinaryDataListener binaryDataListener;
private RtpPacketTransmitter packetTransmitter; protected RtpPacketTransmitter packetTransmitter;
/** /**
* Creates a new instance. * Creates a new instance.
...@@ -240,22 +334,54 @@ public final class RtspPlaybackTest { ...@@ -240,22 +334,54 @@ public final class RtspPlaybackTest {
} }
} }
private static final class FakeUdpDataSourceRtpDataChannel extends BaseDataSource private static final class ResponseProviderSupportingOnlyTcp extends ResponseProvider {
implements RtpDataChannel, RtspMessageChannel.InterleavedBinaryDataListener {
private static final int LOCAL_PORT = 40000; /**
* Creates a new instance.
*
* @param clock The {@link Clock} used in the test.
* @param rtpPacketStreamDumps A list of {@link RtpPacketStreamDump}.
* @param binaryDataListener A {@link RtspMessageChannel.InterleavedBinaryDataListener} to send
* RTP data.
*/
public ResponseProviderSupportingOnlyTcp(
Clock clock,
List<RtpPacketStreamDump> rtpPacketStreamDumps,
RtspMessageChannel.InterleavedBinaryDataListener binaryDataListener) {
super(clock, rtpPacketStreamDumps, binaryDataListener);
}
@Override
public RtspResponse getSetupResponse(Uri requestedUri, RtspHeaders headers) {
String transportHeaderValue = checkNotNull(headers.get(RtspHeaders.TRANSPORT));
if (!transportHeaderValue.contains("TCP")) {
return new RtspResponse(
/* status= */ 461, headers.buildUpon().add(RtspHeaders.SESSION, SESSION_ID).build());
}
for (RtpPacketStreamDump rtpPacketStreamDump : rtpPacketStreamDumps) {
if (requestedUri.toString().contains(rtpPacketStreamDump.trackName)) {
dumpsForSetUpTracks.add(rtpPacketStreamDump);
packetTransmitter = new RtpPacketTransmitter(rtpPacketStreamDump, clock);
}
}
return new RtspResponse(
/* status= */ 200, headers.buildUpon().add(RtspHeaders.SESSION, SESSION_ID).build());
}
}
private abstract static class FakeBaseDataSourceRtpDataChannel extends BaseDataSource
implements RtpDataChannel, RtspMessageChannel.InterleavedBinaryDataListener {
protected static final int LOCAL_PORT = 40000;
private final ConcurrentLinkedQueue<byte[]> packetQueue; private final ConcurrentLinkedQueue<byte[]> packetQueue;
public FakeUdpDataSourceRtpDataChannel() { public FakeBaseDataSourceRtpDataChannel() {
super(/* isNetwork= */ false); super(/* isNetwork= */ false);
packetQueue = new ConcurrentLinkedQueue<>(); packetQueue = new ConcurrentLinkedQueue<>();
} }
@Override @Override
public String getTransport() { public abstract String getTransport();
return Util.formatInvariant("RTP/AVP;unicast;client_port=%d-%d", LOCAL_PORT, LOCAL_PORT + 1);
}
@Override @Override
public int getLocalPort() { public int getLocalPort() {
...@@ -307,4 +433,49 @@ public final class RtspPlaybackTest { ...@@ -307,4 +433,49 @@ public final class RtspPlaybackTest {
return byteToRead; return byteToRead;
} }
} }
private static final class FakeUdpDataSourceRtpDataChannel
extends FakeBaseDataSourceRtpDataChannel {
@Override
public String getTransport() {
return Util.formatInvariant("RTP/AVP;unicast;client_port=%d-%d", LOCAL_PORT, LOCAL_PORT + 1);
}
@Override
public RtspMessageChannel.InterleavedBinaryDataListener getInterleavedBinaryDataListener() {
return null;
}
}
private static final class FakeTcpDataSourceRtpDataChannel
extends FakeBaseDataSourceRtpDataChannel {
@Override
public String getTransport() {
return Util.formatInvariant(
"RTP/AVP/TCP;unicast;interleaved=%d-%d", LOCAL_PORT + 2, LOCAL_PORT + 3);
}
}
private static class ForwardingRtpDataChannelFactory implements RtpDataChannel.Factory {
private final RtpDataChannel.Factory rtpChannelFactory;
private final RtpDataChannel.Factory rtpFallbackChannelFactory;
public ForwardingRtpDataChannelFactory(
RtpDataChannel.Factory rtpChannelFactory,
RtpDataChannel.Factory rtpFallbackChannelFactory) {
this.rtpChannelFactory = rtpChannelFactory;
this.rtpFallbackChannelFactory = rtpFallbackChannelFactory;
}
@Override
public RtpDataChannel createAndOpenDataChannel(int trackId) throws IOException {
return rtpChannelFactory.createAndOpenDataChannel(trackId);
}
@Override
public RtpDataChannel.Factory createFallbackDataChannelFactory() {
return rtpFallbackChannelFactory;
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment