mirror of
https://github.com/samsonjs/media.git
synced 2026-03-31 10:25:48 +00:00
Merge pull request #9606 from ened:rtsp-socket-factory
PiperOrigin-RevId: 413751821
This commit is contained in:
commit
e288f9428f
8 changed files with 143 additions and 2 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
21
docs/rtsp.md
21
docs/rtsp.md
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<RtpLoadInfo> 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) {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,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;
|
||||
|
||||
|
|
@ -102,6 +103,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,
|
||||
|
|
@ -109,6 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
Uri uri,
|
||||
Listener listener,
|
||||
String userAgent,
|
||||
SocketFactory socketFactory,
|
||||
boolean debugLoggingEnabled) {
|
||||
this.allocator = allocator;
|
||||
this.rtpDataChannelFactory = rtpDataChannelFactory;
|
||||
|
|
@ -122,6 +126,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
/* playbackEventListener= */ internalListener,
|
||||
/* userAgent= */ userAgent,
|
||||
/* uri= */ uri,
|
||||
socketFactory,
|
||||
debugLoggingEnabled);
|
||||
rtspLoaderWrappers = new ArrayList<>();
|
||||
selectedLoadInfos = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
|||
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 {
|
||||
|
|
@ -69,12 +70,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 = ExoPlayerLibraryInfo.VERSION_SLASHY;
|
||||
socketFactory = SocketFactory.getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,6 +107,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}.
|
||||
*
|
||||
|
|
@ -203,6 +218,7 @@ public final class RtspMediaSource extends BaseMediaSource {
|
|||
? new TransferRtpDataChannelFactory(timeoutMs)
|
||||
: new UdpDataSourceRtpDataChannelFactory(timeoutMs),
|
||||
userAgent,
|
||||
socketFactory,
|
||||
debugLoggingEnabled);
|
||||
}
|
||||
}
|
||||
|
|
@ -226,6 +242,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;
|
||||
|
|
@ -238,11 +255,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;
|
||||
|
|
@ -282,6 +301,7 @@ public final class RtspMediaSource extends BaseMediaSource {
|
|||
notifySourceInfoRefreshed();
|
||||
},
|
||||
userAgent,
|
||||
socketFactory,
|
||||
debugLoggingEnabled);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener;
|
|||
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;
|
||||
|
|
@ -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<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 {
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue