@@ -1902,5 +1902,9 @@ public class XmppConnection implements Runnable {
public boolean bookmarks2() {
return Config.USE_BOOKMARKS2 /* || hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT)*/;
}
+
+ public boolean extendedServiceDiscovery() {
+ return hasDiscoFeature(Jid.of(account.getServer()),Namespace.EXTERNAL_SERVICE_DISCOVERY);
+ }
}
}
@@ -16,12 +16,15 @@ import java.util.List;
import java.util.Map;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import rocks.xmpp.addr.Jid;
@@ -51,6 +54,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
super(jingleConnectionManager, id, initiator);
}
+ private static State reasonToState(Reason reason) {
+ switch (reason) {
+ case SUCCESS:
+ return State.TERMINATED_SUCCESS;
+ case DECLINE:
+ case BUSY:
+ return State.TERMINATED_DECLINED_OR_BUSY;
+ case CANCEL:
+ case TIMEOUT:
+ return State.TERMINATED_CANCEL_OR_TIMEOUT;
+ default:
+ return State.TERMINATED_CONNECTIVITY_ERROR;
+ }
+ }
+
@Override
void deliverPacket(final JinglePacket jinglePacket) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": packet delivered to JingleRtpConnection");
@@ -85,21 +103,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
jingleConnectionManager.finishConnection(this);
}
- private static State reasonToState(Reason reason) {
- switch (reason) {
- case SUCCESS:
- return State.TERMINATED_SUCCESS;
- case DECLINE:
- case BUSY:
- return State.TERMINATED_DECLINED_OR_BUSY;
- case CANCEL:
- case TIMEOUT:
- return State.TERMINATED_CANCEL_OR_TIMEOUT;
- default:
- return State.TERMINATED_CONNECTIVITY_ERROR;
- }
- }
-
private void receiveTransportInfo(final JinglePacket jinglePacket) {
if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
final RtpContentMap contentMap;
@@ -211,22 +214,24 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
if (rtpContentMap == null) {
throw new IllegalStateException("initiator RTP Content Map has not been set");
}
- setupWebRTC();
- final org.webrtc.SessionDescription offer = new org.webrtc.SessionDescription(
- org.webrtc.SessionDescription.Type.OFFER,
- SessionDescription.of(rtpContentMap).toString()
- );
- try {
- this.webRTCWrapper.setRemoteDescription(offer).get();
- org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get();
- final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
- final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
- sendSessionAccept(respondingRtpContentMap);
- this.webRTCWrapper.setLocalDescription(webRTCSessionDescription);
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "unable to send session accept", e);
+ discoverIceServers(iceServers -> {
+ setupWebRTC(iceServers);
+ final org.webrtc.SessionDescription offer = new org.webrtc.SessionDescription(
+ org.webrtc.SessionDescription.Type.OFFER,
+ SessionDescription.of(rtpContentMap).toString()
+ );
+ try {
+ this.webRTCWrapper.setRemoteDescription(offer).get();
+ org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get();
+ final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
+ final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
+ sendSessionAccept(respondingRtpContentMap);
+ this.webRTCWrapper.setLocalDescription(webRTCSessionDescription);
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "unable to send session accept", e);
- }
+ }
+ });
}
private void sendSessionAccept(final RtpContentMap rtpContentMap) {
@@ -346,17 +351,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void sendSessionInitiate(final State targetState) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": prepare session-initiate");
- setupWebRTC();
- try {
- org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get();
- final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
- Log.d(Config.LOGTAG, "description: " + webRTCSessionDescription.description);
- final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
- sendSessionInitiate(rtpContentMap, targetState);
- this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e);
- }
+ discoverIceServers(iceServers -> {
+ setupWebRTC(iceServers);
+ try {
+ org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get();
+ final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
+ Log.d(Config.LOGTAG, "description: " + webRTCSessionDescription.description);
+ final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
+ sendSessionInitiate(rtpContentMap, targetState);
+ this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "unable to sendSessionInitiate", e);
+ }
+ });
}
private void sendSessionInitiate(RtpContentMap rtpContentMap, final State targetState) {
@@ -481,9 +488,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private void setupWebRTC() {
+ private void setupWebRTC(final List<PeerConnection.IceServer> iceServers) {
this.webRTCWrapper.setup(this.xmppConnectionService);
- this.webRTCWrapper.initializePeerConnection();
+ this.webRTCWrapper.initializePeerConnection(iceServers);
}
private void acceptCallFromProposed() {
@@ -559,4 +566,51 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void updateEndUserState() {
xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, getEndUserState());
}
+
+ private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
+ if (id.account.getXmppConnection().getFeatures().extendedServiceDiscovery()) {
+ final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
+ request.setTo(Jid.of(id.account.getJid().getDomain()));
+ request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
+ xmppConnectionService.sendIqPacket(id.account, request, (account, response) -> {
+ ImmutableList.Builder<PeerConnection.IceServer> listBuilder = new ImmutableList.Builder<>();
+ if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Element services = response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
+ final List<Element> children = services == null ? Collections.emptyList() : services.getChildren();
+ for (final Element child : children) {
+ if ("service".equals(child.getName())) {
+ final String type = child.getAttribute("type");
+ final String host = child.getAttribute("host");
+ final String port = child.getAttribute("port");
+ final String transport = child.getAttribute("transport");
+ final String username = child.getAttribute("username");
+ final String password = child.getAttribute("password");
+ if (Arrays.asList("stun", "type").contains(type) && host != null && port != null && "udp".equals(transport)) {
+ PeerConnection.IceServer.Builder iceServerBuilder = PeerConnection.IceServer.builder(String.format("%s:%s:%s", type, host, port));
+ if (username != null && password != null) {
+ iceServerBuilder.setUsername(username);
+ iceServerBuilder.setPassword(password);
+ }
+ final PeerConnection.IceServer iceServer = iceServerBuilder.createIceServer();
+ Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": discovered ICE Server: " + iceServer);
+ listBuilder.add(iceServer);
+ }
+ }
+ }
+ }
+ List<PeerConnection.IceServer> iceServers = listBuilder.build();
+ if (iceServers.size() == 0) {
+ Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no ICE server found " + response);
+ }
+ onIceServersDiscovered.onIceServersDiscovered(iceServers);
+ });
+ } else {
+ Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": has no external service discovery");
+ onIceServersDiscovered.onIceServersDiscovered(Collections.emptyList());
+ }
+ }
+
+ private interface OnIceServersDiscovered {
+ void onIceServersDiscovered(List<PeerConnection.IceServer> iceServers);
+ }
}
@@ -132,7 +132,7 @@ public class WebRTCWrapper {
);
}
- public void initializePeerConnection() {
+ public void initializePeerConnection(final List<PeerConnection.IceServer> iceServers) {
PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
CameraVideoCapturer capturer = null;
@@ -193,10 +193,6 @@ public class WebRTCWrapper {
this.localVideoTrack = videoTrack;
-
- final List<PeerConnection.IceServer> iceServers = ImmutableList.of(
- PeerConnection.IceServer.builder("stun:xmpp.conversations.im:3478").createIceServer()
- );
final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
if (peerConnection == null) {
throw new IllegalStateException("Unable to create PeerConnection");