diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts
index 7b84ee298..9e91b6039 100644
--- a/openvidu-browser/src/OpenVidu/Session.ts
+++ b/openvidu-browser/src/OpenVidu/Session.ts
@@ -1501,7 +1501,21 @@ export class Session extends EventDispatcher {
private processJoinRoomResponse(opts: LocalConnectionOptions) {
this.sessionId = opts.session;
- if (opts.coturnIp != null && opts.coturnPort != null && opts.turnUsername != null && opts.turnCredential != null) {
+ if (opts.customIceServers != null && opts.customIceServers.length > 0) {
+ this.openvidu.iceServers = [];
+ for(const iceServer of opts.customIceServers) {
+ let rtcIceServer: RTCIceServer = {
+ urls: [ iceServer.url ]
+ }
+ logger.log("STUN/TURN server IP: " + iceServer.url);
+ if (iceServer.username != null && iceServer.credential != null) {
+ rtcIceServer.username = iceServer.username;
+ rtcIceServer.credential = iceServer.credential;
+ logger.log('TURN credentials [' + iceServer.username + ':' + iceServer.credential + ']');
+ }
+ this.openvidu.iceServers.push(rtcIceServer);
+ }
+ } else if (opts.coturnIp != null && opts.coturnPort != null && opts.turnUsername != null && opts.turnCredential != null) {
const turnUrl1 = 'turn:' + opts.coturnIp + ':' + opts.coturnPort;
this.openvidu.iceServers = [
{ urls: [turnUrl1], username: opts.turnUsername, credential: opts.turnCredential }
diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts
new file mode 100644
index 000000000..38402e513
--- /dev/null
+++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/IceServerProperties.ts
@@ -0,0 +1,21 @@
+/*
+ * (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+export interface IceServerProperties {
+ url: string;
+ username?: string;
+ credential?: string;
+}
\ No newline at end of file
diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts
index ad057f422..9f52796cc 100644
--- a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts
+++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts
@@ -16,6 +16,7 @@
*/
import { RemoteConnectionOptions } from './RemoteConnectionOptions';
+import { IceServerProperties } from './IceServerProperties';
export interface LocalConnectionOptions {
id: string;
@@ -35,4 +36,5 @@ export interface LocalConnectionOptions {
mediaServer: string;
videoSimulcast: boolean;
life: number;
+ customIceServers?: IceServerProperties[]
}
diff --git a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java
index 85a327d58..9574b48ce 100644
--- a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java
+++ b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java
@@ -165,6 +165,7 @@ public class ProtocolElements {
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
public static final String PARTICIPANTJOINED_COTURNPORT_PARAM = "coturnPort";
+ public static final String PARTICIPANTJOINED_CUSTOM_ICE_SERVERS = "customIceServers";
public static final String PARTICIPANTJOINED_TURNUSERNAME_PARAM = "turnUsername";
public static final String PARTICIPANTJOINED_TURNCREDENTIAL_PARAM = "turnCredential";
diff --git a/openvidu-java-client/pom.xml b/openvidu-java-client/pom.xml
index 501181a8d..4be6cd837 100644
--- a/openvidu-java-client/pom.xml
+++ b/openvidu-java-client/pom.xml
@@ -86,6 +86,11 @@
${version.junit}test
+
+ commons-validator
+ commons-validator
+ ${version.commons-validator}
+
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java
index b838e43e6..8208c563e 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/Connection.java
@@ -25,7 +25,9 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
/**
* See {@link io.openvidu.java.client.Session#getConnections()}
@@ -190,6 +192,19 @@ public class Connection {
return this.connectionProperties.getNetworkCache();
}
+ /**
+ * Returns a list of custom ICE Servers configured for this connection.
+ *
+ * See {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} for more
+ * information.
+ *
+ * Only for
+ * {@link io.openvidu.java.client.ConnectionType#WEBRTC}
+ */
+ public List getCustomIceServers() {
+ return this.connectionProperties.getCustomIceServers();
+ }
+
/**
* Returns the token string associated to the Connection. This is the value that
* must be sent to the client-side to be consumed in OpenVidu Browser method
@@ -322,6 +337,11 @@ public class Connection {
if (this.connectionProperties.getNetworkCache() != null) {
builder.networkCache(this.connectionProperties.getNetworkCache());
}
+ if (this.connectionProperties.getCustomIceServers() != null && !this.connectionProperties.getCustomIceServers().isEmpty()) {
+ for (IceServerProperties iceServerProperties: this.connectionProperties.getCustomIceServers()) {
+ builder.addCustomIceServer(iceServerProperties);
+ }
+ }
this.connectionProperties = builder.build();
}
@@ -415,6 +435,24 @@ public class Connection {
? OpenViduRole.valueOf(json.get("role").getAsString())
: null;
+ List customIceServers = new ArrayList<>();
+ if (json.has("customIceServers") && json.get("customIceServers").isJsonArray()) {
+ JsonArray customIceServersJsonArray = json.get("customIceServers").getAsJsonArray();
+ customIceServersJsonArray.forEach(iceJsonElem -> {
+ JsonObject iceJsonObj = iceJsonElem.getAsJsonObject();
+ String url = (iceJsonObj.has("url") && !iceJsonObj.get("url").isJsonNull())
+ ? iceJsonObj.get("url").getAsString()
+ : null;
+ String username = (iceJsonObj.has("username") && !iceJsonObj.get("username").isJsonNull())
+ ? iceJsonObj.get("username").getAsString()
+ : null;
+ String credential = (iceJsonObj.has("credential") && !iceJsonObj.get("credential").isJsonNull())
+ ? iceJsonObj.get("credential").getAsString()
+ : null;
+ customIceServers.add(new IceServerProperties.Builder().url(url).username(username).credential(credential).build());
+ });
+ }
+
// IPCAM
String rtspUri = (json.has("rtspUri") && !json.get("rtspUri").isJsonNull()) ? json.get("rtspUri").getAsString()
: null;
@@ -428,8 +466,9 @@ public class Connection {
Integer networkCache = (json.has("networkCache") && !json.get("networkCache").isJsonNull())
? json.get("networkCache").getAsInt()
: null;
+
this.connectionProperties = new ConnectionProperties(type, data, record, role, null, rtspUri, adaptativeBitrate,
- onlyPlayWithSubscribers, networkCache);
+ onlyPlayWithSubscribers, networkCache, customIceServers);
return this;
}
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java
index b68504c3c..a9125dec8 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/ConnectionProperties.java
@@ -1,8 +1,12 @@
package io.openvidu.java.client;
+import com.google.gson.JsonArray;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* See
* {@link io.openvidu.java.client.Session#createConnection(ConnectionProperties)}
@@ -22,6 +26,9 @@ public class ConnectionProperties {
private Boolean onlyPlayWithSubscribers;
private Integer networkCache;
+ // External Turn Service
+ private List customIceServers;
+
/**
*
* Builder for {@link io.openvidu.java.client.ConnectionProperties}
@@ -36,18 +43,21 @@ public class ConnectionProperties {
// WEBRTC
private OpenViduRole role;
private KurentoOptions kurentoOptions;
+ private List customIceServers = new ArrayList<>();
// IPCAM
private String rtspUri;
private Boolean adaptativeBitrate;
private Boolean onlyPlayWithSubscribers;
private Integer networkCache;
+
/**
* Builder for {@link io.openvidu.java.client.ConnectionProperties}.
*/
public ConnectionProperties build() {
return new ConnectionProperties(this.type, this.data, this.record, this.role, this.kurentoOptions,
- this.rtspUri, this.adaptativeBitrate, this.onlyPlayWithSubscribers, this.networkCache);
+ this.rtspUri, this.adaptativeBitrate, this.onlyPlayWithSubscribers, this.networkCache,
+ this.customIceServers);
}
/**
@@ -219,11 +229,42 @@ public class ConnectionProperties {
this.networkCache = networkCache;
return this;
}
+
+ /**
+ * On certain type of networks, clients using default OpenVidu STUN/TURN server can not be reached it because
+ * firewall rules and network topologies at the client side. This method allows you to configure your
+ * own ICE Server for specific connections if you need it. This is usually not necessary, only it is usefull for
+ * OpenVidu users behind firewalls which allows traffic from/to specific ports which may need a custom
+ * ICE Server configuration
+ *
+ * Add an ICE Server if in your use case you need this connection to use your own ICE Server deployment.
+ * When the user uses this connection, it will use the specified ICE Servers defined here.
+ *
+ * The level of precedence for ICE Server configuration on every OpenVidu connection is:
+ *
+ *
Configured ICE Server using Openvidu.setAdvancedCofiguration() at openvidu-browser.
+ *
Configured ICE server at
+ * {@link io.openvidu.java.client.ConnectionProperties#customIceServers ConnectionProperties.customIceServers}
+ *
Configured ICE Server at global configuration parameter: OPENVIDU_WEBRTC_ICE_SERVERS
+ *
Default deployed Coturn within OpenVidu deployment
+ *
+ *
+ * If no value is found at level 1, level 2 will be used, and so on until level 4.
+ *
+ * This method is equivalent to level 2 of precedence.
+ *
+ * Only for
+ * {@link io.openvidu.java.client.ConnectionType#WEBRTC}
+ */
+ public Builder addCustomIceServer(IceServerProperties iceServerProperties) {
+ this.customIceServers.add(iceServerProperties);
+ return this;
+ }
}
ConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role,
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
- Integer networkCache) {
+ Integer networkCache, List customIceServers) {
this.type = type;
this.data = data;
this.record = record;
@@ -233,6 +274,7 @@ public class ConnectionProperties {
this.adaptativeBitrate = adaptativeBitrate;
this.onlyPlayWithSubscribers = onlyPlayWithSubscribers;
this.networkCache = networkCache;
+ this.customIceServers = customIceServers;
}
/**
@@ -346,6 +388,19 @@ public class ConnectionProperties {
return this.networkCache;
}
+ /**
+ * Returns a list of custom ICE Servers configured for this connection.
+ *
+ * See {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} for more
+ * information.
+ *
+ * Only for
+ * {@link io.openvidu.java.client.ConnectionType#WEBRTC}
+ */
+ public List getCustomIceServers() {
+ return new ArrayList<>(this.customIceServers);
+ }
+
public JsonObject toJson(String sessionId) {
JsonObject json = new JsonObject();
json.addProperty("session", sessionId);
@@ -376,6 +431,12 @@ public class ConnectionProperties {
} else {
json.add("kurentoOptions", JsonNull.INSTANCE);
}
+ JsonArray customIceServersJsonList = new JsonArray();
+ customIceServers.forEach((customIceServer) -> {
+ customIceServersJsonList.add(customIceServer.toJson());
+ });
+ json.add("customIceServers", customIceServersJsonList);
+
// IPCAM
if (getRtspUri() != null) {
json.addProperty("rtspUri", getRtspUri());
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java
new file mode 100644
index 000000000..c9f728766
--- /dev/null
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java
@@ -0,0 +1,286 @@
+/*
+ * (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.openvidu.java.client;
+
+import com.google.gson.JsonObject;
+import org.apache.commons.validator.routines.DomainValidator;
+import org.apache.commons.validator.routines.InetAddressValidator;
+
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * See
+ * {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)}
+ */
+public class IceServerProperties {
+
+ private String url;
+ private String username;
+ private String credential;
+
+ /**
+ * Returns the defined ICE Server url for this {@link IceServerProperties} object.
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Returns the Username to be used for TURN connections at the defined {@link IceServerProperties#getUrl()}
+ * and {@link IceServerProperties#getCredential()} for this {@link IceServerProperties} object.
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Returns the credential to be used for TURN connections at the defined {@link IceServerProperties#getUrl()}
+ * and {@link IceServerProperties#getUsername()} for this {@link IceServerProperties} object.
+ */
+ public String getCredential() {
+ return credential;
+ }
+
+ private IceServerProperties(String url, String username, String credential) {
+ this.url = url;
+ this.username = username;
+ this.credential = credential;
+ }
+
+ /**
+ * @hidden
+ */
+ public JsonObject toJson() {
+ JsonObject json = new JsonObject();
+ json.addProperty("url", getUrl());
+ if (getUsername() != null && !getUsername().isEmpty()) {
+ json.addProperty("username", getUsername());
+ }
+ if (getCredential() != null && !getCredential().isEmpty()) {
+ json.addProperty("credential", getCredential());
+ }
+ return json;
+ }
+
+ /**
+ * Builder for {@link IceServerProperties}
+ */
+ public static class Builder {
+
+ private String url;
+ private String username;
+ private String credential;
+
+ /**
+ * Set the url for the ICE Server you want to use.
+ * It should follow a valid format:
+ *
+ */
+ public IceServerProperties.Builder url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Set a username for the ICE Server you want to use.
+ * This parameter should be defined only for TURN, not for STUN ICE Servers.
+ */
+ public IceServerProperties.Builder username(String userName) {
+ this.username = userName;
+ return this;
+ }
+
+ /**
+ * Set a credential for the ICE Server you want to use.
+ * This parameter should be defined only for TURN, not for STUN ICE Servers.
+ */
+ public IceServerProperties.Builder credential(String credential) {
+ this.credential = credential;
+ return this;
+ }
+
+
+ /**
+ * Builder for {@link io.openvidu.java.client.RecordingProperties}
+ * @throws IllegalArgumentException if the defined properties does not follows
+ * common STUN/TURN RFCs:
+ *