diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java index 58ef8b40cb..b0bde93e00 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java @@ -123,6 +123,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final SessionInfoListener sessionInfoListener; private final PlaybackEventListener playbackEventListener; private final String userAgent; + private final SocketFactory socketFactory; private final boolean debugLoggingEnabled; private final ArrayDeque pendingSetupRtpLoadInfos; // TODO(b/172331505) Add a timeout monitor for pending requests. @@ -155,16 +156,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param playbackEventListener The {@link PlaybackEventListener}. * @param userAgent The user agent. * @param uri The RTSP playback URI. + * @param socketFactory A socket factory for the RTSP connection. + * @param debugLoggingEnabled Whether to log RTSP messages. */ public RtspClient( SessionInfoListener sessionInfoListener, PlaybackEventListener playbackEventListener, String userAgent, Uri uri, + SocketFactory socketFactory, boolean debugLoggingEnabled) { this.sessionInfoListener = sessionInfoListener; this.playbackEventListener = playbackEventListener; this.userAgent = userAgent; + this.socketFactory = socketFactory; this.debugLoggingEnabled = debugLoggingEnabled; this.pendingSetupRtpLoadInfos = new ArrayDeque<>(); this.pendingRequests = new SparseArray<>(); @@ -286,10 +291,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } /** Returns a {@link Socket} that is connected to the {@code uri}. */ - private static Socket getSocket(Uri uri) throws IOException { + private Socket getSocket(Uri uri) throws IOException { checkArgument(uri.getHost() != null); int rtspPort = uri.getPort() > 0 ? uri.getPort() : DEFAULT_RTSP_PORT; - return SocketFactory.getDefault().createSocket(checkNotNull(uri.getHost()), rtspPort); + return socketFactory.createSocket(checkNotNull(uri.getHost()), rtspPort); } private void dispatchRtspError(Throwable error) { diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriod.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriod.java index 681ed5f27e..2081d81b8f 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriod.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriod.java @@ -56,6 +56,7 @@ import java.io.IOException; import java.net.BindException; import java.util.ArrayList; import java.util.List; +import javax.net.SocketFactory; import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -104,6 +105,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param uri The RTSP playback {@link Uri}. * @param listener A {@link Listener} to receive session information updates. * @param userAgent The user agent. + * @param socketFactory A socket factory for {@link RtspClient}'s connection. + * @param debugLoggingEnabled Whether to log RTSP messages. */ public RtspMediaPeriod( Allocator allocator, @@ -111,6 +114,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; Uri uri, Listener listener, String userAgent, + SocketFactory socketFactory, boolean debugLoggingEnabled) { this.allocator = allocator; this.rtpDataChannelFactory = rtpDataChannelFactory; @@ -124,6 +128,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* playbackEventListener= */ internalListener, /* userAgent= */ userAgent, /* uri= */ uri, + socketFactory, debugLoggingEnabled); rtspLoaderWrappers = new ArrayList<>(); selectedLoadInfos = new ArrayList<>(); diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaSource.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaSource.java index a639d97723..2d25c29f5e 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaSource.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaSource.java @@ -42,6 +42,7 @@ import androidx.media3.exoplayer.source.SinglePeriodTimeline; import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import java.io.IOException; +import javax.net.SocketFactory; /** An Rtsp {@link MediaSource} */ @UnstableApi @@ -71,12 +72,14 @@ public final class RtspMediaSource extends BaseMediaSource { private long timeoutMs; private String userAgent; + private SocketFactory socketFactory; private boolean forceUseRtpTcp; private boolean debugLoggingEnabled; public Factory() { timeoutMs = DEFAULT_TIMEOUT_MS; userAgent = MediaLibraryInfo.VERSION_SLASHY; + socketFactory = SocketFactory.getDefault(); } /** @@ -106,6 +109,18 @@ public final class RtspMediaSource extends BaseMediaSource { return this; } + /** + * Sets a socket factory for {@link RtspClient}'s connection, the default value is {@link + * SocketFactory#getDefault()}. + * + * @param socketFactory A socket factory. + * @return This Factory, for convenience. + */ + public Factory setSocketFactory(SocketFactory socketFactory) { + this.socketFactory = socketFactory; + return this; + } + /** * Sets whether to log RTSP messages, the default value is {@code false}. * @@ -205,6 +220,7 @@ public final class RtspMediaSource extends BaseMediaSource { ? new TransferRtpDataChannelFactory(timeoutMs) : new UdpDataSourceRtpDataChannelFactory(timeoutMs), userAgent, + socketFactory, debugLoggingEnabled); } } @@ -228,6 +244,7 @@ public final class RtspMediaSource extends BaseMediaSource { private final RtpDataChannel.Factory rtpDataChannelFactory; private final String userAgent; private final Uri uri; + private final SocketFactory socketFactory; private final boolean debugLoggingEnabled; private long timelineDurationUs; @@ -240,11 +257,13 @@ public final class RtspMediaSource extends BaseMediaSource { MediaItem mediaItem, RtpDataChannel.Factory rtpDataChannelFactory, String userAgent, + SocketFactory socketFactory, boolean debugLoggingEnabled) { this.mediaItem = mediaItem; this.rtpDataChannelFactory = rtpDataChannelFactory; this.userAgent = userAgent; this.uri = checkNotNull(this.mediaItem.localConfiguration).uri; + this.socketFactory = socketFactory; this.debugLoggingEnabled = debugLoggingEnabled; this.timelineDurationUs = C.TIME_UNSET; this.timelineIsPlaceholder = true; @@ -284,6 +303,7 @@ public final class RtspMediaSource extends BaseMediaSource { notifySourceInfoRefreshed(); }, userAgent, + socketFactory, debugLoggingEnabled); } diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java index 818df221a2..2e99b36aeb 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java @@ -27,8 +27,12 @@ import androidx.media3.exoplayer.rtsp.RtspMediaSource.RtspPlaybackException; import androidx.media3.test.utils.robolectric.RobolectricUtil; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import javax.net.SocketFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -77,6 +81,80 @@ public final class RtspClientTest { Util.closeQuietly(rtspClient); } + @Test + public void connectServerAndClient_usesCustomSocketFactory() 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) { + return RtspTestUtils.newDescribeResponseWithSdpMessage( + SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); + } + } + rtspServer = new RtspServer(new ResponseProvider()); + + AtomicBoolean didCallCreateSocket = new AtomicBoolean(); + SocketFactory socketFactory = + new SocketFactory() { + + @Override + public Socket createSocket(String host, int port) throws IOException { + didCallCreateSocket.set(true); + return SocketFactory.getDefault().createSocket(host, port); + } + + @Override + public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) + throws IOException { + didCallCreateSocket.set(true); + return SocketFactory.getDefault().createSocket(s, i, inetAddress, i1); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i) throws IOException { + didCallCreateSocket.set(true); + return SocketFactory.getDefault().createSocket(inetAddress, i); + } + + @Override + public Socket createSocket( + InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException { + didCallCreateSocket.set(true); + return SocketFactory.getDefault().createSocket(inetAddress, i, inetAddress1, i1); + } + }; + + 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()), + socketFactory, + /* debugLoggingEnabled= */ false); + rtspClient.start(); + RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null); + + assertThat(didCallCreateSocket.get()).isTrue(); + } + @Test public void connectServerAndClient_serverSupportsDescribe_updatesSessionTimeline() throws Exception { @@ -113,6 +191,7 @@ public final class RtspClientTest { EMPTY_PLAYBACK_LISTENER, /* userAgent= */ "ExoPlayer:RtspClientTest", RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + SocketFactory.getDefault(), /* debugLoggingEnabled= */ false); rtspClient.start(); RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null); @@ -164,6 +243,7 @@ public final class RtspClientTest { EMPTY_PLAYBACK_LISTENER, /* userAgent= */ "ExoPlayer:RtspClientTest", RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + SocketFactory.getDefault(), /* debugLoggingEnabled= */ false); rtspClient.start(); RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null); @@ -207,6 +287,7 @@ public final class RtspClientTest { EMPTY_PLAYBACK_LISTENER, /* userAgent= */ "ExoPlayer:RtspClientTest", RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + SocketFactory.getDefault(), /* debugLoggingEnabled= */ false); rtspClient.start(); RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null); @@ -253,6 +334,7 @@ public final class RtspClientTest { EMPTY_PLAYBACK_LISTENER, /* userAgent= */ "ExoPlayer:RtspClientTest", RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + SocketFactory.getDefault(), /* debugLoggingEnabled= */ false); rtspClient.start(); RobolectricUtil.runMainLooperUntil(() -> failureMessage.get() != null); @@ -299,6 +381,7 @@ public final class RtspClientTest { EMPTY_PLAYBACK_LISTENER, /* userAgent= */ "ExoPlayer:RtspClientTest", RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + SocketFactory.getDefault(), /* debugLoggingEnabled= */ false); rtspClient.start(); diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java index 7a1e1145df..fdbe133d39 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java @@ -27,6 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import javax.net.SocketFactory; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,6 +85,7 @@ public final class RtspMediaPeriodTest { RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), /* listener= */ timing -> refreshedSourceDurationMs.set(timing.getDurationMs()), /* userAgent= */ "ExoPlayer:RtspPeriodTest", + /* socketFactory= */ SocketFactory.getDefault(), /* debugLoggingEnabled= */ false); mediaPeriod.prepare( diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java index 38333c32fd..1caded0039 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java @@ -46,6 +46,7 @@ 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.Before; import org.junit.Rule; import org.junit.Test; @@ -156,6 +157,7 @@ public final class RtspPlaybackTest { MediaItem.fromUri(RtspTestUtils.getTestUri(serverRtspPortNumber)), rtpDataChannelFactory, "ExoPlayer:PlaybackTest", + SocketFactory.getDefault(), /* debugLoggingEnabled= */ false), false); return player;