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
This commit is contained in:
parent
df558b59db
commit
ecf4d8b891
@ -42,6 +42,10 @@
|
||||
* DASH:
|
||||
* Fix handling of empty segment timelines
|
||||
([#11014](https://github.com/google/ExoPlayer/issues/11014)).
|
||||
* RTSP:
|
||||
* Retry with TCP if RTSP Setup with UDP fails with RTSP Error 461
|
||||
UnsupportedTransport
|
||||
([#11069](https://github.com/google/ExoPlayer/issues/11069)).
|
||||
* IMA DAI extension:
|
||||
* Fix a bug where a new ad group is inserted in live streams because the
|
||||
calculated content position in consecutive timelines varies slightly.
|
||||
|
@ -49,6 +49,7 @@ import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.rtsp.RtspMediaPeriod.RtpLoadInfo;
|
||||
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.RtspMessageUtil.RtspAuthUserInfo;
|
||||
import androidx.media3.exoplayer.rtsp.RtspMessageUtil.RtspSessionHeader;
|
||||
@ -577,8 +578,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
receivedAuthorizationRequest = true;
|
||||
return;
|
||||
}
|
||||
// fall through: if unauthorized and no userInfo present, or previous authentication
|
||||
// unsuccessful.
|
||||
// if unauthorized and no userInfo present, or previous authentication
|
||||
// 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:
|
||||
dispatchRtspError(
|
||||
new RtspPlaybackException(
|
||||
|
@ -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
|
||||
// loadables.
|
||||
retryWithRtpTcp();
|
||||
isUsingRtpTcp = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -644,7 +643,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
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
|
||||
@ -668,6 +673,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
private void retryWithRtpTcp() {
|
||||
// Retry should only run once.
|
||||
isUsingRtpTcp = true;
|
||||
|
||||
rtspClient.retryWithRtpTcp();
|
||||
|
||||
@Nullable
|
||||
|
@ -192,7 +192,7 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
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 RtpDataChannel.Factory rtpDataChannelFactory;
|
||||
private final String userAgent;
|
||||
|
@ -453,4 +453,77 @@ public final class RtspClientTest {
|
||||
RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get);
|
||||
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 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.rtsp;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
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.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.net.SocketFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@ -58,30 +61,20 @@ import org.robolectric.annotation.Config;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class RtspPlaybackTest {
|
||||
|
||||
private static final long DEFAULT_TIMEOUT_MS = 8000;
|
||||
private static final String SESSION_DESCRIPTION =
|
||||
"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";
|
||||
|
||||
private final Context applicationContext;
|
||||
private final CapturingRenderersFactory capturingRenderersFactory;
|
||||
private final Clock clock;
|
||||
private final FakeUdpDataSourceRtpDataChannel fakeRtpDataChannel;
|
||||
private final RtpDataChannel.Factory rtpDataChannelFactory;
|
||||
|
||||
private Context applicationContext;
|
||||
private CapturingRenderersFactory capturingRenderersFactory;
|
||||
private Clock clock;
|
||||
private RtpPacketStreamDump aacRtpPacketStreamDump;
|
||||
// ExoPlayer does not support extracting MP4A-LATM RTP payload at the moment.
|
||||
private RtpPacketStreamDump mpeg2tsRtpPacketStreamDump;
|
||||
|
||||
/** Creates a new instance. */
|
||||
public RtspPlaybackTest() {
|
||||
applicationContext = ApplicationProvider.getApplicationContext();
|
||||
capturingRenderersFactory = new CapturingRenderersFactory(applicationContext);
|
||||
clock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
fakeRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
|
||||
rtpDataChannelFactory = (trackId) -> fakeRtpDataChannel;
|
||||
}
|
||||
private RtspServer rtspServer;
|
||||
|
||||
@Rule
|
||||
public ShadowMediaCodecConfig mediaCodecConfig =
|
||||
@ -89,61 +82,162 @@ public final class RtspPlaybackTest {
|
||||
|
||||
@Before
|
||||
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");
|
||||
mpeg2tsRtpPacketStreamDump =
|
||||
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/mpeg2ts-dump.json");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Util.closeQuietly(rtspServer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withSupportedTrack_playsTrackUntilEnded() throws Exception {
|
||||
FakeUdpDataSourceRtpDataChannel fakeRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
|
||||
RtpDataChannel.Factory rtpDataChannelFactory = (trackId) -> fakeRtpDataChannel;
|
||||
ResponseProvider responseProvider =
|
||||
new ResponseProvider(
|
||||
clock,
|
||||
ImmutableList.of(aacRtpPacketStreamDump, mpeg2tsRtpPacketStreamDump),
|
||||
fakeRtpDataChannel);
|
||||
rtspServer = new RtspServer(responseProvider);
|
||||
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
|
||||
|
||||
try (RtspServer rtspServer = new RtspServer(responseProvider)) {
|
||||
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
|
||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
|
||||
player.prepare();
|
||||
player.play();
|
||||
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
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(responseProvider.getDumpsForSetUpTracks()).containsExactly(aacRtpPacketStreamDump);
|
||||
DumpFileAsserts.assertOutput(
|
||||
applicationContext, playbackOutput, "playbackdumps/rtsp/aac.dump");
|
||||
}
|
||||
// Only setup the supported track (aac).
|
||||
assertThat(responseProvider.getDumpsForSetUpTracks()).containsExactly(aacRtpPacketStreamDump);
|
||||
DumpFileAsserts.assertOutput(applicationContext, playbackOutput, "playbackdumps/rtsp/aac.dump");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_noSupportedTrack_throwsPreparationError() throws Exception {
|
||||
|
||||
try (RtspServer rtspServer =
|
||||
FakeUdpDataSourceRtpDataChannel fakeRtpDataChannel = new FakeUdpDataSourceRtpDataChannel();
|
||||
RtpDataChannel.Factory rtpDataChannelFactory = (trackId) -> fakeRtpDataChannel;
|
||||
rtspServer =
|
||||
new RtspServer(
|
||||
new ResponseProvider(
|
||||
clock, ImmutableList.of(mpeg2tsRtpPacketStreamDump), fakeRtpDataChannel))) {
|
||||
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
|
||||
clock, ImmutableList.of(mpeg2tsRtpPacketStreamDump), fakeRtpDataChannel));
|
||||
ExoPlayer player = createExoPlayer(rtspServer.startAndGetPortNumber(), rtpDataChannelFactory);
|
||||
|
||||
AtomicReference<Throwable> 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();
|
||||
AtomicReference<Throwable> 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()
|
||||
.hasMessageThat()
|
||||
.contains("No playable track.");
|
||||
}
|
||||
assertThat(playbackError.get()).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(
|
||||
@ -163,16 +257,16 @@ public final class RtspPlaybackTest {
|
||||
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;
|
||||
private final ArrayList<RtpPacketStreamDump> dumpsForSetUpTracks;
|
||||
private final ImmutableList<RtpPacketStreamDump> rtpPacketStreamDumps;
|
||||
protected final Clock clock;
|
||||
protected final ArrayList<RtpPacketStreamDump> dumpsForSetUpTracks;
|
||||
protected final ImmutableList<RtpPacketStreamDump> rtpPacketStreamDumps;
|
||||
private final RtspMessageChannel.InterleavedBinaryDataListener binaryDataListener;
|
||||
|
||||
private RtpPacketTransmitter packetTransmitter;
|
||||
protected RtpPacketTransmitter packetTransmitter;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -240,22 +334,54 @@ public final class RtspPlaybackTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FakeUdpDataSourceRtpDataChannel extends BaseDataSource
|
||||
implements RtpDataChannel, RtspMessageChannel.InterleavedBinaryDataListener {
|
||||
private static final class ResponseProviderSupportingOnlyTcp extends ResponseProvider {
|
||||
|
||||
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;
|
||||
|
||||
public FakeUdpDataSourceRtpDataChannel() {
|
||||
public FakeBaseDataSourceRtpDataChannel() {
|
||||
super(/* isNetwork= */ false);
|
||||
packetQueue = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTransport() {
|
||||
return Util.formatInvariant("RTP/AVP;unicast;client_port=%d-%d", LOCAL_PORT, LOCAL_PORT + 1);
|
||||
}
|
||||
public abstract String getTransport();
|
||||
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
@ -307,4 +433,49 @@ public final class RtspPlaybackTest {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user