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) {
|
private processJoinRoomResponse(opts: LocalConnectionOptions) {
|
||||||
this.sessionId = opts.session;
|
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;
|
const turnUrl1 = 'turn:' + opts.coturnIp + ':' + opts.coturnPort;
|
||||||
this.openvidu.iceServers = [
|
this.openvidu.iceServers = [
|
||||||
{ urls: [turnUrl1], username: opts.turnUsername, credential: opts.turnCredential }
|
{ 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 { RemoteConnectionOptions } from './RemoteConnectionOptions';
|
||||||
|
import { IceServerProperties } from './IceServerProperties';
|
||||||
|
|
||||||
export interface LocalConnectionOptions {
|
export interface LocalConnectionOptions {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -35,4 +36,5 @@ export interface LocalConnectionOptions {
|
||||||
mediaServer: string;
|
mediaServer: string;
|
||||||
videoSimulcast: boolean;
|
videoSimulcast: boolean;
|
||||||
life: number;
|
life: number;
|
||||||
|
customIceServers?: IceServerProperties[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,7 @@ public class ProtocolElements {
|
||||||
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
||||||
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
||||||
public static final String PARTICIPANTJOINED_COTURNPORT_PARAM = "coturnPort";
|
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_TURNUSERNAME_PARAM = "turnUsername";
|
||||||
public static final String PARTICIPANTJOINED_TURNCREDENTIAL_PARAM = "turnCredential";
|
public static final String PARTICIPANTJOINED_TURNCREDENTIAL_PARAM = "turnCredential";
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,11 @@
|
||||||
<version>${version.junit}</version>
|
<version>${version.junit}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-validator</groupId>
|
||||||
|
<artifactId>commons-validator</artifactId>
|
||||||
|
<version>${version.commons-validator}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -25,7 +25,9 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link io.openvidu.java.client.Session#getConnections()}
|
* See {@link io.openvidu.java.client.Session#getConnections()}
|
||||||
|
@ -190,6 +192,19 @@ public class Connection {
|
||||||
return this.connectionProperties.getNetworkCache();
|
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
|
* 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
|
* 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) {
|
if (this.connectionProperties.getNetworkCache() != null) {
|
||||||
builder.networkCache(this.connectionProperties.getNetworkCache());
|
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();
|
this.connectionProperties = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,6 +435,24 @@ public class Connection {
|
||||||
? OpenViduRole.valueOf(json.get("role").getAsString())
|
? OpenViduRole.valueOf(json.get("role").getAsString())
|
||||||
: null;
|
: 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
|
// IPCAM
|
||||||
String rtspUri = (json.has("rtspUri") && !json.get("rtspUri").isJsonNull()) ? json.get("rtspUri").getAsString()
|
String rtspUri = (json.has("rtspUri") && !json.get("rtspUri").isJsonNull()) ? json.get("rtspUri").getAsString()
|
||||||
: null;
|
: null;
|
||||||
|
@ -428,8 +466,9 @@ public class Connection {
|
||||||
Integer networkCache = (json.has("networkCache") && !json.get("networkCache").isJsonNull())
|
Integer networkCache = (json.has("networkCache") && !json.get("networkCache").isJsonNull())
|
||||||
? json.get("networkCache").getAsInt()
|
? json.get("networkCache").getAsInt()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
this.connectionProperties = new ConnectionProperties(type, data, record, role, null, rtspUri, adaptativeBitrate,
|
this.connectionProperties = new ConnectionProperties(type, data, record, role, null, rtspUri, adaptativeBitrate,
|
||||||
onlyPlayWithSubscribers, networkCache);
|
onlyPlayWithSubscribers, networkCache, customIceServers);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package io.openvidu.java.client;
|
package io.openvidu.java.client;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonNull;
|
import com.google.gson.JsonNull;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See
|
* See
|
||||||
* {@link io.openvidu.java.client.Session#createConnection(ConnectionProperties)}
|
* {@link io.openvidu.java.client.Session#createConnection(ConnectionProperties)}
|
||||||
|
@ -22,6 +26,9 @@ public class ConnectionProperties {
|
||||||
private Boolean onlyPlayWithSubscribers;
|
private Boolean onlyPlayWithSubscribers;
|
||||||
private Integer networkCache;
|
private Integer networkCache;
|
||||||
|
|
||||||
|
// External Turn Service
|
||||||
|
private List<IceServerProperties> customIceServers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Builder for {@link io.openvidu.java.client.ConnectionProperties}
|
* Builder for {@link io.openvidu.java.client.ConnectionProperties}
|
||||||
|
@ -36,18 +43,21 @@ public class ConnectionProperties {
|
||||||
// WEBRTC
|
// WEBRTC
|
||||||
private OpenViduRole role;
|
private OpenViduRole role;
|
||||||
private KurentoOptions kurentoOptions;
|
private KurentoOptions kurentoOptions;
|
||||||
|
private List<IceServerProperties> customIceServers = new ArrayList<>();
|
||||||
// IPCAM
|
// IPCAM
|
||||||
private String rtspUri;
|
private String rtspUri;
|
||||||
private Boolean adaptativeBitrate;
|
private Boolean adaptativeBitrate;
|
||||||
private Boolean onlyPlayWithSubscribers;
|
private Boolean onlyPlayWithSubscribers;
|
||||||
private Integer networkCache;
|
private Integer networkCache;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for {@link io.openvidu.java.client.ConnectionProperties}.
|
* Builder for {@link io.openvidu.java.client.ConnectionProperties}.
|
||||||
*/
|
*/
|
||||||
public ConnectionProperties build() {
|
public ConnectionProperties build() {
|
||||||
return new ConnectionProperties(this.type, this.data, this.record, this.role, this.kurentoOptions,
|
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;
|
this.networkCache = networkCache;
|
||||||
return this;
|
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,
|
ConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role,
|
||||||
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
|
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
|
||||||
Integer networkCache) {
|
Integer networkCache, List<IceServerProperties> customIceServers) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.record = record;
|
this.record = record;
|
||||||
|
@ -233,6 +274,7 @@ public class ConnectionProperties {
|
||||||
this.adaptativeBitrate = adaptativeBitrate;
|
this.adaptativeBitrate = adaptativeBitrate;
|
||||||
this.onlyPlayWithSubscribers = onlyPlayWithSubscribers;
|
this.onlyPlayWithSubscribers = onlyPlayWithSubscribers;
|
||||||
this.networkCache = networkCache;
|
this.networkCache = networkCache;
|
||||||
|
this.customIceServers = customIceServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -346,6 +388,19 @@ public class ConnectionProperties {
|
||||||
return this.networkCache;
|
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) {
|
public JsonObject toJson(String sessionId) {
|
||||||
JsonObject json = new JsonObject();
|
JsonObject json = new JsonObject();
|
||||||
json.addProperty("session", sessionId);
|
json.addProperty("session", sessionId);
|
||||||
|
@ -376,6 +431,12 @@ public class ConnectionProperties {
|
||||||
} else {
|
} else {
|
||||||
json.add("kurentoOptions", JsonNull.INSTANCE);
|
json.add("kurentoOptions", JsonNull.INSTANCE);
|
||||||
}
|
}
|
||||||
|
JsonArray customIceServersJsonList = new JsonArray();
|
||||||
|
customIceServers.forEach((customIceServer) -> {
|
||||||
|
customIceServersJsonList.add(customIceServer.toJson());
|
||||||
|
});
|
||||||
|
json.add("customIceServers", customIceServersJsonList);
|
||||||
|
|
||||||
// IPCAM
|
// IPCAM
|
||||||
if (getRtspUri() != null) {
|
if (getRtspUri() != null) {
|
||||||
json.addProperty("rtspUri", getRtspUri());
|
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 { Publisher } from './Publisher';
|
||||||
import { ConnectionProperties } from './ConnectionProperties';
|
import { ConnectionProperties } from './ConnectionProperties';
|
||||||
import { OpenViduRole } from './OpenViduRole';
|
import { OpenViduRole } from './OpenViduRole';
|
||||||
|
import { IceServerProperties } from './IceServerProperties';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [[Session.connections]]
|
* See [[Session.connections]]
|
||||||
|
@ -138,6 +139,7 @@ export class Connection {
|
||||||
this.connectionProperties.adaptativeBitrate = json.adaptativeBitrate;
|
this.connectionProperties.adaptativeBitrate = json.adaptativeBitrate;
|
||||||
this.connectionProperties.onlyPlayWithSubscribers = json.onlyPlayWithSubscribers;
|
this.connectionProperties.onlyPlayWithSubscribers = json.onlyPlayWithSubscribers;
|
||||||
this.connectionProperties.networkCache = json.networkCache;
|
this.connectionProperties.networkCache = json.networkCache;
|
||||||
|
this.connectionProperties.customIceServers = json.customIceServers ?? []
|
||||||
} else {
|
} else {
|
||||||
this.connectionProperties = {
|
this.connectionProperties = {
|
||||||
type: json.type,
|
type: json.type,
|
||||||
|
@ -148,7 +150,8 @@ export class Connection {
|
||||||
rtspUri: json.rtspUri,
|
rtspUri: json.rtspUri,
|
||||||
adaptativeBitrate: json.adaptativeBitrate,
|
adaptativeBitrate: json.adaptativeBitrate,
|
||||||
onlyPlayWithSubscribers: json.onlyPlayWithSubscribers,
|
onlyPlayWithSubscribers: json.onlyPlayWithSubscribers,
|
||||||
networkCache: json.networkCache
|
networkCache: json.networkCache,
|
||||||
|
customIceServers: json.customIceServers ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.role = json.role;
|
this.role = json.role;
|
||||||
|
@ -224,6 +227,7 @@ export class Connection {
|
||||||
this.connectionProperties.adaptativeBitrate === other.connectionProperties.adaptativeBitrate &&
|
this.connectionProperties.adaptativeBitrate === other.connectionProperties.adaptativeBitrate &&
|
||||||
this.connectionProperties.onlyPlayWithSubscribers === other.connectionProperties.onlyPlayWithSubscribers &&
|
this.connectionProperties.onlyPlayWithSubscribers === other.connectionProperties.onlyPlayWithSubscribers &&
|
||||||
this.connectionProperties.networkCache === other.connectionProperties.networkCache &&
|
this.connectionProperties.networkCache === other.connectionProperties.networkCache &&
|
||||||
|
this.connectionProperties.customIceServers.length === other.connectionProperties.customIceServers.length &&
|
||||||
this.token === other.token &&
|
this.token === other.token &&
|
||||||
this.location === other.location &&
|
this.location === other.location &&
|
||||||
this.ip === other.ip &&
|
this.ip === other.ip &&
|
||||||
|
@ -238,6 +242,15 @@ export class Connection {
|
||||||
equals = (this.connectionProperties.kurentoOptions === other.connectionProperties.kurentoOptions);
|
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) {
|
if (equals) {
|
||||||
equals = JSON.stringify(this.subscribers.sort()) === JSON.stringify(other.subscribers.sort());
|
equals = JSON.stringify(this.subscribers.sort()) === JSON.stringify(other.subscribers.sort());
|
||||||
if (equals) {
|
if (equals) {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IceServerProperties } from './IceServerProperties';
|
||||||
import { ConnectionType } from './ConnectionType';
|
import { ConnectionType } from './ConnectionType';
|
||||||
import { OpenViduRole } from './OpenViduRole';
|
import { OpenViduRole } from './OpenViduRole';
|
||||||
|
|
||||||
|
@ -127,4 +128,30 @@ export interface ConnectionProperties {
|
||||||
*/
|
*/
|
||||||
networkCache?: number;
|
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 { SessionProperties } from './SessionProperties';
|
||||||
import { TokenOptions } from './TokenOptions';
|
import { TokenOptions } from './TokenOptions';
|
||||||
import { RecordingProperties } from 'RecordingProperties';
|
import { RecordingProperties } from 'RecordingProperties';
|
||||||
|
import { IceServerProperties } from 'IceServerProperties';
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
|
|
||||||
|
@ -150,7 +151,8 @@ export class Session {
|
||||||
rtspUri: (!!connectionProperties && !!connectionProperties.rtspUri) ? connectionProperties.rtspUri : null,
|
rtspUri: (!!connectionProperties && !!connectionProperties.rtspUri) ? connectionProperties.rtspUri : null,
|
||||||
adaptativeBitrate: !!connectionProperties ? connectionProperties.adaptativeBitrate : null,
|
adaptativeBitrate: !!connectionProperties ? connectionProperties.adaptativeBitrate : null,
|
||||||
onlyPlayWithSubscribers: !!connectionProperties ? connectionProperties.onlyPlayWithSubscribers : 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(
|
axios.post(
|
||||||
this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection',
|
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
|
// 1. Array to store fetched connections and later remove closed ones
|
||||||
const fetchedConnectionIds: string[] = [];
|
const fetchedConnectionIds: string[] = [];
|
||||||
json.connections.content.forEach(jsonConnection => {
|
json.connections.content.forEach(jsonConnection => {
|
||||||
|
|
||||||
const connectionObj: Connection = new Connection(jsonConnection);
|
const connectionObj: Connection = new Connection(jsonConnection);
|
||||||
fetchedConnectionIds.push(connectionObj.connectionId);
|
fetchedConnectionIds.push(connectionObj.connectionId);
|
||||||
let storedConnection = this.connections.find(c => c.connectionId === 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
|
// Order connections by time of creation
|
||||||
this.connections.sort((c1, c2) => (c1.createdAt > c2.createdAt) ? 1 : ((c2.createdAt > c1.createdAt) ? -1 : 0));
|
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
|
// Populate activeConnections array
|
||||||
this.updateActiveConnectionsArray();
|
this.updateActiveConnectionsArray();
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -12,4 +12,5 @@ export * from './Recording';
|
||||||
export * from './RecordingProperties';
|
export * from './RecordingProperties';
|
||||||
export * from './Connection';
|
export * from './Connection';
|
||||||
export * from './Publisher';
|
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 javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import io.openvidu.java.client.IceServerProperties;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.validator.routines.DomainValidator;
|
import org.apache.commons.validator.routines.DomainValidator;
|
||||||
|
@ -220,10 +221,12 @@ public class OpenviduConfig {
|
||||||
|
|
||||||
private MediaServer mediaServerInfo = MediaServer.kurento;
|
private MediaServer mediaServerInfo = MediaServer.kurento;
|
||||||
|
|
||||||
// Media properties
|
// Webrtc properties
|
||||||
|
|
||||||
private boolean webrtcSimulcast = false;
|
private boolean webrtcSimulcast = false;
|
||||||
|
|
||||||
|
private List<IceServerProperties> webrtcIceServers;
|
||||||
|
|
||||||
// Plain config properties getters
|
// Plain config properties getters
|
||||||
|
|
||||||
public String getCoturnDatabaseDbname() {
|
public String getCoturnDatabaseDbname() {
|
||||||
|
@ -290,6 +293,10 @@ public class OpenviduConfig {
|
||||||
return this.webrtcSimulcast;
|
return this.webrtcSimulcast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<IceServerProperties> getWebrtcIceServers() {
|
||||||
|
return webrtcIceServers;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOpenViduRecordingPath() {
|
public String getOpenViduRecordingPath() {
|
||||||
return this.openviduRecordingPath;
|
return this.openviduRecordingPath;
|
||||||
}
|
}
|
||||||
|
@ -619,6 +626,8 @@ public class OpenviduConfig {
|
||||||
|
|
||||||
checkCertificateType();
|
checkCertificateType();
|
||||||
|
|
||||||
|
webrtcIceServers = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCertificateType() {
|
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.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import io.openvidu.java.client.IceServerProperties;
|
||||||
import org.kurento.client.GenericMediaEvent;
|
import org.kurento.client.GenericMediaEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -186,6 +187,11 @@ public class SessionEventsHandler {
|
||||||
}
|
}
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNIP_PARAM, openviduConfig.getCoturnIp());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNIP_PARAM, openviduConfig.getCoturnIp());
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_COTURNPORT_PARAM, openviduConfig.getCoturnPort());
|
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) {
|
if (participant.getToken().getTurnCredentials() != null) {
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_TURNUSERNAME_PARAM,
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_TURNUSERNAME_PARAM,
|
||||||
participant.getToken().getTurnCredentials().getUsername());
|
participant.getToken().getTurnCredentials().getUsername());
|
||||||
|
|
|
@ -19,6 +19,7 @@ package io.openvidu.server.core;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
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.OpenViduRole;
|
||||||
import io.openvidu.java.client.Recording;
|
import io.openvidu.java.client.Recording;
|
||||||
import io.openvidu.java.client.SessionProperties;
|
import io.openvidu.java.client.SessionProperties;
|
||||||
|
import io.openvidu.java.client.IceServerProperties;
|
||||||
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
|
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.coturn.CoturnCredentialsService;
|
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,
|
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)) {
|
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
|
||||||
log.error("Data invalid format");
|
log.error("Data invalid format");
|
||||||
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
|
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
|
||||||
}
|
}
|
||||||
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role,
|
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), serverMetadata, record, role,
|
||||||
kurentoOptions);
|
kurentoOptions, customIceServers);
|
||||||
|
|
||||||
// Internal dev feature: allows customizing connectionId
|
// Internal dev feature: allows customizing connectionId
|
||||||
if (serverMetadata.contains("openviduCustomConnectionId")) {
|
if (serverMetadata.contains("openviduCustomConnectionId")) {
|
||||||
|
|
|
@ -17,18 +17,18 @@
|
||||||
|
|
||||||
package io.openvidu.server.core;
|
package io.openvidu.server.core;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import io.openvidu.java.client.*;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
|
||||||
import com.google.gson.JsonNull;
|
import com.google.gson.JsonNull;
|
||||||
import com.google.gson.JsonObject;
|
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.core.Participant.ParticipantStatus;
|
||||||
import io.openvidu.server.coturn.TurnCredentials;
|
import io.openvidu.server.coturn.TurnCredentials;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class Token {
|
public class Token {
|
||||||
|
|
||||||
private String token;
|
private String token;
|
||||||
|
@ -77,7 +77,8 @@ public class Token {
|
||||||
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(), newRecord,
|
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(), newRecord,
|
||||||
connectionProperties.getRole(), connectionProperties.getKurentoOptions(),
|
connectionProperties.getRole(), connectionProperties.getKurentoOptions(),
|
||||||
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
|
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
|
||||||
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache());
|
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache(),
|
||||||
|
connectionProperties.getCustomIceServers());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenViduRole getRole() {
|
public OpenViduRole getRole() {
|
||||||
|
@ -88,7 +89,8 @@ public class Token {
|
||||||
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(),
|
this.updateConnectionProperties(connectionProperties.getType(), connectionProperties.getData(),
|
||||||
connectionProperties.record(), newRole, connectionProperties.getKurentoOptions(),
|
connectionProperties.record(), newRole, connectionProperties.getKurentoOptions(),
|
||||||
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
|
connectionProperties.getRtspUri(), connectionProperties.adaptativeBitrate(),
|
||||||
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache());
|
connectionProperties.onlyPlayWithSubscribers(), connectionProperties.getNetworkCache(),
|
||||||
|
connectionProperties.getCustomIceServers());
|
||||||
}
|
}
|
||||||
|
|
||||||
public KurentoOptions getKurentoOptions() {
|
public KurentoOptions getKurentoOptions() {
|
||||||
|
@ -118,6 +120,10 @@ public class Token {
|
||||||
public String getConnectionId() {
|
public String getConnectionId() {
|
||||||
return connectionId;
|
return connectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<IceServerProperties> getCustomIceServers() {
|
||||||
|
return this.connectionProperties.getCustomIceServers();
|
||||||
|
}
|
||||||
|
|
||||||
public void setConnectionId(String connectionId) {
|
public void setConnectionId(String connectionId) {
|
||||||
this.connectionId = connectionId;
|
this.connectionId = connectionId;
|
||||||
|
@ -138,6 +144,16 @@ public class Token {
|
||||||
return json;
|
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() {
|
public JsonObject toJsonAsParticipant() {
|
||||||
JsonObject json = new JsonObject();
|
JsonObject json = new JsonObject();
|
||||||
json.addProperty("id", this.getConnectionId());
|
json.addProperty("id", this.getConnectionId());
|
||||||
|
@ -178,7 +194,7 @@ public class Token {
|
||||||
|
|
||||||
private void updateConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role,
|
private void updateConnectionProperties(ConnectionType type, String data, Boolean record, OpenViduRole role,
|
||||||
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
|
KurentoOptions kurentoOptions, String rtspUri, Boolean adaptativeBitrate, Boolean onlyPlayWithSubscribers,
|
||||||
Integer networkCache) {
|
Integer networkCache, List<IceServerProperties> iceServerProperties) {
|
||||||
ConnectionProperties.Builder builder = new ConnectionProperties.Builder();
|
ConnectionProperties.Builder builder = new ConnectionProperties.Builder();
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
builder.type(type);
|
builder.type(type);
|
||||||
|
@ -207,6 +223,11 @@ public class Token {
|
||||||
if (networkCache != null) {
|
if (networkCache != null) {
|
||||||
builder.networkCache(networkCache);
|
builder.networkCache(networkCache);
|
||||||
}
|
}
|
||||||
|
if (iceServerProperties != null) {
|
||||||
|
for (IceServerProperties customIceServer: iceServerProperties) {
|
||||||
|
builder.addCustomIceServer(customIceServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.connectionProperties = builder.build();
|
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.ConnectionType;
|
||||||
import io.openvidu.java.client.KurentoOptions;
|
import io.openvidu.java.client.KurentoOptions;
|
||||||
import io.openvidu.java.client.OpenViduRole;
|
import io.openvidu.java.client.OpenViduRole;
|
||||||
|
import io.openvidu.java.client.IceServerProperties;
|
||||||
import io.openvidu.server.OpenViduServer;
|
import io.openvidu.server.OpenViduServer;
|
||||||
import io.openvidu.server.config.OpenviduBuildInfo;
|
import io.openvidu.server.config.OpenviduBuildInfo;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.coturn.CoturnCredentialsService;
|
import io.openvidu.server.coturn.CoturnCredentialsService;
|
||||||
import io.openvidu.server.coturn.TurnCredentials;
|
import io.openvidu.server.coturn.TurnCredentials;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class TokenGenerator {
|
public class TokenGenerator {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -42,7 +45,7 @@ public class TokenGenerator {
|
||||||
protected OpenviduBuildInfo openviduBuildConfig;
|
protected OpenviduBuildInfo openviduBuildConfig;
|
||||||
|
|
||||||
public Token generateToken(String sessionId, String serverMetadata, boolean record, OpenViduRole role,
|
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;
|
String token = OpenViduServer.wsUrl;
|
||||||
token += "?sessionId=" + sessionId;
|
token += "?sessionId=" + sessionId;
|
||||||
token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
|
token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
|
||||||
|
@ -51,8 +54,13 @@ public class TokenGenerator {
|
||||||
if (this.openviduConfig.isTurnadminAvailable()) {
|
if (this.openviduConfig.isTurnadminAvailable()) {
|
||||||
turnCredentials = coturnCredentialsService.createUser();
|
turnCredentials = coturnCredentialsService.createUser();
|
||||||
}
|
}
|
||||||
ConnectionProperties connectionProperties = new ConnectionProperties.Builder().type(ConnectionType.WEBRTC)
|
ConnectionProperties.Builder connectionPropertiesBuilder = new ConnectionProperties.Builder()
|
||||||
.data(serverMetadata).record(record).role(role).kurentoOptions(kurentoOptions).build();
|
.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);
|
return new Token(token, sessionId, connectionProperties, turnCredentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,13 @@ import java.net.MalformedURLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import io.openvidu.java.client.*;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -51,17 +53,7 @@ import com.google.gson.JsonParser;
|
||||||
import io.openvidu.client.OpenViduException;
|
import io.openvidu.client.OpenViduException;
|
||||||
import io.openvidu.client.OpenViduException.Code;
|
import io.openvidu.client.OpenViduException.Code;
|
||||||
import io.openvidu.client.internal.ProtocolElements;
|
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.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.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.IdentifierPrefixes;
|
import io.openvidu.server.core.IdentifierPrefixes;
|
||||||
|
@ -662,7 +654,7 @@ public class SessionRestController {
|
||||||
try {
|
try {
|
||||||
Token token = sessionManager.newToken(session, connectionProperties.getRole(),
|
Token token = sessionManager.newToken(session, connectionProperties.getRole(),
|
||||||
connectionProperties.getData(), connectionProperties.record(),
|
connectionProperties.getData(), connectionProperties.record(),
|
||||||
connectionProperties.getKurentoOptions());
|
connectionProperties.getKurentoOptions(), connectionProperties.getCustomIceServers());
|
||||||
return new ResponseEntity<>(token.toJsonAsParticipant().toString(), RestUtils.getResponseHeaders(),
|
return new ResponseEntity<>(token.toJsonAsParticipant().toString(), RestUtils.getResponseHeaders(),
|
||||||
HttpStatus.OK);
|
HttpStatus.OK);
|
||||||
} catch (Exception e) {
|
} 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
|
// Build WEBRTC options
|
||||||
builder.role(role).kurentoOptions(kurentoOptions);
|
builder.role(role).kurentoOptions(kurentoOptions);
|
||||||
|
|
||||||
|
@ -939,6 +966,8 @@ public class SessionRestController {
|
||||||
.onlyPlayWithSubscribers(onlyPlayWithSubscribers).networkCache(networkCache).build();
|
.onlyPlayWithSubscribers(onlyPlayWithSubscribers).networkCache(networkCache).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return builder;
|
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";
|
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_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_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':[]}";
|
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':[]}";
|
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':{}}";
|
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";
|
protected static String OPENVIDU_SECRET = "MY_SECRET";
|
||||||
|
|
|
@ -35,6 +35,8 @@ import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import io.openvidu.java.client.*;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
@ -49,35 +51,15 @@ import org.openqa.selenium.Dimension;
|
||||||
import org.openqa.selenium.Keys;
|
import org.openqa.selenium.Keys;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.interactions.Actions;
|
||||||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||||
import org.openqa.selenium.support.ui.ExpectedConditions;
|
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
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 com.mashape.unirest.http.HttpMethod;
|
||||||
|
|
||||||
import io.appium.java_client.AppiumDriver;
|
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.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.BrowserUser;
|
||||||
import io.openvidu.test.browsers.utils.BrowserNames;
|
import io.openvidu.test.browsers.utils.BrowserNames;
|
||||||
import io.openvidu.test.browsers.utils.CustomHttpClient;
|
import io.openvidu.test.browsers.utils.CustomHttpClient;
|
||||||
|
@ -3002,6 +2984,20 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
body = "{'type':'WEBRTC','role':'MODERATOR','data':true}";
|
body = "{'type':'WEBRTC','role':'MODERATOR','data':true}";
|
||||||
restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", body,
|
||||||
HttpStatus.SC_BAD_REQUEST);
|
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
|
// 200
|
||||||
String kurentoOpts = "'kurentoOptions':{'videoMaxSendBandwidth':777,'allowedFilters':['GStreamerFilter']}";
|
String kurentoOpts = "'kurentoOptions':{'videoMaxSendBandwidth':777,'allowedFilters':['GStreamerFilter']}";
|
||||||
body = "{'type':'WEBRTC','role':'MODERATOR','data':'SERVER_DATA'," + kurentoOpts + "}";
|
body = "{'type':'WEBRTC','role':'MODERATOR','data':'SERVER_DATA'," + kurentoOpts + "}";
|
||||||
|
@ -3011,6 +3007,18 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
restClient.rest(HttpMethod.DELETE,
|
restClient.rest(HttpMethod.DELETE,
|
||||||
"/openvidu/api/sessions/CUSTOM_SESSION_ID/connection/" + res.get("id").getAsString(),
|
"/openvidu/api/sessions/CUSTOM_SESSION_ID/connection/" + res.get("id").getAsString(),
|
||||||
HttpStatus.SC_NO_CONTENT);
|
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
|
// Default values
|
||||||
res = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", "{}",
|
res = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/CUSTOM_SESSION_ID/connection", "{}",
|
||||||
HttpStatus.SC_OK);
|
HttpStatus.SC_OK);
|
||||||
|
@ -4373,7 +4381,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
connectionJson.get("subscribers").getAsJsonArray().size() == 0);
|
connectionJson.get("subscribers").getAsJsonArray().size() == 0);
|
||||||
Assert.assertTrue("Wrong token Connection property",
|
Assert.assertTrue("Wrong token Connection property",
|
||||||
connectionJson.get("token").getAsString().contains(session.getSessionId()));
|
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(),
|
Assert.assertEquals("Wrong type property", ConnectionType.WEBRTC.name(),
|
||||||
connectionProperties.get("type").getAsString());
|
connectionProperties.get("type").getAsString());
|
||||||
Assert.assertEquals("Wrong data property", "MY_SERVER_DATA", connectionProperties.get("data").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());
|
Assert.assertFalse("Java fetch should be false", OV.fetch());
|
||||||
checkNodeFetchChanged(user, false, true);
|
checkNodeFetchChanged(user, false, true);
|
||||||
checkNodeFetchChanged(user, false, false);
|
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);
|
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
|
@Test
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { OpenviduParamsService } from './services/openvidu-params.service';
|
||||||
import { TestFeedService } from './services/test-feed.service';
|
import { TestFeedService } from './services/test-feed.service';
|
||||||
import { MuteSubscribersService } from './services/mute-subscribers.service';
|
import { MuteSubscribersService } from './services/mute-subscribers.service';
|
||||||
import { SessionInfoDialogComponent } from "./components/dialogs/session-info-dialog/session-info-dialog.component";
|
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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -53,6 +54,7 @@ import { SessionInfoDialogComponent } from "./components/dialogs/session-info-di
|
||||||
ScenarioPropertiesDialogComponent,
|
ScenarioPropertiesDialogComponent,
|
||||||
FilterDialogComponent,
|
FilterDialogComponent,
|
||||||
ShowCodecDialogComponent,
|
ShowCodecDialogComponent,
|
||||||
|
ShowIceServerConfiguredDialog,
|
||||||
SessionInfoDialogComponent,
|
SessionInfoDialogComponent,
|
||||||
UsersTableComponent,
|
UsersTableComponent,
|
||||||
TableVideoComponent
|
TableVideoComponent
|
||||||
|
@ -82,6 +84,7 @@ import { SessionInfoDialogComponent } from "./components/dialogs/session-info-di
|
||||||
ScenarioPropertiesDialogComponent,
|
ScenarioPropertiesDialogComponent,
|
||||||
FilterDialogComponent,
|
FilterDialogComponent,
|
||||||
ShowCodecDialogComponent,
|
ShowCodecDialogComponent,
|
||||||
|
ShowIceServerConfiguredDialog,
|
||||||
SessionInfoDialogComponent
|
SessionInfoDialogComponent
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|
|
@ -39,3 +39,12 @@ mat-dialog-content button {
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 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">
|
<input matInput id="connection-data-field" placeholder="data" [(ngModel)]="connectionProperties.data">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<button mat-button id="crate-connection-api-btn" (click)="createConnection()">Create connection</button>
|
<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
|
<button mat-button id="update-connection-api-btn" (click)="updateConnection()" [disabled]="!connectionId">Update
|
||||||
|
|
|
@ -23,6 +23,8 @@ export class SessionApiDialogComponent {
|
||||||
customLayout = '';
|
customLayout = '';
|
||||||
recPropertiesIcon = 'add_circle';
|
recPropertiesIcon = 'add_circle';
|
||||||
showRecProperties = false;
|
showRecProperties = false;
|
||||||
|
numCustomIceServers = 0;
|
||||||
|
configuredCustomIceServers = []
|
||||||
|
|
||||||
connectionProperties: ConnectionProperties = {
|
connectionProperties: ConnectionProperties = {
|
||||||
record: true,
|
record: true,
|
||||||
|
@ -205,6 +207,7 @@ export class SessionApiDialogComponent {
|
||||||
|
|
||||||
createConnection() {
|
createConnection() {
|
||||||
console.log('Creating connection');
|
console.log('Creating connection');
|
||||||
|
this.connectionProperties.customIceServers = this.configuredCustomIceServers;
|
||||||
this.session.createConnection(this.connectionProperties)
|
this.session.createConnection(this.connectionProperties)
|
||||||
.then(connection => {
|
.then(connection => {
|
||||||
this.response = 'Connection created: ' + JSON.stringify(connection);
|
this.response = 'Connection created: ' + JSON.stringify(connection);
|
||||||
|
@ -238,4 +241,23 @@ export class SessionApiDialogComponent {
|
||||||
this.recPropertiesIcon = this.showRecProperties ? 'remove_circle' : 'add_circle';
|
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()">
|
<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>
|
<mat-icon aria-label="Peer Connection Stats" class="mat-icon material-icons" role="img" aria-hidden="true">info</mat-icon>
|
||||||
</button>
|
</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>
|
||||||
<div class="bottom-div">
|
<div class="bottom-div">
|
||||||
<button class="video-btn pub-btn" title="Publish/Unpublish" (click)="pubUnpub()">
|
<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()">
|
<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>
|
<mat-icon aria-label="Peer Connection Stats" class="mat-icon material-icons" role="img" aria-hidden="true">info</mat-icon>
|
||||||
</button>
|
</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>
|
||||||
<div class="bottom-div">
|
<div class="bottom-div">
|
||||||
<button class="video-btn sub-btn" title="Subscribe/Unsubscribe" (click)="subUnsub()">
|
<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 { ExtensionDialogComponent } from '../dialogs/extension-dialog/extension-dialog.component';
|
||||||
import { FilterDialogComponent } from '../dialogs/filter-dialog/filter-dialog.component';
|
import { FilterDialogComponent } from '../dialogs/filter-dialog/filter-dialog.component';
|
||||||
import { OpenViduEvent } from '../openvidu-instance/openvidu-instance.component';
|
import { OpenViduEvent } from '../openvidu-instance/openvidu-instance.component';
|
||||||
|
import { ShowIceServerConfiguredDialog } from '../dialogs/show-configured-ice/show-configured-ice.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-video',
|
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() {
|
async showCodecUsed() {
|
||||||
let stats = await this.streamManager.stream.getWebRtcPeer().pc.getStats();
|
let stats = await this.streamManager.stream.getWebRtcPeer().pc.getStats();
|
||||||
let codecIdIndex = null;
|
let codecIdIndex = null;
|
||||||
|
|
Loading…
Reference in New Issue