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
(cherry picked from commit ecf4d8b89193e8e3bb3ad3dbc9c7983fd26765ae)
This commit is contained in:
michaelkatz 2023-03-23 09:54:54 +00:00 committed by Rohit Singh
parent b70b320c49
commit eea37031d1
5 changed files with 342 additions and 66 deletions

View File

@ -49,6 +49,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.rtsp.RtspMediaPeriod.RtpLoadInfo; import androidx.media3.exoplayer.rtsp.RtspMediaPeriod.RtpLoadInfo;
import androidx.media3.exoplayer.rtsp.RtspMediaSource.RtspPlaybackException; import androidx.media3.exoplayer.rtsp.RtspMediaSource.RtspPlaybackException;
import androidx.media3.exoplayer.rtsp.RtspMediaSource.RtspUdpUnsupportedTransportException;
import androidx.media3.exoplayer.rtsp.RtspMessageChannel.InterleavedBinaryDataListener; import androidx.media3.exoplayer.rtsp.RtspMessageChannel.InterleavedBinaryDataListener;
import androidx.media3.exoplayer.rtsp.RtspMessageUtil.RtspAuthUserInfo; import androidx.media3.exoplayer.rtsp.RtspMessageUtil.RtspAuthUserInfo;
import androidx.media3.exoplayer.rtsp.RtspMessageUtil.RtspSessionHeader; import androidx.media3.exoplayer.rtsp.RtspMessageUtil.RtspSessionHeader;
@ -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(

View File

@ -518,7 +518,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;
} }
@ -644,7 +643,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
@ -668,6 +673,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

View File

@ -192,7 +192,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);
} }
@ -206,6 +206,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;

View File

@ -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);
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.exoplayer.rtsp; package androidx.media3.exoplayer.rtsp;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.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 androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
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;
@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 {
@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);
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
try (RtspServer rtspServer = new RtspServer(responseProvider)) { PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory); player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); // Only setup the supported track (aac).
player.prepare(); assertThat(responseProvider.getDumpsForSetUpTracks()).containsExactly(aacRtpPacketStreamDump);
player.play(); DumpFileAsserts.assertOutput(applicationContext, playbackOutput, "playbackdumps/rtsp/aac.dump");
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
// Only setup the supported track (aac).
assertThat(responseProvider.getDumpsForSetUpTracks()).containsExactly(aacRtpPacketStreamDump);
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 {
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 {
} }
} }
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 {
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;
}
}
} }