mirror of https://github.com/OpenVidu/openvidu.git
Merge pull request #698 from OpenVidu/feature/custom-ice-servers
Feature/custom ice serverspull/699/head
commit
a6df44699c
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -86,6 +86,11 @@
|
|||
<version>${version.junit}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-validator</groupId>
|
||||
<artifactId>commons-validator</artifactId>
|
||||
<version>${version.commons-validator}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -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.
|
||||
* <br><br>
|
||||
* See {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} for more
|
||||
* information.
|
||||
* <br><br>
|
||||
* <strong>Only for
|
||||
* {@link io.openvidu.java.client.ConnectionType#WEBRTC}</strong>
|
||||
*/
|
||||
public List<IceServerProperties> 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<IceServerProperties> 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;
|
||||
}
|
||||
|
|
|
@ -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<IceServerProperties> 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<IceServerProperties> 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:
|
||||
* <ol>
|
||||
* <li>Configured ICE Server using Openvidu.setAdvancedCofiguration() at openvidu-browser.</li>
|
||||
* <li>Configured ICE server at
|
||||
* {@link io.openvidu.java.client.ConnectionProperties#customIceServers ConnectionProperties.customIceServers}</li>
|
||||
* <li>Configured ICE Server at global configuration parameter: OPENVIDU_WEBRTC_ICE_SERVERS</li>
|
||||
* <li>Default deployed Coturn within OpenVidu deployment</li>
|
||||
* </ol>
|
||||
* <br>
|
||||
* If no value is found at level 1, level 2 will be used, and so on until level 4.
|
||||
* <br>
|
||||
* This method is equivalent to level 2 of precedence.
|
||||
* <br><br>
|
||||
* <strong>Only for
|
||||
* {@link io.openvidu.java.client.ConnectionType#WEBRTC}</strong>
|
||||
*/
|
||||
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<IceServerProperties> 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.
|
||||
* <br><br>
|
||||
* See {@link io.openvidu.java.client.ConnectionProperties.Builder#addCustomIceServer(IceServerProperties)} for more
|
||||
* information.
|
||||
* <br><br>
|
||||
* <strong>Only for
|
||||
* {@link io.openvidu.java.client.ConnectionType#WEBRTC}</strong>
|
||||
*/
|
||||
public List<IceServerProperties> 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());
|
||||
|
|
|
@ -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:
|
||||
* <ul>
|
||||
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7065#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7065#section-3.1</a></li>
|
||||
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7064#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7064#section-3.1</a></li>
|
||||
* </ul>
|
||||
*/
|
||||
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:
|
||||
* <ul>
|
||||
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7065#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7065#section-3.1</a></li>
|
||||
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7064#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7064#section-3.1</a></li>
|
||||
* </ul>
|
||||
*/
|
||||
public IceServerProperties build() throws IllegalArgumentException {
|
||||
if (this.url == null) {
|
||||
throw new IllegalArgumentException("External turn url cannot be null");
|
||||
}
|
||||
this.checkValidStunTurn(this.url);
|
||||
if (this.url.startsWith("turn")) {
|
||||
if ((this.username == null || this.credential == null)) {
|
||||
throw new IllegalArgumentException("Credentials must be defined while using turn");
|
||||
}
|
||||
} else if (this.url.startsWith("stun")) {
|
||||
if (this.username != null || this.credential != null) {
|
||||
// Credentials can not be defined using stun
|
||||
throw new IllegalArgumentException("Credentials can not be defined while using stun.");
|
||||
}
|
||||
}
|
||||
return new IceServerProperties(this.url, this.username, this.credential);
|
||||
}
|
||||
|
||||
private void checkValidStunTurn(String uri) throws IllegalArgumentException {
|
||||
final String TCP_TRANSPORT_SUFFIX = "?transport=tcp";
|
||||
final String UDP_TRANSPORT_SUFFIX = "?transport=udp";
|
||||
|
||||
// Protocols which accepts transport=tcp and transport=udp
|
||||
final Set<String> TURN_PROTOCOLS = new HashSet<>(Arrays.asList(
|
||||
"turn",
|
||||
"turns"
|
||||
));
|
||||
final Set<String> STUN_PROTOCOLS = new HashSet<>(Arrays.asList(
|
||||
"stun",
|
||||
"stuns"
|
||||
));
|
||||
|
||||
// Fails if no colons
|
||||
int firstColonPos = uri.indexOf(':');
|
||||
if (firstColonPos == -1) {
|
||||
throw new IllegalArgumentException("Not a valid TURN/STUN uri provided. " +
|
||||
"No colons found in: '" + uri + "'");
|
||||
}
|
||||
|
||||
// Get protocol and check
|
||||
String protocol = uri.substring(0, firstColonPos);
|
||||
if (!TURN_PROTOCOLS.contains(protocol) && !STUN_PROTOCOLS.contains(protocol)) {
|
||||
throw new IllegalArgumentException("The protocol '" + protocol + "' is invalid. Only valid values are: "
|
||||
+ TURN_PROTOCOLS + " " + STUN_PROTOCOLS);
|
||||
}
|
||||
|
||||
// Check if query param with transport exist
|
||||
int qmarkPos = uri.indexOf('?');
|
||||
String hostAndPort = uri.substring(firstColonPos + 1);
|
||||
if (qmarkPos != -1) {
|
||||
if (TURN_PROTOCOLS.contains(protocol)) {
|
||||
// Only Turn uses transport arg
|
||||
String rawTransportType = uri.substring(qmarkPos);
|
||||
hostAndPort = uri.substring(firstColonPos + 1, qmarkPos);
|
||||
if (!TCP_TRANSPORT_SUFFIX.equals(rawTransportType) && !UDP_TRANSPORT_SUFFIX.equals(rawTransportType)) {
|
||||
// If other argument rather than transport is specified, it is a wrong query for a STUN/TURN uri
|
||||
throw new IllegalArgumentException("Wrong value specified in STUN/TURN uri: '"
|
||||
+ uri + "'. " + "Unique valid arguments after '?' are '"
|
||||
+ TCP_TRANSPORT_SUFFIX + "' or '" + UDP_TRANSPORT_SUFFIX);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("STUN uri can't have any '?' query param");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if port is defined
|
||||
int portColon = hostAndPort.indexOf(':');
|
||||
// IPv6 are defined between brackets
|
||||
int startIpv6Index = hostAndPort.indexOf('[');
|
||||
int endIpv6Index = hostAndPort.indexOf(']');
|
||||
if (startIpv6Index == -1 ^ endIpv6Index == -1) {
|
||||
throw new IllegalArgumentException("Not closed bracket '[' or ']' in uri: " + uri);
|
||||
}
|
||||
|
||||
if (portColon != -1) {
|
||||
if (startIpv6Index == -1 && endIpv6Index == -1) {
|
||||
// If Ipv4 and port defined
|
||||
String[] splittedHostAndPort = hostAndPort.split(":");
|
||||
if (splittedHostAndPort.length != 2) {
|
||||
throw new IllegalArgumentException("Host or port are not correctly " +
|
||||
"defined in STUN/TURN uri: '" + uri + "'");
|
||||
}
|
||||
String host = splittedHostAndPort[0];
|
||||
String port = splittedHostAndPort[1];
|
||||
|
||||
// Check if host is defined. Valid Host (Domain or IP) will be done at server side
|
||||
checkHostAndPort(uri, host, port);
|
||||
} else {
|
||||
// If portColon is found and Ipv6
|
||||
String ipv6 = hostAndPort.substring(startIpv6Index + 1, endIpv6Index);
|
||||
String auxPort = hostAndPort.substring(endIpv6Index + 1);
|
||||
if (auxPort.startsWith(":")) {
|
||||
if (auxPort.length() == 1) {
|
||||
throw new IllegalArgumentException("Host or port are not correctly defined in STUN/TURN uri: " + uri);
|
||||
}
|
||||
// If port is defined
|
||||
// Get port without colon and check host and port
|
||||
String host = ipv6;
|
||||
String port = auxPort.substring(1);
|
||||
checkHostAndPort(uri, host, port);
|
||||
} else if (auxPort.length() > 0) {
|
||||
// If auxPort = 0, no port is defined
|
||||
throw new IllegalArgumentException("Port is not specified correctly after IPv6 in uri: '" + uri + "'");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If portColon not found, only host is defined
|
||||
String host = hostAndPort;
|
||||
checkHost(uri, host);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkHost(String uri, String host) {
|
||||
if (host == null || host.isEmpty()) {
|
||||
throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null");
|
||||
}
|
||||
if (DomainValidator.getInstance().isValid(host)) {
|
||||
return;
|
||||
}
|
||||
InetAddressValidator ipValidator = InetAddressValidator.getInstance();
|
||||
if (ipValidator.isValid(host)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Inet6Address.getByName(host).getHostAddress();
|
||||
return;
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IllegalArgumentException("Is not a valid Internet Address (IP or Domain Name): '" + host + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPort(String uri, String port) {
|
||||
if (port == null || port.isEmpty()) {
|
||||
throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null");
|
||||
}
|
||||
|
||||
try {
|
||||
int parsedPort = Integer.parseInt(port);
|
||||
if (parsedPort <= 0 || parsedPort > 65535) {
|
||||
throw new IllegalArgumentException("The port defined in '" + uri + "' is not a valid port number (0-65535)");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number (0-65535)");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkHostAndPort(String uri, String host, String port) {
|
||||
this.checkHost(uri, host);
|
||||
this.checkPort(uri, port);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
import { Publisher } from './Publisher';
|
||||
import { ConnectionProperties } from './ConnectionProperties';
|
||||
import { OpenViduRole } from './OpenViduRole';
|
||||
import { IceServerProperties } from './IceServerProperties';
|
||||
|
||||
/**
|
||||
* See [[Session.connections]]
|
||||
|
@ -138,6 +139,7 @@ export class Connection {
|
|||
this.connectionProperties.adaptativeBitrate = json.adaptativeBitrate;
|
||||
this.connectionProperties.onlyPlayWithSubscribers = json.onlyPlayWithSubscribers;
|
||||
this.connectionProperties.networkCache = json.networkCache;
|
||||
this.connectionProperties.customIceServers = json.customIceServers ?? []
|
||||
} else {
|
||||
this.connectionProperties = {
|
||||
type: json.type,
|
||||
|
@ -148,7 +150,8 @@ export class Connection {
|
|||
rtspUri: json.rtspUri,
|
||||
adaptativeBitrate: json.adaptativeBitrate,
|
||||
onlyPlayWithSubscribers: json.onlyPlayWithSubscribers,
|
||||
networkCache: json.networkCache
|
||||
networkCache: json.networkCache,
|
||||
customIceServers: json.customIceServers ?? []
|
||||
}
|
||||
}
|
||||
this.role = json.role;
|
||||
|
@ -224,6 +227,7 @@ export class Connection {
|
|||
this.connectionProperties.adaptativeBitrate === other.connectionProperties.adaptativeBitrate &&
|
||||
this.connectionProperties.onlyPlayWithSubscribers === other.connectionProperties.onlyPlayWithSubscribers &&
|
||||
this.connectionProperties.networkCache === other.connectionProperties.networkCache &&
|
||||
this.connectionProperties.customIceServers.length === other.connectionProperties.customIceServers.length &&
|
||||
this.token === other.token &&
|
||||
this.location === other.location &&
|
||||
this.ip === other.ip &&
|
||||
|
@ -238,6 +242,15 @@ export class Connection {
|
|||
equals = (this.connectionProperties.kurentoOptions === other.connectionProperties.kurentoOptions);
|
||||
}
|
||||
}
|
||||
if (equals) {
|
||||
if (this.connectionProperties.customIceServers != null) {
|
||||
// Order alphabetically Ice servers using url just to keep the same list order.
|
||||
const simpleIceComparator = (a: IceServerProperties, b: IceServerProperties) => (a.url > b.url) ? 1 : -1
|
||||
const sortedIceServers = this.connectionProperties.customIceServers.sort(simpleIceComparator);
|
||||
const sortedOtherIceServers = other.connectionProperties.customIceServers.sort(simpleIceComparator);
|
||||
equals = JSON.stringify(sortedIceServers) === JSON.stringify(sortedOtherIceServers);
|
||||
}
|
||||
}
|
||||
if (equals) {
|
||||
equals = JSON.stringify(this.subscribers.sort()) === JSON.stringify(other.subscribers.sort());
|
||||
if (equals) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { IceServerProperties } from './IceServerProperties';
|
||||
import { ConnectionType } from './ConnectionType';
|
||||
import { OpenViduRole } from './OpenViduRole';
|
||||
|
||||
|
@ -127,4 +128,30 @@ export interface ConnectionProperties {
|
|||
*/
|
||||
networkCache?: number;
|
||||
|
||||
/**
|
||||
* 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:
|
||||
*
|
||||
* 1. Configured ICE Server using Openvidu.setAdvancedCofiguration() at openvidu-browser.
|
||||
* 2. Configured ICE server at [[ConnectionProperties.customIceServers]].
|
||||
* 3. Configured ICE Server at global configuration parameter: `OPENVIDU_WEBRTC_ICE_SERVERS`.
|
||||
* 4. 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 [[ConnectionType.WEBRTC]]**
|
||||
*
|
||||
*/
|
||||
customIceServers?: IceServerProperties[];
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* (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.
|
||||
*
|
||||
*/
|
||||
|
||||
export interface IceServerProperties {
|
||||
|
||||
/**
|
||||
* Set the url for the ICE Server you want to use.
|
||||
* It should follow a valid format:
|
||||
*
|
||||
* - [https://datatracker.ietf.org/doc/html/rfc7065#section-3.1](https://datatracker.ietf.org/doc/html/rfc7065#section-3.1)
|
||||
* - [https://datatracker.ietf.org/doc/html/rfc7064#section-3.1](https://datatracker.ietf.org/doc/html/rfc7064#section-3.1)
|
||||
*
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* Set a username for the ICE Server you want to use.
|
||||
* This parameter should be defined only for TURN, not for STUN ICE Servers.
|
||||
*/
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* Set a credential for the ICE Server you want to use.
|
||||
* This parameter should be defined only for TURN, not for STUN ICE Servers.
|
||||
*/
|
||||
credential?: string;
|
||||
|
||||
}
|
|
@ -28,6 +28,7 @@ import { RecordingMode } from './RecordingMode';
|
|||
import { SessionProperties } from './SessionProperties';
|
||||
import { TokenOptions } from './TokenOptions';
|
||||
import { RecordingProperties } from 'RecordingProperties';
|
||||
import { IceServerProperties } from 'IceServerProperties';
|
||||
|
||||
export class Session {
|
||||
|
||||
|
@ -150,7 +151,8 @@ export class Session {
|
|||
rtspUri: (!!connectionProperties && !!connectionProperties.rtspUri) ? connectionProperties.rtspUri : null,
|
||||
adaptativeBitrate: !!connectionProperties ? connectionProperties.adaptativeBitrate : null,
|
||||
onlyPlayWithSubscribers: !!connectionProperties ? connectionProperties.onlyPlayWithSubscribers : null,
|
||||
networkCache: (!!connectionProperties && (connectionProperties.networkCache != null)) ? connectionProperties.networkCache : null
|
||||
networkCache: (!!connectionProperties && (connectionProperties.networkCache != null)) ? connectionProperties.networkCache : null,
|
||||
customIceServers: (!!connectionProperties && (!!connectionProperties.customIceServers != null)) ? connectionProperties.customIceServers : null
|
||||
});
|
||||
axios.post(
|
||||
this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection',
|
||||
|
@ -560,7 +562,6 @@ export class Session {
|
|||
// 1. Array to store fetched connections and later remove closed ones
|
||||
const fetchedConnectionIds: string[] = [];
|
||||
json.connections.content.forEach(jsonConnection => {
|
||||
|
||||
const connectionObj: Connection = new Connection(jsonConnection);
|
||||
fetchedConnectionIds.push(connectionObj.connectionId);
|
||||
let storedConnection = this.connections.find(c => c.connectionId === connectionObj.connectionId);
|
||||
|
@ -583,6 +584,16 @@ export class Session {
|
|||
|
||||
// Order connections by time of creation
|
||||
this.connections.sort((c1, c2) => (c1.createdAt > c2.createdAt) ? 1 : ((c2.createdAt > c1.createdAt) ? -1 : 0));
|
||||
|
||||
// Order Ice candidates in connection properties
|
||||
this.connections.forEach(connection => {
|
||||
if (connection.connectionProperties.customIceServers != null &&
|
||||
connection.connectionProperties.customIceServers.length > 0) {
|
||||
// Order alphabetically Ice servers using url just to keep the same list order.
|
||||
const simpleIceComparator = (a: IceServerProperties, b: IceServerProperties) => (a.url > b.url) ? 1 : -1
|
||||
connection.connectionProperties.customIceServers.sort(simpleIceComparator);
|
||||
}
|
||||
});
|
||||
// Populate activeConnections array
|
||||
this.updateActiveConnectionsArray();
|
||||
return this;
|
||||
|
|
|
@ -12,4 +12,5 @@ export * from './Recording';
|
|||
export * from './RecordingProperties';
|
||||
export * from './Connection';
|
||||
export * from './Publisher';
|
||||
export * from './VideoCodec';
|
||||
export * from './VideoCodec';
|
||||
export * from './IceServerProperties';
|
|
@ -38,6 +38,7 @@ import java.util.Map.Entry;
|
|||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import io.openvidu.java.client.IceServerProperties;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.validator.routines.DomainValidator;
|
||||
|
@ -220,10 +221,12 @@ public class OpenviduConfig {
|
|||
|
||||
private MediaServer mediaServerInfo = MediaServer.kurento;
|
||||
|
||||
// Media properties
|
||||
// Webrtc properties
|
||||
|
||||
private boolean webrtcSimulcast = false;
|
||||
|
||||
private List<IceServerProperties> webrtcIceServers;
|
||||
|
||||
// Plain config properties getters
|
||||
|
||||
public String getCoturnDatabaseDbname() {
|
||||
|
@ -290,6 +293,10 @@ public class OpenviduConfig {
|
|||
return this.webrtcSimulcast;
|
||||
}
|
||||
|
||||
public List<IceServerProperties> getWebrtcIceServers() {
|
||||
return webrtcIceServers;
|
||||
}
|
||||
|
||||
public String getOpenViduRecordingPath() {
|
||||
return this.openviduRecordingPath;
|
||||
}
|
||||
|
@ -619,6 +626,8 @@ public class OpenviduConfig {
|
|||
|
||||
checkCertificateType();
|
||||
|
||||
webrtcIceServers = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS");
|
||||
|
||||
}
|
||||
|
||||
private void checkCertificateType() {
|
||||
|
@ -1147,4 +1156,46 @@ public class OpenviduConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private List<IceServerProperties> loadWebrtcIceServers(String property) {
|
||||
String rawIceServers = asOptionalString(property);
|
||||
List<IceServerProperties> webrtcIceServers = new ArrayList<>();
|
||||
if (rawIceServers == null || rawIceServers.isEmpty()) {
|
||||
return webrtcIceServers;
|
||||
}
|
||||
List<String> arrayIceServers = asJsonStringsArray(property);
|
||||
for (String iceServerString : arrayIceServers) {
|
||||
try {
|
||||
IceServerProperties iceServerProperties = readIceServer(property, iceServerString);
|
||||
webrtcIceServers.add(iceServerProperties);
|
||||
} catch (Exception e) {
|
||||
addError(property, iceServerString + " is not a valid webrtc ice server: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return webrtcIceServers;
|
||||
}
|
||||
|
||||
private IceServerProperties readIceServer(String property, String iceServerString) {
|
||||
String url = null, username = null, credential = null;
|
||||
String[] iceServerPropList = iceServerString.split(",");
|
||||
for (String iceServerProp: iceServerPropList) {
|
||||
String[] iceServerPropEntry = iceServerProp.split("=");
|
||||
if (iceServerPropEntry.length == 2) {
|
||||
if (iceServerProp.startsWith("url=")) {
|
||||
url = iceServerPropEntry[1];
|
||||
} else if (iceServerProp.startsWith("username=")) {
|
||||
username = iceServerPropEntry[1];
|
||||
} else if (iceServerProp.startsWith("credential=")) {
|
||||
credential = iceServerPropEntry[1];
|
||||
} else {
|
||||
addError(property, "Wrong parameter: " + iceServerProp);
|
||||
}
|
||||
} else {
|
||||
addError(property, "Wrong parameter: " + iceServerProp);
|
||||
}
|
||||
}
|
||||
IceServerProperties iceServerProperties = new IceServerProperties.Builder()
|
||||
.url(url).username(username).credential(credential).build();
|
||||
return iceServerProperties;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.openvidu.java.client.IceServerProperties;
|
||||
import org.kurento.client.GenericMediaEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -186,6 +187,11 @@ public class SessionEventsHandler {
|
|||
}
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNIP_PARAM, openviduConfig.getCoturnIp());
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNPORT_PARAM, openviduConfig.getCoturnPort());
|
||||
List<IceServerProperties> customIceServers = participant.getToken().getCustomIceServers();
|
||||
if (customIceServers!= null && !customIceServers.isEmpty()) {
|
||||
result.add(ProtocolElements.PARTICIPANTJOINED_CUSTOM_ICE_SERVERS,
|
||||
participant.getToken().getCustomIceServersAsJson());
|
||||
}
|
||||
if (participant.getToken().getTurnCredentials() != null) {
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_TURNUSERNAME_PARAM,
|
||||
participant.getToken().getTurnCredentials().getUsername());
|
||||
|
|
|
@ -19,6 +19,7 @@ package io.openvidu.server.core;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
@ -51,6 +52,7 @@ import io.openvidu.java.client.KurentoOptions;
|
|||
import io.openvidu.java.client.OpenViduRole;
|
||||
import io.openvidu.java.client.Recording;
|
||||
import io.openvidu.java.client.SessionProperties;
|
||||
import io.openvidu.java.client.IceServerProperties;
|
||||
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
|
||||
import io.openvidu.server.config.OpenviduConfig;
|
||||
import io.openvidu.server.coturn.CoturnCredentialsService;
|
||||
|
@ -329,13 +331,13 @@ public abstract class SessionManager {
|
|||
}
|
||||
|
||||
public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record,
|
||||
KurentoOptions kurentoOptions) throws Exception {
|
||||
KurentoOptions kurentoOptions, List<IceServerProperties> customIceServers) throws Exception {
|
||||
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
|
||||
log.error("Data invalid format");
|
||||
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
|
||||
}
|
||||
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role,
|
||||
kurentoOptions);
|
||||
kurentoOptions, customIceServers);
|
||||
|
||||
// Internal dev feature: allows customizing connectionId
|
||||
if (serverMetadata.contains("openviduCustomConnectionId")) {
|
||||
|
|
|
@ -17,18 +17,18 @@
|
|||
|
||||
package io.openvidu.server.core;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import io.openvidu.java.client.*;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import io.openvidu.java.client.ConnectionProperties;
|
||||
import io.openvidu.java.client.ConnectionType;
|
||||
import io.openvidu.java.client.KurentoOptions;
|
||||
import io.openvidu.java.client.OpenViduRole;
|
||||
import io.openvidu.server.core.Participant.ParticipantStatus;
|
||||
import io.openvidu.server.coturn.TurnCredentials;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Token {
|
||||
|
||||
private String token;
|
||||
|
@ -77,7 +77,8 @@ public class Token {
|
|||
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(), newRecord,
|
||||
connectionProperties.getRole(), connectionProperties.getKurentoOptions(),
|
||||
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
|
||||
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache());
|
||||
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache(),
|
||||
connectionProperties.getCustomIceServers());
|
||||
}
|
||||
|
||||
public OpenViduRole getRole() {
|
||||
|
@ -88,7 +89,8 @@ public class Token {
|
|||
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(),
|
||||
connectionProperties.record(), newRole, connectionProperties.getKurentoOptions(),
|
||||
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
|
||||
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache());
|
||||
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache(),
|
||||
connectionProperties.getCustomIceServers());
|
||||
}
|
||||
|
||||
public KurentoOptions getKurentoOptions() {
|
||||
|
@ -118,6 +120,10 @@ public class Token {
|
|||
public String getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public List<IceServerProperties> getCustomIceServers() {
|
||||
return this.connectionProperties.getCustomIceServers();
|
||||
}
|
||||
|
||||
public void setConnectionId(String connectionId) {
|
||||
this.connectionId = connectionId;
|
||||
|
@ -138,6 +144,16 @@ public class Token {
|
|||
return json;
|
||||
}
|
||||
|
||||
public JsonArray getCustomIceServersAsJson() {
|
||||
JsonArray customIceServersJsonList = new JsonArray();
|
||||
if (this.connectionProperties.getCustomIceServers() != null) {
|
||||
this.connectionProperties.getCustomIceServers().forEach((customIceServer) -> {
|
||||
customIceServersJsonList.add(customIceServer.toJson());
|
||||
});
|
||||
}
|
||||
return customIceServersJsonList;
|
||||
}
|
||||
|
||||
public JsonObject toJsonAsParticipant() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("id", this.getConnectionId());
|
||||
|
@ -178,7 +194,7 @@ public class Token {
|
|||
|
||||
private void updateConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role,
|
||||
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
|
||||
Integer networkCache) {
|
||||
Integer networkCache, List<IceServerProperties> iceServerProperties) {
|
||||
ConnectionProperties.Builder builder = new ConnectionProperties.Builder();
|
||||
if (type != null) {
|
||||
builder.type(type);
|
||||
|
@ -207,6 +223,11 @@ public class Token {
|
|||
if (networkCache != null) {
|
||||
builder.networkCache(networkCache);
|
||||
}
|
||||
if (iceServerProperties != null) {
|
||||
for (IceServerProperties customIceServer: iceServerProperties) {
|
||||
builder.addCustomIceServer(customIceServer);
|
||||
}
|
||||
}
|
||||
this.connectionProperties = builder.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,12 +24,15 @@ import io.openvidu.java.client.ConnectionProperties;
|
|||
import io.openvidu.java.client.ConnectionType;
|
||||
import io.openvidu.java.client.KurentoOptions;
|
||||
import io.openvidu.java.client.OpenViduRole;
|
||||
import io.openvidu.java.client.IceServerProperties;
|
||||
import io.openvidu.server.OpenViduServer;
|
||||
import io.openvidu.server.config.OpenviduBuildInfo;
|
||||
import io.openvidu.server.config.OpenviduConfig;
|
||||
import io.openvidu.server.coturn.CoturnCredentialsService;
|
||||
import io.openvidu.server.coturn.TurnCredentials;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TokenGenerator {
|
||||
|
||||
@Autowired
|
||||
|
@ -42,7 +45,7 @@ public class TokenGenerator {
|
|||
protected OpenviduBuildInfo openviduBuildConfig;
|
||||
|
||||
public Token generateToken(String sessionId, String serverMetadata, boolean record, OpenViduRole role,
|
||||
KurentoOptions kurentoOptions) throws Exception {
|
||||
KurentoOptions kurentoOptions, List<IceServerProperties> customIceServers) throws Exception {
|
||||
String token = OpenViduServer.wsUrl;
|
||||
token += "?sessionId=" + sessionId;
|
||||
token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
|
||||
|
@ -51,8 +54,13 @@ public class TokenGenerator {
|
|||
if (this.openviduConfig.isTurnadminAvailable()) {
|
||||
turnCredentials = coturnCredentialsService.createUser();
|
||||
}
|
||||
ConnectionProperties connectionProperties = new ConnectionProperties.Builder().type(ConnectionType.WEBRTC)
|
||||
.data(serverMetadata).record(record).role(role).kurentoOptions(kurentoOptions).build();
|
||||
ConnectionProperties.Builder connectionPropertiesBuilder = new ConnectionProperties.Builder()
|
||||
.type(ConnectionType.WEBRTC).data(serverMetadata).record(record).role(role)
|
||||
.kurentoOptions(kurentoOptions);
|
||||
for (IceServerProperties customIceServer: customIceServers) {
|
||||
connectionPropertiesBuilder.addCustomIceServer(customIceServer);
|
||||
}
|
||||
ConnectionProperties connectionProperties = connectionPropertiesBuilder.build();
|
||||
return new Token(token, sessionId, connectionProperties, turnCredentials);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,11 +21,13 @@ import java.net.MalformedURLException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.openvidu.java.client.*;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -51,17 +53,7 @@ import com.google.gson.JsonParser;
|
|||
import io.openvidu.client.OpenViduException;
|
||||
import io.openvidu.client.OpenViduException.Code;
|
||||
import io.openvidu.client.internal.ProtocolElements;
|
||||
import io.openvidu.java.client.ConnectionProperties;
|
||||
import io.openvidu.java.client.ConnectionType;
|
||||
import io.openvidu.java.client.KurentoOptions;
|
||||
import io.openvidu.java.client.MediaMode;
|
||||
import io.openvidu.java.client.OpenViduRole;
|
||||
import io.openvidu.java.client.Recording.OutputMode;
|
||||
import io.openvidu.java.client.RecordingLayout;
|
||||
import io.openvidu.java.client.RecordingMode;
|
||||
import io.openvidu.java.client.RecordingProperties;
|
||||
import io.openvidu.java.client.SessionProperties;
|
||||
import io.openvidu.java.client.VideoCodec;
|
||||
import io.openvidu.server.config.OpenviduConfig;
|
||||
import io.openvidu.server.core.EndReason;
|
||||
import io.openvidu.server.core.IdentifierPrefixes;
|
||||
|
@ -662,7 +654,7 @@ public class SessionRestController {
|
|||
try {
|
||||
Token token = sessionManager.newToken(session, connectionProperties.getRole(),
|
||||
connectionProperties.getData(), connectionProperties.record(),
|
||||
connectionProperties.getKurentoOptions());
|
||||
connectionProperties.getKurentoOptions(), connectionProperties.getCustomIceServers());
|
||||
return new ResponseEntity<>(token.toJsonAsParticipant().toString(), RestUtils.getResponseHeaders(),
|
||||
HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
|
@ -914,6 +906,41 @@ public class SessionRestController {
|
|||
}
|
||||
}
|
||||
|
||||
// Custom Ice Servers
|
||||
JsonArray customIceServersJsonArray = null;
|
||||
if (params.get("customIceServers") != null) {
|
||||
try {
|
||||
customIceServersJsonArray = new Gson().toJsonTree(params.get("customIceServers"), List.class)
|
||||
.getAsJsonArray();
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Error in parameter 'customIceServersJson'. It is not a valid JSON object");
|
||||
}
|
||||
}
|
||||
if (customIceServersJsonArray != null) {
|
||||
try {
|
||||
for (int i = 0; i < customIceServersJsonArray.size(); i++) {
|
||||
JsonObject customIceServerJson = customIceServersJsonArray.get(i).getAsJsonObject();
|
||||
IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder();
|
||||
iceServerPropertiesBuilder.url(customIceServerJson.get("url").getAsString());
|
||||
if (customIceServerJson.has("username")) {
|
||||
iceServerPropertiesBuilder.username(customIceServerJson.get("username").getAsString());
|
||||
}
|
||||
if (customIceServerJson.has("credential")) {
|
||||
iceServerPropertiesBuilder.credential(customIceServerJson.get("credential").getAsString());
|
||||
}
|
||||
IceServerProperties iceServerProperties = iceServerPropertiesBuilder.build();
|
||||
builder.addCustomIceServer(iceServerProperties);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Type error in some parameter of 'customIceServers': " + e.getMessage());
|
||||
}
|
||||
} else if(!openviduConfig.getWebrtcIceServers().isEmpty()){
|
||||
// If not defined in connection, check if defined in openvidu config
|
||||
for (IceServerProperties iceServerProperties: openviduConfig.getWebrtcIceServers()) {
|
||||
builder.addCustomIceServer(iceServerProperties);
|
||||
}
|
||||
}
|
||||
|
||||
// Build WEBRTC options
|
||||
builder.role(role).kurentoOptions(kurentoOptions);
|
||||
|
||||
|
@ -939,6 +966,8 @@ public class SessionRestController {
|
|||
.onlyPlayWithSubscribers(onlyPlayWithSubscribers).networkCache(networkCache).build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
package io.openvidu.server.test.unit;
|
||||
|
||||
import io.openvidu.java.client.IceServerProperties;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class IceServerPropertiesTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("IceServerProperty exceptions tests")
|
||||
public void iceServerPropertiesExceptionTest() {
|
||||
// Wrong urls
|
||||
notValidIceServerTest(
|
||||
"wrongurl", null, null,
|
||||
"Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"wrongurl", "anyuser", null,
|
||||
"Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"wrongurl", null, "anypassword",
|
||||
"Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"wrongurl", "anyuser", "anypassword",
|
||||
"Not a valid TURN/STUN uri provided. No colons found in: 'wrongurl'"
|
||||
);
|
||||
|
||||
// Wrong prefixes
|
||||
notValidIceServerTest(
|
||||
"turnss:wrongurl", null, null,
|
||||
"The protocol 'turnss' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stunss:wrongurl", "anyuser", null,
|
||||
"The protocol 'stunss' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"anything:wrongurl", null, "anypassword",
|
||||
"The protocol 'anything' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
":", null, null,
|
||||
"The protocol '' is invalid. Only valid values are: [turn, turns] [stuns, stun]"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"", null, null,
|
||||
"Not a valid TURN/STUN uri provided. No colons found in: ''"
|
||||
);
|
||||
|
||||
// Try invalid host and ports
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com:99a99", null, null,
|
||||
"The port defined in 'stun:hostname.com:99a99' is not a number (0-65535)"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com:-1", null, null,
|
||||
"The port defined in 'stun:hostname.com:-1' is not a valid port number (0-65535)"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname:port:more", null, null,
|
||||
"Host or port are not correctly defined in STUN/TURN uri: 'stun:hostname:port:more'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com:port more", null, null,
|
||||
"The port defined in 'stun:hostname.com:port more' is not a number (0-65535)"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com:", null, null,
|
||||
"Host or port are not correctly defined in STUN/TURN uri: 'stun:hostname.com:'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:[1:2:3:4:5:6:7:8]junk:1000", null, null,
|
||||
"Port is not specified correctly after IPv6 in uri: 'stun:[1:2:3:4:5:6:7:8]junk:1000'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:[notvalid:]:1000", null, null,
|
||||
"Is not a valid Internet Address (IP or Domain Name): 'notvalid:'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun::5555", null, null,
|
||||
"Host defined in 'stun::5555' is empty or null"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:", null, null,
|
||||
"Host defined in 'stun:' is empty or null"
|
||||
);
|
||||
|
||||
// Illegal Uri tests according to RFC 3986 and RFC 7064 (URI schemes for STUN and TURN)
|
||||
notValidIceServerTest(
|
||||
"stun:/hostname.com", null, null,
|
||||
"Is not a valid Internet Address (IP or Domain Name): '/hostname.com'"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:?hostname.com", null, null,
|
||||
"STUN uri can't have any '?' query param"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:#hostname.com", null, null,
|
||||
"Is not a valid Internet Address (IP or Domain Name): '#hostname.com'"
|
||||
);
|
||||
|
||||
// illegal ?transport=xxx tests in turn uris
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com?transport=invalid", "anyuser", "anypassword",
|
||||
"Wrong value specified in STUN/TURN uri: 'turn:hostname.com?transport=invalid'. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com?transport=", "anyuser", "anypassword",
|
||||
"Wrong value specified in STUN/TURN uri: 'turn:hostname.com?transport='. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com?=", "anyuser", "anypassword",
|
||||
"Wrong value specified in STUN/TURN uri: 'turn:hostname.com?='. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com?", "anyuser", "anypassword",
|
||||
"Wrong value specified in STUN/TURN uri: 'turn:hostname.com?'. Unique valid arguments after '?' are '?transport=tcp' or '?transport=udp"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"?", "anyuser", "anypassword",
|
||||
"Not a valid TURN/STUN uri provided. No colons found in: '?'"
|
||||
);
|
||||
|
||||
// Transport can not be defined in STUN
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com?transport=tcp", null, null,
|
||||
"STUN uri can't have any '?' query param"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com?transport=udp", null, null,
|
||||
"STUN uri can't have any '?' query param"
|
||||
);
|
||||
|
||||
// Stun can not have credentials defined
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com", "username", "credential",
|
||||
"Credentials can not be defined while using stun."
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com", "username", "credential",
|
||||
"Credentials can not be defined while using stun."
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com", "username", null,
|
||||
"Credentials can not be defined while using stun."
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"stun:hostname.com", null, "credential",
|
||||
"Credentials can not be defined while using stun."
|
||||
);
|
||||
|
||||
// Turn must have credentials
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com", null, null,
|
||||
"Credentials must be defined while using turn"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com", "username", null,
|
||||
"Credentials must be defined while using turn"
|
||||
);
|
||||
notValidIceServerTest(
|
||||
"turn:hostname.com", null, "credential",
|
||||
"Credentials must be defined while using turn"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("IceServerProperty exceptions tests")
|
||||
public void iceServerPropertiesValidTest() {
|
||||
// Stun and stuns
|
||||
validIceServerTest("stun:hostname.com", null, null);
|
||||
validIceServerTest("stuns:hostname.com", null, null);
|
||||
|
||||
// Turn and turns
|
||||
validIceServerTest("turn:hostname.com", "anyuser", "credential");
|
||||
validIceServerTest("turns:hostname.com", "anyuser", "credential");
|
||||
|
||||
// Test IPv4/IPv6/hostname and with/without port
|
||||
validIceServerTest("stun:1.2.3.4:1234", null, null);
|
||||
validIceServerTest("stun:[1:2:3:4:5:6:7:8]:4321", null, null);
|
||||
validIceServerTest("stun:hostname.com:9999", null, null);
|
||||
validIceServerTest("stun:1.2.3.4", null, null);
|
||||
validIceServerTest("stun:[1:2:3:4:5:6:7:8]", null, null);
|
||||
validIceServerTest("stuns:1.2.3.4:1234", null, null);
|
||||
validIceServerTest("stuns:[1:2:3:4:5:6:7:8]:4321", null, null);
|
||||
validIceServerTest("stuns:hostname.com:9999", null, null);
|
||||
validIceServerTest("stuns:1.2.3.4", null, null);
|
||||
validIceServerTest("stuns:[1:2:3:4:5:6:7:8]", null, null);
|
||||
validIceServerTest("turn:1.2.3.4:1234", "anyuser", "credential");
|
||||
validIceServerTest("turn:[1:2:3:4:5:6:7:8]:4321", "anyuser", "credential");
|
||||
validIceServerTest("turn:hostname.com:9999", "anyuser", "credential");
|
||||
validIceServerTest("turn:1.2.3.4", "anyuser", "credential");
|
||||
validIceServerTest("turn:[1:2:3:4:5:6:7:8]", "anyuser", "credential");
|
||||
validIceServerTest("turns:1.2.3.4:1234", "anyuser", "credential");
|
||||
validIceServerTest("turns:[1:2:3:4:5:6:7:8]:4321", "anyuser", "credential");
|
||||
validIceServerTest("turns:hostname.com:9999", "anyuser", "credential");
|
||||
validIceServerTest("turns:1.2.3.4", "anyuser", "credential");
|
||||
validIceServerTest("turns:[1:2:3:4:5:6:7:8]", "anyuser", "credential");
|
||||
|
||||
// Test valid ?transport=tcp or ?transport=udp
|
||||
validIceServerTest("turn:hostname.com:1234?transport=tcp", "anyuser", "credential");
|
||||
validIceServerTest("turn:hostname.com?transport=udp", "anyuser", "credential");
|
||||
validIceServerTest("turn:1.2.3.4:1234?transport=tcp", "anyuser", "credential");
|
||||
validIceServerTest("turn:1.2.3.4?transport=udp", "anyuser", "credential");
|
||||
validIceServerTest("turn:[1:2:3:4:5:6:7:8]:4321?transport=udp", "anyuser", "credential");
|
||||
validIceServerTest("turn:[1:2:3:4:5:6:7:8]?transport=udp", "anyuser", "credential");
|
||||
}
|
||||
|
||||
private void validIceServerTest(String url, String username, String credential) {
|
||||
assertDoesNotThrow(() -> {
|
||||
IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder().url(url);
|
||||
if (username != null) {
|
||||
iceServerPropertiesBuilder.username(username);
|
||||
}
|
||||
if (credential != null) {
|
||||
iceServerPropertiesBuilder.credential(credential);
|
||||
}
|
||||
IceServerProperties iceServerProperties = iceServerPropertiesBuilder.build();
|
||||
assertEquals(url, iceServerProperties.getUrl());
|
||||
if (username != null) {
|
||||
assertEquals(username, iceServerProperties.getUsername());
|
||||
}
|
||||
if (credential != null) {
|
||||
assertEquals(credential, iceServerProperties.getCredential());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notValidIceServerTest(String url, String username, String credential, String expectedMessage) {
|
||||
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder().url(url);
|
||||
if (username != null) {
|
||||
iceServerPropertiesBuilder.username(username);
|
||||
}
|
||||
if (credential != null) {
|
||||
iceServerPropertiesBuilder.credential(credential);
|
||||
}
|
||||
iceServerPropertiesBuilder.build();
|
||||
});
|
||||
|
||||
String actualMessage = exception.getMessage();
|
||||
assertEquals(actualMessage, expectedMessage);
|
||||
}
|
||||
|
||||
}
|
|
@ -68,9 +68,9 @@ public class OpenViduTestE2e {
|
|||
protected static String MEDIA_SERVER_IMAGE = KURENTO_IMAGE + ":6.16.0";
|
||||
|
||||
final protected String DEFAULT_JSON_SESSION = "{'id':'STR','object':'session','sessionId':'STR','createdAt':0,'mediaMode':'STR','recordingMode':'STR','defaultRecordingProperties':{'hasVideo':true,'frameRate':25,'hasAudio':true,'shmSize':536870912,'name':'','outputMode':'COMPOSED','resolution':'1280x720','recordingLayout':'BEST_FIT'},'customSessionId':'STR','connections':{'numberOfElements':0,'content':[]},'recording':false,'forcedVideoCodec':'STR','forcedVideoCodecResolved':'STR','allowTranscoding':false}";
|
||||
final protected String DEFAULT_JSON_PENDING_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'pending','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':null,'location':null,'ip':null,'platform':null,'token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':null,'publishers':null,'subscribers':null}";
|
||||
final protected String DEFAULT_JSON_ACTIVE_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','ip':'STR','platform':'STR','token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':'STR','publishers':[],'subscribers':[]}";
|
||||
final protected String DEFAULT_JSON_IPCAM_CONNECTION = "{'id':'STR','object':'connection','type':'IPCAM','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','ip':'STR','platform':'IPCAM','token':null,'serverData':'STR','record':true,'role':null,'kurentoOptions':null,'rtspUri':'STR','adaptativeBitrate':true,'onlyPlayWithSubscribers':true,'networkCache':2000,'clientData':null,'publishers':[],'subscribers':[]}";
|
||||
final protected String DEFAULT_JSON_PENDING_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'pending','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':null,'location':null,'ip':null,'platform':null,'token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':null,'publishers':null,'subscribers':null, 'customIceServers':[]}";
|
||||
final protected String DEFAULT_JSON_ACTIVE_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','ip':'STR','platform':'STR','token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':'STR','publishers':[],'subscribers':[], 'customIceServers':[]}";
|
||||
final protected String DEFAULT_JSON_IPCAM_CONNECTION = "{'id':'STR','object':'connection','type':'IPCAM','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','ip':'STR','platform':'IPCAM','token':null,'serverData':'STR','record':true,'role':null,'kurentoOptions':null,'rtspUri':'STR','adaptativeBitrate':true,'onlyPlayWithSubscribers':true,'networkCache':2000,'clientData':null,'publishers':[],'subscribers':[], 'customIceServers':[]}";
|
||||
final protected String DEFAULT_JSON_TOKEN = "{'id':'STR','token':'STR','connectionId':'STR','createdAt':0,'session':'STR','role':'STR','data':'STR','kurentoOptions':{}}";
|
||||
|
||||
protected static String OPENVIDU_SECRET = "MY_SECRET";
|
||||
|
|
|
@ -35,6 +35,8 @@ import java.util.concurrent.TimeoutException;
|
|||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.*;
|
||||
import io.openvidu.java.client.*;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
@ -49,35 +51,15 @@ import org.openqa.selenium.Dimension;
|
|||
import org.openqa.selenium.Keys;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.interactions.Actions;
|
||||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mashape.unirest.http.HttpMethod;
|
||||
|
||||
import io.appium.java_client.AppiumDriver;
|
||||
import io.openvidu.java.client.Connection;
|
||||
import io.openvidu.java.client.ConnectionProperties;
|
||||
import io.openvidu.java.client.ConnectionType;
|
||||
import io.openvidu.java.client.KurentoOptions;
|
||||
import io.openvidu.java.client.MediaMode;
|
||||
import io.openvidu.java.client.OpenVidu;
|
||||
import io.openvidu.java.client.OpenViduHttpException;
|
||||
import io.openvidu.java.client.OpenViduJavaClientException;
|
||||
import io.openvidu.java.client.OpenViduRole;
|
||||
import io.openvidu.java.client.Publisher;
|
||||
import io.openvidu.java.client.Recording;
|
||||
import io.openvidu.java.client.Recording.OutputMode;
|
||||
import io.openvidu.java.client.RecordingLayout;
|
||||
import io.openvidu.java.client.RecordingMode;
|
||||
import io.openvidu.java.client.RecordingProperties;
|
||||
import io.openvidu.java.client.Session;
|
||||
import io.openvidu.java.client.SessionProperties;
|
||||
import io.openvidu.java.client.VideoCodec;
|
||||
import io.openvidu.test.browsers.BrowserUser;
|
||||
import io.openvidu.test.browsers.utils.BrowserNames;
|
||||
import io.openvidu.test.browsers.utils.CustomHttpClient;
|
||||
|
@ -3002,6 +2984,20 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
body = "{'type':'WEBRTC','role':'MODERATOR','data':true}";
|
||||
restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
||||
HttpStatus.SC_BAD_REQUEST);
|
||||
|
||||
// 400 - Test some not valid customIceServers configured
|
||||
body = "{'customIceServers': [{'url':'bad-ice-server'}]}";
|
||||
restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
||||
HttpStatus.SC_BAD_REQUEST);
|
||||
|
||||
body = "{'customIceServers': [{'url':'turn:bad-ice-server'}]}";
|
||||
restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
||||
HttpStatus.SC_BAD_REQUEST);
|
||||
|
||||
body = "{'customIceServers': [{'url':'bad-prefix:bad-ice-server'}]}";
|
||||
restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
||||
HttpStatus.SC_BAD_REQUEST);
|
||||
|
||||
// 200
|
||||
String kurentoOpts = "'kurentoOptions':{'videoMaxSendBandwidth':777,'allowedFilters':['GStreamerFilter']}";
|
||||
body = "{'type':'WEBRTC','role':'MODERATOR','data':'SERVER_DATA'," + kurentoOpts + "}";
|
||||
|
@ -3011,6 +3007,18 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
restClient.rest(HttpMethod.DELETE,
|
||||
"/openvidu/api/sessions/CUSTOM_SESSION_ID/connection/" + res.get("id").getAsString(),
|
||||
HttpStatus.SC_NO_CONTENT);
|
||||
|
||||
// 200 - Test some good Ice Servers configured
|
||||
String goodTurn = "{'url': 'turn:valid-domain.es', 'username': 'user', 'credential': 'pass'}";
|
||||
String goodStun = "{'url': 'stun:valid-domain.es:1234'}";
|
||||
body = "{ 'customIceServers': [" + goodTurn + "," + goodStun + "]}";
|
||||
res = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
||||
HttpStatus.SC_OK, true, false, true,
|
||||
mergeJson(DEFAULT_JSON_PENDING_CONNECTION, "{ 'customIceServers': [" + goodTurn + "," + goodStun+ "] }", new String[0]));
|
||||
restClient.rest(HttpMethod.DELETE,
|
||||
"/openvidu/api/sessions/CUSTOM_SESSION_ID/connection/" + res.get("id").getAsString(),
|
||||
HttpStatus.SC_NO_CONTENT);
|
||||
|
||||
// Default values
|
||||
res = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", "{}",
|
||||
HttpStatus.SC_OK);
|
||||
|
@ -4373,7 +4381,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
connectionJson.get("subscribers").getAsJsonArray().size() == 0);
|
||||
Assert.assertTrue("Wrong token Connection property",
|
||||
connectionJson.get("token").getAsString().contains(session.getSessionId()));
|
||||
Assert.assertEquals("Wrong number of keys in connectionProperties", 9, connectionProperties.keySet().size());
|
||||
Assert.assertEquals("Wrong number of keys in connectionProperties", 10, connectionProperties.keySet().size());
|
||||
Assert.assertTrue("Wrong customIceServer property", connectionProperties.get("customIceServers").getAsJsonArray().size() == 0);
|
||||
Assert.assertEquals("Wrong type property", ConnectionType.WEBRTC.name(),
|
||||
connectionProperties.get("type").getAsString());
|
||||
Assert.assertEquals("Wrong data property", "MY_SERVER_DATA", connectionProperties.get("data").getAsString());
|
||||
|
@ -4489,7 +4498,216 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
|||
Assert.assertFalse("Java fetch should be false", OV.fetch());
|
||||
checkNodeFetchChanged(user, false, true);
|
||||
checkNodeFetchChanged(user, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Custom Ice Server connection tests from openvidu-java-client")
|
||||
void customIceServerConnectionJavaClientTest() throws Exception {
|
||||
customIceServerTest("openvidu-java-client");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Custom Ice Server connection tests from openvidu-node-client")
|
||||
void customIceServerConnectionNodeClientTest() throws Exception {
|
||||
customIceServerTest("openvidu-node-client");
|
||||
}
|
||||
|
||||
private void customIceServerTest(String client) throws Exception {
|
||||
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
|
||||
|
||||
// ------
|
||||
// 1. Initialize connection with Custom Ice Servers from openvidu-java-client
|
||||
// ------
|
||||
Session session = OV.createSession();
|
||||
String sessionId1 = session.getSessionId();
|
||||
|
||||
Assert.assertFalse("Java fetch should be false", OV.fetch());
|
||||
Assert.assertFalse("Session fetch should be false", session.fetch());
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
WebElement sessionName1 = user.getDriver().findElement(By.id("session-name-input-0"));
|
||||
sessionName1.clear();
|
||||
sessionName1.sendKeys(session.getSessionId());
|
||||
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
|
||||
user.getEventManager().waitUntilEventReaches("connectionCreated", 1);
|
||||
user.getEventManager().waitUntilEventReaches("accessAllowed", 1);
|
||||
user.getEventManager().waitUntilEventReaches("streamCreated", 1);
|
||||
user.getEventManager().waitUntilEventReaches("streamPlaying", 1);
|
||||
|
||||
session.fetch();
|
||||
Assert.assertEquals(session.getActiveConnections().size(), 1);
|
||||
|
||||
// Add second user with connection using custom ice servers
|
||||
Connection connection1 = createConnWithCustomIceServer(session, user, client);
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
WebElement sessionName2 = user.getDriver().findElement(By.id("session-name-input-1"));
|
||||
sessionName2.clear();
|
||||
sessionName2.sendKeys(sessionId1);
|
||||
|
||||
user.getDriver().findElement(By.id("session-settings-btn-1")).click();
|
||||
Thread.sleep(1000);
|
||||
|
||||
WebElement token1Input = user.getDriver().findElement(By.cssSelector("#custom-token-div input"));
|
||||
token1Input.clear();
|
||||
token1Input.sendKeys(connection1.getToken());
|
||||
|
||||
user.getDriver().findElement(By.id("save-btn")).click();
|
||||
Thread.sleep(1000);
|
||||
user.getDriver().findElements(By.className("join-btn"))
|
||||
.stream().filter(el -> el.isEnabled()).forEach(el -> el.sendKeys(Keys.ENTER));
|
||||
user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
|
||||
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
|
||||
user.getEventManager().waitUntilEventReaches("streamCreated", 4);
|
||||
user.getEventManager().waitUntilEventReaches("streamPlaying", 4);
|
||||
|
||||
// ------
|
||||
// 2. Check that the IceServer is correctly setup on RTCPeerConnections created. This will ensure that the property
|
||||
// is reached in WebRTC browser related objects
|
||||
// ------
|
||||
List<WebElement> iceConfiguredButtons = user.getDriver().findElements(By.className("ice-config-button-" + connection1.getConnectionId()));
|
||||
for (WebElement iceConfigured : iceConfiguredButtons) {
|
||||
iceConfigured.click();
|
||||
Thread.sleep(1000);
|
||||
for(int i = 0; i < connection1.getCustomIceServers().size(); i++) {
|
||||
IceServerProperties customIceServer = connection1.getCustomIceServers().get(i);
|
||||
String foundIceUrl = user.getDriver().findElement(By.id("ice-server-url-" + i)).getText();
|
||||
String foundIceUsername = null, foundIceCred = null;
|
||||
List<WebElement> foundIceUsernameWebElem = user.getDriver().findElements(By.id("ice-server-username-" + i));
|
||||
List<WebElement> foundIceCredWebElem = user.getDriver().findElements(By.id("ice-server-credential-" + i));
|
||||
if (foundIceUsernameWebElem.size() == 1) {
|
||||
foundIceUsername = foundIceUsernameWebElem.get(0).getText();
|
||||
}
|
||||
if (foundIceCredWebElem.size() == 1) {
|
||||
foundIceCred = foundIceCredWebElem.get(0).getText();
|
||||
}
|
||||
Assert.assertEquals(foundIceUrl, customIceServer.getUrl());
|
||||
Assert.assertEquals(foundIceUsername, customIceServer.getUsername());
|
||||
Assert.assertEquals(foundIceCred, customIceServer.getCredential());
|
||||
}
|
||||
user.getDriver().findElement(By.id("close-dialog-btn")).click();
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
// ------
|
||||
// 3. Check that the data in openvidu-node-client is correctly fetched
|
||||
// ------
|
||||
user.getDriver().findElement(By.id("session-api-btn-0")).click();
|
||||
Thread.sleep(1000);
|
||||
checkNodeFetchChanged(user, false, true);
|
||||
checkNodeFetchChanged(user, false, false);
|
||||
checkNodeFetchChanged(user, true, false);
|
||||
|
||||
// ------
|
||||
// 4. Check if Ice Servers are correctly received in openvidu-node-client
|
||||
// ------
|
||||
user.getDriver().findElement(By.id("close-dialog-btn")).click();
|
||||
Thread.sleep(1000);
|
||||
user.getDriver().findElement(By.id("session-info-btn-0")).click();
|
||||
|
||||
// 4.1. Get all session data
|
||||
JsonObject res = JsonParser
|
||||
.parseString(user.getDriver().findElement(By.id("session-text-area")).getAttribute("value"))
|
||||
.getAsJsonObject();
|
||||
|
||||
JsonArray connectionsJsonArray = res.get("connections").getAsJsonArray();
|
||||
boolean foundConnection = false;
|
||||
for (JsonElement connectionJson: connectionsJsonArray) {
|
||||
// 4.2. Of all connections, get only the one created with openvidu-java-client with customIceServers added
|
||||
// Check if connection is the one configured from java client
|
||||
String connectionId = connectionJson.getAsJsonObject().get("connectionId").getAsString();
|
||||
if (connectionId.equals(connection1.getConnectionId())) {
|
||||
// 4.3. If connection with custom ice server is found, get the property with all customIceServers
|
||||
foundConnection = true;
|
||||
JsonArray customIceServersJsonArray = connectionJson.getAsJsonObject()
|
||||
.get("connectionProperties").getAsJsonObject()
|
||||
.get("customIceServers").getAsJsonArray();
|
||||
for (IceServerProperties customIceServer: connection1.getCustomIceServers()) {
|
||||
// 4.4 Compare both list, the one created with openvidu-java-client with the one received from openvidu-node-client
|
||||
boolean foundIceServer = false;
|
||||
Iterator<JsonElement> customIceServersJsonIterator = customIceServersJsonArray.iterator();
|
||||
while(customIceServersJsonIterator.hasNext() && !foundIceServer) {
|
||||
// 4.5 When the custom ICE server is found in the openvidu-node-client object, compare it with the
|
||||
// openvidu-java-client object
|
||||
JsonObject customIceJsonObject = customIceServersJsonIterator.next().getAsJsonObject();
|
||||
String url = customIceJsonObject.get("url").getAsString();
|
||||
if (url.equals(customIceServer.getUrl())) {
|
||||
foundIceServer = true;
|
||||
Assert.assertEquals(customIceServer.getUrl(), customIceJsonObject.get("url").getAsString());
|
||||
if (customIceJsonObject.get("username") != null) {
|
||||
Assert.assertEquals(customIceServer.getUsername(), customIceJsonObject.get("username").getAsString());
|
||||
}
|
||||
if (customIceJsonObject.get("credential") != null) {
|
||||
Assert.assertEquals(customIceServer.getCredential(), customIceJsonObject.get("credential").getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4.6 Assert that the custom Ice Server was found on the openvidu-node-client connection object
|
||||
Assert.assertTrue(foundIceServer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 4.7 Assert that the connection was found on the openvidu-node-client session object, to fail in case it was not registered
|
||||
Assert.assertTrue(foundConnection);
|
||||
}
|
||||
|
||||
private Connection createConnWithCustomIceServer(Session session, OpenViduTestappUser user, String fromClient)
|
||||
throws OpenViduJavaClientException, OpenViduHttpException, InterruptedException {
|
||||
if (fromClient.equals("openvidu-java-client")) {
|
||||
IceServerProperties iceServerProperties1 = new IceServerProperties.Builder()
|
||||
.url("turn:turn-server.com")
|
||||
.username("usertest")
|
||||
.credential("credtest")
|
||||
.build();
|
||||
IceServerProperties iceServerProperties2 = new IceServerProperties.Builder()
|
||||
.url("stun:1.2.3.4:1234")
|
||||
.build();
|
||||
ConnectionProperties connectionProperties = new ConnectionProperties.Builder()
|
||||
.addCustomIceServer(iceServerProperties1)
|
||||
.addCustomIceServer(iceServerProperties2)
|
||||
.build();
|
||||
return session.createConnection(connectionProperties);
|
||||
} else if (fromClient.equals("openvidu-node-client")) {
|
||||
user.getDriver().findElement(By.id("session-api-btn-0")).click();
|
||||
Thread.sleep(1000);
|
||||
user.getDriver().findElement(By.id("num-ice-servers-select")).click();
|
||||
Thread.sleep(500);
|
||||
user.getDriver().findElement(By.id("num-ice-servers-2")).click();
|
||||
Thread.sleep(500);
|
||||
WebElement iceUrl1 = user.getDriver().findElement(By.id("ice-server-url-0"));
|
||||
WebElement iceUsername1 = user.getDriver().findElement(By.id("ice-server-username-0"));
|
||||
WebElement iceCredential1 = user.getDriver().findElement(By.id("ice-server-credential-0"));
|
||||
WebElement iceUrl2 = user.getDriver().findElement(By.id("ice-server-url-1"));
|
||||
iceUrl1.clear();
|
||||
iceUsername1.clear();
|
||||
iceCredential1.clear();
|
||||
iceUrl2.clear();
|
||||
iceUrl1.sendKeys("turn:turn-server.com");
|
||||
iceUsername1.sendKeys("usertest");
|
||||
iceCredential1.sendKeys("credtest");
|
||||
iceUrl2.sendKeys("stun:1.2.3.4:1234");
|
||||
|
||||
// Create connection
|
||||
user.getDriver().findElement(By.id("crate-connection-api-btn")).click();
|
||||
Thread.sleep(1000);
|
||||
String responseAreaText = user.getDriver().findElement(By.id("api-response-text-area")).getAttribute("value");
|
||||
user.getDriver().findElement(By.id("close-dialog-btn")).click();
|
||||
String connectionCreatedPrefix = "Connection created: ";
|
||||
Assert.assertTrue(responseAreaText.startsWith(connectionCreatedPrefix));
|
||||
String connectionStringResponse = responseAreaText.split(connectionCreatedPrefix, 2)[1];
|
||||
JsonObject connectionJsonResponse = JsonParser.parseString(connectionStringResponse).getAsJsonObject();
|
||||
String connectionId = connectionJsonResponse.get("connectionId").getAsString();
|
||||
Assert.assertTrue(session.fetch());
|
||||
Assert.assertFalse(session.fetch());
|
||||
Assert.assertFalse(OV.fetch());
|
||||
for (Connection connection: session.getConnections()) {
|
||||
if (connection.getConnectionId().equals(connectionId)) {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -33,6 +33,7 @@ import { OpenviduParamsService } from './services/openvidu-params.service';
|
|||
import { TestFeedService } from './services/test-feed.service';
|
||||
import { MuteSubscribersService } from './services/mute-subscribers.service';
|
||||
import { SessionInfoDialogComponent } from "./components/dialogs/session-info-dialog/session-info-dialog.component";
|
||||
import { ShowIceServerConfiguredDialog } from './components/dialogs/show-configured-ice/show-configured-ice.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -53,6 +54,7 @@ import { SessionInfoDialogComponent } from "./components/dialogs/session-info-di
|
|||
ScenarioPropertiesDialogComponent,
|
||||
FilterDialogComponent,
|
||||
ShowCodecDialogComponent,
|
||||
ShowIceServerConfiguredDialog,
|
||||
SessionInfoDialogComponent,
|
||||
UsersTableComponent,
|
||||
TableVideoComponent
|
||||
|
@ -82,6 +84,7 @@ import { SessionInfoDialogComponent } from "./components/dialogs/session-info-di
|
|||
ScenarioPropertiesDialogComponent,
|
||||
FilterDialogComponent,
|
||||
ShowCodecDialogComponent,
|
||||
ShowIceServerConfiguredDialog,
|
||||
SessionInfoDialogComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
|
|
|
@ -39,3 +39,12 @@ mat-dialog-content button {
|
|||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#manual-turn-div {
|
||||
background-color: #f7f7f7;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid #00000026;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,27 @@
|
|||
<input matInput id="connection-data-field" placeholder="data" [(ngModel)]="connectionProperties.data">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field class="inner-text-input" [style.fontSize.px]=14 style="width:33%">
|
||||
<mat-label>Custom Ice Servers</mat-label>
|
||||
<mat-select [(ngModel)]="numCustomIceServers" id="num-ice-servers-select" (selectionChange)="changedNumIceServers(numCustomIceServers)">
|
||||
<mat-option *ngFor="let i of [0,1,2,3,4,5,6,7,8,9,10]" [value]="i">
|
||||
<span [attr.id]="'num-ice-servers-' + i">{{ i }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div id="manual-turn-div" *ngFor="let configuredIce of configuredCustomIceServers; let i = index">
|
||||
<mat-form-field style="width: 100%">
|
||||
<input matInput id="ice-server-url-{{i}}" placeholder="url" type="text" [(ngModel)]="configuredIce.url">
|
||||
</mat-form-field>
|
||||
<mat-form-field style="width: 48%; padding-right: 2px">
|
||||
<input matInput id="ice-server-username-{{i}}" placeholder="username" type="text" [(ngModel)]="configuredIce.username">
|
||||
</mat-form-field>
|
||||
<mat-form-field style="width: 48%; padding-left: 2px">
|
||||
<input matInput id="ice-server-credential-{{i}}" placeholder="credential" type="text" [(ngModel)]="configuredIce.credential">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button mat-button id="crate-connection-api-btn" (click)="createConnection()">Create connection</button>
|
||||
<button mat-button id="update-connection-api-btn" (click)="updateConnection()" [disabled]="!connectionId">Update
|
||||
|
|
|
@ -23,6 +23,8 @@ export class SessionApiDialogComponent {
|
|||
customLayout = '';
|
||||
recPropertiesIcon = 'add_circle';
|
||||
showRecProperties = false;
|
||||
numCustomIceServers = 0;
|
||||
configuredCustomIceServers = []
|
||||
|
||||
connectionProperties: ConnectionProperties = {
|
||||
record: true,
|
||||
|
@ -205,6 +207,7 @@ export class SessionApiDialogComponent {
|
|||
|
||||
createConnection() {
|
||||
console.log('Creating connection');
|
||||
this.connectionProperties.customIceServers = this.configuredCustomIceServers;
|
||||
this.session.createConnection(this.connectionProperties)
|
||||
.then(connection => {
|
||||
this.response = 'Connection created: ' + JSON.stringify(connection);
|
||||
|
@ -238,4 +241,23 @@ export class SessionApiDialogComponent {
|
|||
this.recPropertiesIcon = this.showRecProperties ? 'remove_circle' : 'add_circle';
|
||||
}
|
||||
|
||||
changedNumIceServers(numIceServers: number) {
|
||||
// Save Previous Ice Servers
|
||||
let previousIceServers = [];
|
||||
for (let i = 0; i < this.configuredCustomIceServers.length; i++) {
|
||||
previousIceServers.push(this.configuredCustomIceServers[i]);
|
||||
}
|
||||
|
||||
// Fill empty ice servers
|
||||
this.configuredCustomIceServers = []
|
||||
for(let i = 1; i <= numIceServers; i++) {
|
||||
this.configuredCustomIceServers.push({});
|
||||
}
|
||||
|
||||
// Add previous items
|
||||
for(let i = 0; i < previousIceServers.length && i < this.configuredCustomIceServers.length; i++) {
|
||||
this.configuredCustomIceServers[0] = previousIceServers[0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-ice-configured-dialog',
|
||||
template: `
|
||||
<div id="app-ice-configured-dialog-container">
|
||||
<ul id="ice-server-list">
|
||||
<div class="ice-server" *ngFor="let iceServer of iceServerList; index as i">
|
||||
<li id="ice-server-{{i}}">
|
||||
<p>
|
||||
ICE Server URL: <span id="ice-server-url-{{i}}">{{iceServer.urls}}</span> -
|
||||
Username: <span *ngIf="iceServer.username" id="ice-server-username-{{i}}">{{iceServer.username}}</span> -
|
||||
Credential: <span *ngIf="iceServer.credential" id="ice-server-credential-{{i}}">{{iceServer.credential}}</span>
|
||||
</p>
|
||||
</li>
|
||||
</div>
|
||||
<button mat-button id="close-dialog-btn" [mat-dialog-close]="{}">CLOSE</button>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
#app-ice-configured-dialog-container {
|
||||
text-align: center
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class ShowIceServerConfiguredDialog {
|
||||
|
||||
iceServerList: RTCIceServer[]
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<ShowIceServerConfiguredDialog>,
|
||||
@Inject(MAT_DIALOG_DATA) public data) {
|
||||
this.iceServerList = data.iceServerList;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,11 @@
|
|||
<button class="video-btn stats-button bottom-left-rounded" title="Peer Connection Stats" (click)="showCodecUsed()">
|
||||
<mat-icon aria-label="Peer Connection Stats" class="mat-icon material-icons" role="img" aria-hidden="true">info</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="OV.session.connection.connectionId"
|
||||
class="video-btn bottom-left-rounded ice-config-button-{{OV.session.connection.connectionId}}"
|
||||
title="Ice Server configuration" (click)="getConfiguredIceServer()">
|
||||
<mat-icon aria-label="Ice Server configuration" class="mat-icon material-icons" role="img" aria-hidden="true">storage</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bottom-div">
|
||||
<button class="video-btn pub-btn" title="Publish/Unpublish" (click)="pubUnpub()">
|
||||
|
@ -53,6 +58,11 @@
|
|||
<button class="video-btn stats-button bottom-left-rounded" title="Peer Connection Stats" (click)="showCodecUsed()">
|
||||
<mat-icon aria-label="Peer Connection Stats" class="mat-icon material-icons" role="img" aria-hidden="true">info</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="OV.session.connection.connectionId"
|
||||
class="video-btn bottom-left-rounded ice-config-button-{{OV.session.connection.connectionId}}"
|
||||
title="Ice Server configuration" (click)="getConfiguredIceServer()">
|
||||
<mat-icon aria-label="Ice Server configuration" class="mat-icon material-icons" role="img" aria-hidden="true">storage</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bottom-div">
|
||||
<button class="video-btn sub-btn" title="Subscribe/Unsubscribe" (click)="subUnsub()">
|
||||
|
|
|
@ -23,6 +23,7 @@ import { LocalRecordingDialogComponent } from '../dialogs/local-recording-dialog
|
|||
import { ExtensionDialogComponent } from '../dialogs/extension-dialog/extension-dialog.component';
|
||||
import { FilterDialogComponent } from '../dialogs/filter-dialog/filter-dialog.component';
|
||||
import { OpenViduEvent } from '../openvidu-instance/openvidu-instance.component';
|
||||
import { ShowIceServerConfiguredDialog } from '../dialogs/show-configured-ice/show-configured-ice.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video',
|
||||
|
@ -813,6 +814,16 @@ export class VideoComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
getConfiguredIceServer() {
|
||||
let iceServerList: RTCIceServer[] = this.streamManager.stream.getWebRtcPeer().pc.getConfiguration().iceServers;
|
||||
this.dialog.open(ShowIceServerConfiguredDialog, {
|
||||
data: {
|
||||
iceServerList: iceServerList
|
||||
},
|
||||
width: '450px'
|
||||
})
|
||||
}
|
||||
|
||||
async showCodecUsed() {
|
||||
let stats = await this.streamManager.stream.getWebRtcPeer().pc.getStats();
|
||||
let codecIdIndex = null;
|
||||
|
|
Loading…
Reference in New Issue