Skip to content

Commit

Permalink
Merge pull request #9606 from ened:rtsp-socket-factory
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 413751821
  • Loading branch information
ojw28 committed Dec 6, 2021
2 parents fc99237 + e158f9a commit e288f94
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 2 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
* MediaSession extension
* Remove deprecated call to `onStop(/* reset= */ true)` and provide an
opt-out flag for apps that don't want to clear the playlist on stop.
* RTSP
* Provide a client API to override the `SocketFactory` used for any server
connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)).

### 2.16.1 (2021-11-18)

Expand Down
21 changes: 21 additions & 0 deletions docs/rtsp.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,24 @@ end-of-stream signal under poor network conditions.
RTP/TCP offers better compatibility under some network setups. You can configure
ExoPlayer to use RTP/TCP by default with
`RtspMediaSource.Factory.setForceUseRtpTcp()`.

### Passing a custom SocketFactory
Custom `SocketFactory` instances can be useful when particular routing is
required (e.g. when RTSP traffic needs to pass a specific interface, or the
socket needs additional connectivity flags).

By default, `RtspMediaSource` will use Java's standard socket factory
(`SocketFactory.getDefault()`) to create connections to the remote endpoints.
This behavior can be overridden using
`RtspMediaSource.Factory.setSocketFactory()`.

~~~
// Create an RTSP media source pointing to an RTSP uri and override the socket
// factory.
MediaSource mediaSource =
new RtspMediaSource.Factory()
.setSocketFactory(...)
.createMediaSource(MediaItem.fromUri(rtspUri));
~~~
{: .language-java}

Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public interface PlaybackEventListener {
private final SessionInfoListener sessionInfoListener;
private final PlaybackEventListener playbackEventListener;
private final String userAgent;
private final SocketFactory socketFactory;
private final boolean debugLoggingEnabled;
private final ArrayDeque<RtpLoadInfo> pendingSetupRtpLoadInfos;
// TODO(b/172331505) Add a timeout monitor for pending requests.
Expand Down Expand Up @@ -155,16 +156,20 @@ public interface PlaybackEventListener {
* @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<>();
Expand Down Expand Up @@ -286,10 +291,10 @@ private void maybeLogMessage(List<String> message) {
}

/** 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
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;

Expand Down Expand Up @@ -102,13 +103,16 @@ interface Listener {
* @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,
RtpDataChannel.Factory rtpDataChannelFactory,
Uri uri,
Listener listener,
String userAgent,
SocketFactory socketFactory,
boolean debugLoggingEnabled) {
this.allocator = allocator;
this.rtpDataChannelFactory = rtpDataChannelFactory;
Expand All @@ -122,6 +126,7 @@ public RtspMediaPeriod(
/* playbackEventListener= */ internalListener,
/* userAgent= */ userAgent,
/* uri= */ uri,
socketFactory,
debugLoggingEnabled);
rtspLoaderWrappers = new ArrayList<>();
selectedLoadInfos = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import javax.net.SocketFactory;

/** An Rtsp {@link MediaSource} */
public final class RtspMediaSource extends BaseMediaSource {
Expand Down Expand Up @@ -69,12 +70,14 @@ public static final class Factory implements MediaSourceFactory {

private long timeoutMs;
private String userAgent;
private SocketFactory socketFactory;
private boolean forceUseRtpTcp;
private boolean debugLoggingEnabled;

public Factory() {
timeoutMs = DEFAULT_TIMEOUT_MS;
userAgent = ExoPlayerLibraryInfo.VERSION_SLASHY;
socketFactory = SocketFactory.getDefault();
}

/**
Expand Down Expand Up @@ -104,6 +107,18 @@ public Factory setUserAgent(String userAgent) {
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}.
*
Expand Down Expand Up @@ -203,6 +218,7 @@ public RtspMediaSource createMediaSource(MediaItem mediaItem) {
? new TransferRtpDataChannelFactory(timeoutMs)
: new UdpDataSourceRtpDataChannelFactory(timeoutMs),
userAgent,
socketFactory,
debugLoggingEnabled);
}
}
Expand All @@ -226,6 +242,7 @@ public RtspPlaybackException(String message, Throwable e) {
private final RtpDataChannel.Factory rtpDataChannelFactory;
private final String userAgent;
private final Uri uri;
private final SocketFactory socketFactory;
private final boolean debugLoggingEnabled;

private long timelineDurationUs;
Expand All @@ -238,11 +255,13 @@ public RtspPlaybackException(String message, Throwable e) {
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;
Expand Down Expand Up @@ -282,6 +301,7 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long star
notifySourceInfoRefreshed();
},
userAgent,
socketFactory,
debugLoggingEnabled);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException;
import com.google.android.exoplayer2.util.Util;
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;
Expand Down Expand Up @@ -77,6 +81,80 @@ public void tearDown() {
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<ImmutableList<RtspMediaTrack>> tracksInSession = new AtomicReference<>();
rtspClient =
new RtspClient(
new SessionInfoListener() {
@Override
public void onSessionTimelineUpdated(
RtspSessionTiming timing, ImmutableList<RtspMediaTrack> 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 {
Expand Down Expand Up @@ -113,6 +191,7 @@ public void onSessionTimelineRequestFailed(
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
Expand Down Expand Up @@ -164,6 +243,7 @@ public void onSessionTimelineRequestFailed(
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
Expand Down Expand Up @@ -207,6 +287,7 @@ public void onSessionTimelineRequestFailed(
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();
RobolectricUtil.runMainLooperUntil(() -> tracksInSession.get() != null);
Expand Down Expand Up @@ -253,6 +334,7 @@ public void onSessionTimelineRequestFailed(
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();
RobolectricUtil.runMainLooperUntil(() -> failureMessage.get() != null);
Expand Down Expand Up @@ -299,6 +381,7 @@ public void onSessionTimelineRequestFailed(
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -84,6 +85,7 @@ public RtspResponse getDescribeResponse(Uri requestedUri) {
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
/* listener= */ timing -> refreshedSourceDurationMs.set(timing.getDurationMs()),
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
/* socketFactory= */ SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);

mediaPeriod.prepare(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
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;
Expand Down Expand Up @@ -156,6 +157,7 @@ private ExoPlayer createExoPlayer(
MediaItem.fromUri(RtspTestUtils.getTestUri(serverRtspPortNumber)),
rtpDataChannelFactory,
"ExoPlayer:PlaybackTest",
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false),
false);
return player;
Expand Down

0 comments on commit e288f94

Please sign in to comment.