diff --git a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java index 5d2f72ef..2f819127 100644 --- a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java +++ b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java @@ -50,6 +50,7 @@ public class ProtocolElements { public static final String PUBLISHVIDEO_SDPOFFER_PARAM = "sdpOffer"; public static final String PUBLISHVIDEO_DOLOOPBACK_PARAM = "doLoopback"; public static final String PUBLISHVIDEO_SDPANSWER_PARAM = "sdpAnswer"; + public static final String PUBLISHVIDEO_STREAMID_PARAM = "id"; public static final String PUBLISHVIDEO_AUDIOACTIVE_PARAM = "audioActive"; public static final String PUBLISHVIDEO_VIDEOACTIVE_PARAM = "videoActive"; public static final String PUBLISHVIDEO_TYPEOFVIDEO_PARAM = "typeOfVideo"; diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java index 4fa60599..ea7558ed 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java @@ -36,15 +36,17 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { // Security for API REST - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry conf = http.csrf().disable() - .authorizeRequests().antMatchers(HttpMethod.POST, "/api/sessions").authenticated() + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry conf = http.cors().and() + .csrf().disable().authorizeRequests().antMatchers(HttpMethod.POST, "/api/sessions").authenticated() + .antMatchers(HttpMethod.POST, "/api/sessions/**").authenticated() .antMatchers(HttpMethod.POST, "/api/tokens").authenticated() .antMatchers(HttpMethod.POST, "/api/recordings/start").authenticated() .antMatchers(HttpMethod.POST, "/api/recordings/stop").authenticated() .antMatchers(HttpMethod.GET, "/api/recordings").authenticated() .antMatchers(HttpMethod.GET, "/api/recordings/**").authenticated() .antMatchers(HttpMethod.DELETE, "/api/recordings/**").authenticated() - .antMatchers(HttpMethod.GET, "/config/**").authenticated().antMatchers("/").authenticated(); + .antMatchers(HttpMethod.GET, "/config/openvidu-publicurl").anonymous() + .antMatchers(HttpMethod.GET, "/config/**").authenticated(); // Security for layouts conf.antMatchers("/layouts/*").authenticated(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Participant.java b/openvidu-server/src/main/java/io/openvidu/server/core/Participant.java index 57cfc132..257cfbe7 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/Participant.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/Participant.java @@ -17,6 +17,8 @@ package io.openvidu.server.core; +import org.json.simple.JSONObject; + public class Participant { private String participantPrivatetId; // ID to identify the user on server (org.kurento.jsonrpc.Session.id) @@ -119,7 +121,7 @@ public class Participant { public void setTypeOfVideo(String typeOfVideo) { this.typeOfVideo = typeOfVideo; } - + public int getFrameRate() { return this.frameRate; } @@ -127,6 +129,10 @@ public class Participant { public void setFrameRate(int frameRate) { this.frameRate = frameRate; } + + public String getPublisherStremId() { + return null; + } public String getFullMetadata() { String fullMetadata; @@ -194,4 +200,12 @@ public class Participant { return builder.toString(); } + @SuppressWarnings("unchecked") + public JSONObject toJSON() { + JSONObject json = new JSONObject(); + json.put("connectionId", this.participantPublicId); + json.put("token", this.token.toJSON()); + return json; + } + } diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Session.java b/openvidu-server/src/main/java/io/openvidu/server/core/Session.java index e25c16e1..8476e8a9 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/Session.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/Session.java @@ -19,6 +19,8 @@ package io.openvidu.server.core; import java.util.Set; +import org.json.simple.JSONObject; + import io.openvidu.java.client.SessionProperties; public interface Session { @@ -42,5 +44,7 @@ public interface Session { Participant getParticipantByPublicId(String participantPublicId); int getActivePublishers(); + + JSONObject toJSON(); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java index 6e6a00d5..802c21ce 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java @@ -94,19 +94,9 @@ public class SessionEventsHandler { existingParticipant.getFullMetadata()); if (existingParticipant.isStreaming()) { - - String streamId = ""; - if ("SCREEN".equals(existingParticipant.getTypeOfVideo())) { - streamId = "SCREEN"; - } else if (existingParticipant.isVideoActive()) { - streamId = "CAMERA"; - } else if (existingParticipant.isAudioActive()) { - streamId = "MICRO"; - } - JsonObject stream = new JsonObject(); stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMID_PARAM, - existingParticipant.getParticipantPublicId() + "_" + streamId); + existingParticipant.getPublisherStremId()); stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM, existingParticipant.isAudioActive()); stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM, @@ -178,7 +168,7 @@ public class SessionEventsHandler { } } - public void onPublishMedia(Participant participant, String sessionId, MediaOptions mediaOptions, String sdpAnswer, + public void onPublishMedia(Participant participant, String streamId, String sessionId, MediaOptions mediaOptions, String sdpAnswer, Set participants, Integer transactionId, OpenViduException error) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); @@ -186,23 +176,14 @@ public class SessionEventsHandler { } JsonObject result = new JsonObject(); result.addProperty(ProtocolElements.PUBLISHVIDEO_SDPANSWER_PARAM, sdpAnswer); + result.addProperty(ProtocolElements.PUBLISHVIDEO_STREAMID_PARAM, streamId); rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, result); JsonObject params = new JsonObject(); params.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_USER_PARAM, participant.getParticipantPublicId()); JsonObject stream = new JsonObject(); - String streamId = ""; - if ("SCREEN".equals(mediaOptions.typeOfVideo)) { - streamId = "SCREEN"; - } else if (mediaOptions.videoActive) { - streamId = "CAMERA"; - } else if (mediaOptions.audioActive) { - streamId = "MICRO"; - } - - stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_STREAMID_PARAM, - participant.getParticipantPublicId() + "_" + streamId); + stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_STREAMID_PARAM, streamId); stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_AUDIOACTIVE_PARAM, mediaOptions.audioActive); stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_VIDEOACTIVE_PARAM, mediaOptions.videoActive); stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_TYPEOFVIDEO_PARAM, mediaOptions.typeOfVideo); diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java index 63dde4f6..d3996ab2 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java @@ -17,6 +17,7 @@ package io.openvidu.server.core; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -116,6 +117,15 @@ public abstract class SessionManager { return new HashSet(sessions.keySet()); } + /** + * Returns all currently active (opened) sessions. + * + * @return set of the session's identifiers + */ + public Collection getSessionObjects() { + return sessions.values(); + } + /** * Returns all the participants inside a session. * @@ -193,81 +203,6 @@ public abstract class SessionManager { public String newToken(String sessionId, ParticipantRole role, String serverMetadata) throws OpenViduException { - /*if (!isMetadataFormatCorrect(serverMetadata)) { - log.error("Data invalid format. Max length allowed is 10000 chars"); - throw new OpenViduException(Code.GENERIC_ERROR_CODE, - "Data invalid format. Max length allowed is 10000 chars"); - } - - String token = OpenViduServer.publicUrl; - token += "?sessionId=" + sessionId; - token += "&token=" + this.generateRandomChain(); - token += "&role=" + role.name(); - TurnCredentials turnCredentials = null; - if (this.coturnCredentialsService.isCoturnAvailable()) { - turnCredentials = coturnCredentialsService.createUser(); - if (turnCredentials != null) { - if (turnCredentials != null) { - token += "&turnUsername=" + turnCredentials.getUsername(); - token += "&turnCredential=" + turnCredentials.getCredential(); - } - } - } - Token t = new Token(token, role, serverMetadata, turnCredentials); - - final String finalToken = token; - - ConcurrentHashMap tok = this.sessionidTokenTokenobj.computeIfPresent(sessionId, (key, value) -> { - value.putIfAbsent(finalToken, t); - return value; - }); - - if (tok == null) { - log.error("sessionId [" + sessionId + "] is not valid"); - throw new OpenViduException(Code.ROOM_NOT_FOUND_ERROR_CODE, "sessionId [" + sessionId + "] not found"); - } else { - return tok.get(token).getToken(); - }*/ - - - /*if (!isMetadataFormatCorrect(serverMetadata)) { - log.error("Data invalid format. Max length allowed is 10000 chars"); - throw new OpenViduException(Code.GENERIC_ERROR_CODE, - "Data invalid format. Max length allowed is 10000 chars"); - } - - final String[] tokenArray = {""}; - - try { - sessionidTokenTokenobj.computeIfPresent(sessionId, (key, value) -> { - String token = OpenViduServer.publicUrl; - token += "?sessionId=" + sessionId; - token += "&token=" + this.generateRandomChain(); - token += "&role=" + role.name(); - TurnCredentials turnCredentials = null; - if (this.coturnCredentialsService.isCoturnAvailable()) { - turnCredentials = coturnCredentialsService.createUser(); - if (turnCredentials != null) { - token += "&turnUsername=" + turnCredentials.getUsername(); - token += "&turnCredential=" + turnCredentials.getCredential(); - } - } - Token t = new Token(token, role, serverMetadata, turnCredentials); - value.putIfAbsent(token, t); - tokenArray[0] = token; - throw new RuntimeException(); - }); - } catch(RuntimeException e) { - log.info("Token succesfully created"); - } - - if (tokenArray[0].isEmpty()) { - log.error("sessionId [" + sessionId + "] is not valid"); - throw new OpenViduException(Code.ROOM_NOT_FOUND_ERROR_CODE, "sessionId [" + sessionId + "] not found"); - } - - return tokenArray[0];*/ - ConcurrentHashMap map = this.sessionidTokenTokenobj.putIfAbsent(sessionId, new ConcurrentHashMap<>()); if (map != null) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Token.java b/openvidu-server/src/main/java/io/openvidu/server/core/Token.java index f411d34c..9a2d586f 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/Token.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/Token.java @@ -17,14 +17,16 @@ package io.openvidu.server.core; +import org.json.simple.JSONObject; + import io.openvidu.server.coturn.TurnCredentials; public class Token { - String token; - ParticipantRole role; - String serverMetadata = ""; - TurnCredentials turnCredentials; + private String token; + private ParticipantRole role; + private String serverMetadata = ""; + private TurnCredentials turnCredentials; public Token(String token) { this.token = token; @@ -60,5 +62,14 @@ public class Token { else return this.token; } + + @SuppressWarnings("unchecked") + public JSONObject toJSON() { + JSONObject json = new JSONObject(); + json.put("token", this.token); + json.put("role", this.role.name()); + json.put("data", this.serverMetadata); + return json; + } } \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java index 0d7d038c..c274b9fb 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java @@ -24,6 +24,9 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.RandomStringUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; import org.kurento.client.Continuation; import org.kurento.client.ErrorEvent; import org.kurento.client.Filter; @@ -85,14 +88,17 @@ public class KurentoParticipant extends Participant { } public void createPublishingEndpoint(MediaOptions mediaOptions) { + publisher.createEndpoint(endPointLatch); if (getPublisher().getEndpoint() == null) { throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create publisher endpoint"); } - this.publisher.getEndpoint().addTag("name", "PUBLISHER " + this.getParticipantPublicId()); - - addEndpointListeners(this.publisher); + String publisherStreamId = this.getParticipantPublicId() + "_" + + (mediaOptions.videoActive ? mediaOptions.typeOfVideo : "MICRO") + "_" + + RandomStringUtils.random(5, true, false).toUpperCase(); + this.publisher.getEndpoint().addTag("name", publisherStreamId); + addEndpointListeners(this.publisher); CDR.recordNewPublisher(this, this.session.getSessionId(), mediaOptions); @@ -279,8 +285,9 @@ public class KurentoParticipant extends Participant { throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint"); } - subscriber.getEndpoint().addTag("name", - "SUBSCRIBER " + senderName + " for user " + this.getParticipantPublicId()); + String subscriberStreamId = this.getParticipantPublicId() + "_" + kSender.getPublisherStremId(); + + subscriber.getEndpoint().addTag("name", subscriberStreamId); addEndpointListeners(subscriber); @@ -515,7 +522,7 @@ public class KurentoParticipant extends Participant { * System.out.println(msg); this.infoHandler.sendInfo(msg); }); */ - endpoint.getWebEndpoint().addErrorListener((event) -> { + /*endpoint.getWebEndpoint().addErrorListener((event) -> { String msg = " Error (PUBLISHER) -> " + "ERRORCODE: " + event.getErrorCode() + " | DESCRIPTION: " + event.getDescription() + " | TIMESTAMP: " + System.currentTimeMillis(); log.debug(msg); @@ -620,8 +627,42 @@ public class KurentoParticipant extends Participant { + " | TIMESTAMP: " + System.currentTimeMillis(); log.debug(msg); this.infoHandler.sendInfo(msg); + });*/ + + endpoint.getWebEndpoint().addNewCandidatePairSelectedListener((event) -> { + endpoint.selectedLocalIceCandidate = event.getCandidatePair().getLocalCandidate(); + endpoint.selectedRemoteIceCandidate = event.getCandidatePair().getRemoteCandidate(); + String msg = "ICE CANDIDATE SELECTED (" + endpoint.getEndpoint().getTag("name") + + "): LOCAL CANDIDATE: " + endpoint.selectedLocalIceCandidate + + " | REMOTE CANDIDATE: " + endpoint.selectedRemoteIceCandidate + + " | TIMESTAMP: " + System.currentTimeMillis(); + log.warn(msg); + this.infoHandler.sendInfo(msg); }); + } + + @Override + @SuppressWarnings("unchecked") + public JSONObject toJSON() { + JSONObject json = super.toJSON(); + JSONArray publisherEnpoints = new JSONArray(); + if (this.streaming && this.publisher.getEndpoint() != null) { + publisherEnpoints.add(this.publisher.toJSON()); + } + JSONArray subscriberEndpoints = new JSONArray(); + for (MediaEndpoint sub : this.subscribers.values()) { + if (sub.getEndpoint() != null) { + subscriberEndpoints.add(sub.toJSON()); + } + } + json.put("publishers", publisherEnpoints); + json.put("subscribers", subscriberEndpoints); + return json; + } + @Override + public String getPublisherStremId() { + return this.publisher.getEndpoint().getTag("name"); } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java index 99d59f67..bbe342b4 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java @@ -25,6 +25,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; import org.kurento.client.Continuation; import org.kurento.client.ErrorEvent; import org.kurento.client.EventListener; @@ -347,4 +349,23 @@ public class KurentoSession implements Session { } } + @Override + @SuppressWarnings("unchecked") + public JSONObject toJSON() { + JSONObject json = new JSONObject(); + json.put("sessionId", this.sessionId); + json.put("mediaMode", this.sessionProperties.mediaMode().name()); + json.put("recordingMode", this.sessionProperties.recordingMode().name()); + json.put("defaultRecordingLayout", this.sessionProperties.defaultRecordingLayout().name()); + if (this.sessionProperties.defaultCustomLayout() != null && !this.sessionProperties.defaultCustomLayout().isEmpty()) { + json.put("defaultCustomLayout", this.sessionProperties.defaultCustomLayout()); + } + JSONArray participants = new JSONArray(); + this.participants.values().forEach(p -> { + participants.add(p.toJSON()); + }); + json.put("connections", participants); + return json; + } + } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java index 08e57032..d76a04eb 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java @@ -248,7 +248,7 @@ public class KurentoSessionManager extends SessionManager { OpenViduException e = new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Error generating SDP response for publishing user " + participant.getParticipantPublicId()); log.error("PARTICIPANT {}: Error publishing media", participant.getParticipantPublicId(), e); - sessionEventsHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, + sessionEventsHandler.onPublishMedia(participant, null, session.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, e); } @@ -276,7 +276,7 @@ public class KurentoSessionManager extends SessionManager { participants = kurentoParticipant.getSession().getParticipants(); if (sdpAnswer != null) { - sessionEventsHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, + sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStremId(), session.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, null); } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/MediaEndpoint.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/MediaEndpoint.java index 34e7a604..42f48b60 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/MediaEndpoint.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/MediaEndpoint.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; +import org.json.simple.JSONObject; import org.kurento.client.Continuation; import org.kurento.client.ErrorEvent; import org.kurento.client.EventListener; @@ -44,437 +45,456 @@ import io.openvidu.server.kurento.MutedMediaType; import io.openvidu.server.kurento.core.KurentoParticipant; /** - * {@link WebRtcEndpoint} wrapper that supports buffering of {@link IceCandidate}s until the - * {@link WebRtcEndpoint} is created. Connections to other peers are opened using the corresponding - * method of the internal endpoint. + * {@link WebRtcEndpoint} wrapper that supports buffering of + * {@link IceCandidate}s until the {@link WebRtcEndpoint} is created. + * Connections to other peers are opened using the corresponding method of the + * internal endpoint. * * @author Pablo Fuente (pablofuenteperez@gmail.com) */ public abstract class MediaEndpoint { - private static Logger log; + private static Logger log; - private boolean web = false; + private boolean web = false; - private WebRtcEndpoint webEndpoint = null; - private RtpEndpoint endpoint = null; + private WebRtcEndpoint webEndpoint = null; + private RtpEndpoint endpoint = null; - private KurentoParticipant owner; - private String endpointName; + private KurentoParticipant owner; + private String endpointName; - private MediaPipeline pipeline = null; - private ListenerSubscription endpointSubscription = null; + private MediaPipeline pipeline = null; + private ListenerSubscription endpointSubscription = null; - private LinkedList candidates = new LinkedList(); + private LinkedList candidates = new LinkedList(); - private MutedMediaType muteType; - - public Map flowInMedia = new ConcurrentHashMap<>(); - public Map flowOutMedia = new ConcurrentHashMap<>(); + private MutedMediaType muteType; - /** - * Constructor to set the owner, the endpoint's name and the media pipeline. - * - * @param web - * @param owner - * @param endpointName - * @param pipeline - * @param log - */ - public MediaEndpoint(boolean web, KurentoParticipant owner, String endpointName, - MediaPipeline pipeline, Logger log) { - if (log == null) { - MediaEndpoint.log = LoggerFactory.getLogger(MediaEndpoint.class); - } else { - MediaEndpoint.log = log; - } - this.web = web; - this.owner = owner; - this.setEndpointName(endpointName); - this.setMediaPipeline(pipeline); - } + public Map flowInMedia = new ConcurrentHashMap<>(); + public Map flowOutMedia = new ConcurrentHashMap<>(); - public boolean isWeb() { - return web; - } + public String selectedLocalIceCandidate; + public String selectedRemoteIceCandidate; - /** - * @return the user session that created this endpoint - */ - public Participant getOwner() { - return owner; - } + /** + * Constructor to set the owner, the endpoint's name and the media pipeline. + * + * @param web + * @param owner + * @param endpointName + * @param pipeline + * @param log + */ + public MediaEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline, + Logger log) { + if (log == null) { + MediaEndpoint.log = LoggerFactory.getLogger(MediaEndpoint.class); + } else { + MediaEndpoint.log = log; + } + this.web = web; + this.owner = owner; + this.setEndpointName(endpointName); + this.setMediaPipeline(pipeline); + } - /** - * @return the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) - */ - public SdpEndpoint getEndpoint() { - if (this.isWeb()) { - return this.webEndpoint; - } else { - return this.endpoint; - } - } + public boolean isWeb() { + return web; + } - public WebRtcEndpoint getWebEndpoint() { - return webEndpoint; - } + /** + * @return the user session that created this endpoint + */ + public Participant getOwner() { + return owner; + } - protected RtpEndpoint getRtpEndpoint() { - return endpoint; - } + /** + * @return the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) + */ + public SdpEndpoint getEndpoint() { + if (this.isWeb()) { + return this.webEndpoint; + } else { + return this.endpoint; + } + } - /** - * If this object doesn't have a {@link WebRtcEndpoint}, it is created in a thread-safe way using - * the internal {@link MediaPipeline}. Otherwise no actions are taken. It also registers an error - * listener for the endpoint and for any additional media elements. - * - * @param endpointLatch - * latch whose countdown is performed when the asynchronous call to build the - * {@link WebRtcEndpoint} returns - * - * @return the existing endpoint, if any - */ - public synchronized SdpEndpoint createEndpoint(CountDownLatch endpointLatch) { - SdpEndpoint old = this.getEndpoint(); - if (old == null) { - internalEndpointInitialization(endpointLatch); - } else { - endpointLatch.countDown(); - } - if (this.isWeb()) { - while (!candidates.isEmpty()) { - internalAddIceCandidate(candidates.removeFirst()); - } - } - return old; - } + public WebRtcEndpoint getWebEndpoint() { + return webEndpoint; + } - /** - * @return the pipeline - */ - public MediaPipeline getPipeline() { - return this.pipeline; - } + protected RtpEndpoint getRtpEndpoint() { + return endpoint; + } - /** - * Sets the {@link MediaPipeline} used to create the internal {@link WebRtcEndpoint}. - * - * @param pipeline - * the {@link MediaPipeline} - */ - public void setMediaPipeline(MediaPipeline pipeline) { - this.pipeline = pipeline; - } + /** + * If this object doesn't have a {@link WebRtcEndpoint}, it is created in a + * thread-safe way using the internal {@link MediaPipeline}. Otherwise no + * actions are taken. It also registers an error listener for the endpoint and + * for any additional media elements. + * + * @param endpointLatch + * latch whose countdown is performed when the asynchronous call to + * build the {@link WebRtcEndpoint} returns + * + * @return the existing endpoint, if any + */ + public synchronized SdpEndpoint createEndpoint(CountDownLatch endpointLatch) { + SdpEndpoint old = this.getEndpoint(); + if (old == null) { + internalEndpointInitialization(endpointLatch); + } else { + endpointLatch.countDown(); + } + if (this.isWeb()) { + while (!candidates.isEmpty()) { + internalAddIceCandidate(candidates.removeFirst()); + } + } + return old; + } - /** - * @return name of this endpoint (as indicated by the browser) - */ - public String getEndpointName() { - return endpointName; - } + /** + * @return the pipeline + */ + public MediaPipeline getPipeline() { + return this.pipeline; + } - /** - * Sets the endpoint's name (as indicated by the browser). - * - * @param endpointName - * the name - */ - public void setEndpointName(String endpointName) { - this.endpointName = endpointName; - } + /** + * Sets the {@link MediaPipeline} used to create the internal + * {@link WebRtcEndpoint}. + * + * @param pipeline + * the {@link MediaPipeline} + */ + public void setMediaPipeline(MediaPipeline pipeline) { + this.pipeline = pipeline; + } - /** - * Unregisters all error listeners created for media elements owned by this instance. - */ - public synchronized void unregisterErrorListeners() { - unregisterElementErrListener(endpoint, endpointSubscription); - } + /** + * @return name of this endpoint (as indicated by the browser) + */ + public String getEndpointName() { + return endpointName; + } - /** - * Mute the media stream. - * - * @param muteType - * which type of leg to disconnect (audio, video or both) - */ - public abstract void mute(MutedMediaType muteType); + /** + * Sets the endpoint's name (as indicated by the browser). + * + * @param endpointName + * the name + */ + public void setEndpointName(String endpointName) { + this.endpointName = endpointName; + } - /** - * Reconnect the muted media leg(s). - */ - public abstract void unmute(); + /** + * Unregisters all error listeners created for media elements owned by this + * instance. + */ + public synchronized void unregisterErrorListeners() { + unregisterElementErrListener(endpoint, endpointSubscription); + } - public void setMuteType(MutedMediaType muteType) { - this.muteType = muteType; - } + /** + * Mute the media stream. + * + * @param muteType + * which type of leg to disconnect (audio, video or both) + */ + public abstract void mute(MutedMediaType muteType); - public MutedMediaType getMuteType() { - return this.muteType; - } + /** + * Reconnect the muted media leg(s). + */ + public abstract void unmute(); - protected void resolveCurrentMuteType(MutedMediaType newMuteType) { - MutedMediaType prev = this.getMuteType(); - if (prev != null) { - switch (prev) { - case AUDIO : - if (muteType.equals(MutedMediaType.VIDEO)) { - this.setMuteType(MutedMediaType.ALL); - return; - } - break; - case VIDEO : - if (muteType.equals(MutedMediaType.AUDIO)) { - this.setMuteType(MutedMediaType.ALL); - return; - } - break; - case ALL : - return; - } - } - this.setMuteType(newMuteType); - } + public void setMuteType(MutedMediaType muteType) { + this.muteType = muteType; + } - /** - * Creates the endpoint (RTP or WebRTC) and any other additional elements (if needed). - * - * @param endpointLatch - */ - protected void internalEndpointInitialization(final CountDownLatch endpointLatch) { - if (this.isWeb()) { - WebRtcEndpoint.Builder builder = new WebRtcEndpoint.Builder(pipeline); - /*if (this.dataChannels) { - builder.useDataChannels(); - }*/ - builder.buildAsync(new Continuation() { - @Override - public void onSuccess(WebRtcEndpoint result) throws Exception { - webEndpoint = result; + public MutedMediaType getMuteType() { + return this.muteType; + } - webEndpoint.setMaxVideoRecvBandwidth(600); - webEndpoint.setMinVideoRecvBandwidth(300); - webEndpoint.setMaxVideoSendBandwidth(600); - webEndpoint.setMinVideoSendBandwidth(300); + protected void resolveCurrentMuteType(MutedMediaType newMuteType) { + MutedMediaType prev = this.getMuteType(); + if (prev != null) { + switch (prev) { + case AUDIO: + if (muteType.equals(MutedMediaType.VIDEO)) { + this.setMuteType(MutedMediaType.ALL); + return; + } + break; + case VIDEO: + if (muteType.equals(MutedMediaType.AUDIO)) { + this.setMuteType(MutedMediaType.ALL); + return; + } + break; + case ALL: + return; + } + } + this.setMuteType(newMuteType); + } - endpointLatch.countDown(); - log.trace("EP {}: Created a new WebRtcEndpoint", endpointName); - endpointSubscription = registerElemErrListener(webEndpoint); - } + /** + * Creates the endpoint (RTP or WebRTC) and any other additional elements (if + * needed). + * + * @param endpointLatch + */ + protected void internalEndpointInitialization(final CountDownLatch endpointLatch) { + if (this.isWeb()) { + WebRtcEndpoint.Builder builder = new WebRtcEndpoint.Builder(pipeline); + /* + * if (this.dataChannels) { builder.useDataChannels(); } + */ + builder.buildAsync(new Continuation() { + @Override + public void onSuccess(WebRtcEndpoint result) throws Exception { + webEndpoint = result; - @Override - public void onError(Throwable cause) throws Exception { - endpointLatch.countDown(); - log.error("EP {}: Failed to create a new WebRtcEndpoint", endpointName, cause); - } - }); - } else { - new RtpEndpoint.Builder(pipeline).buildAsync(new Continuation() { - @Override - public void onSuccess(RtpEndpoint result) throws Exception { - endpoint = result; - endpointLatch.countDown(); - log.trace("EP {}: Created a new RtpEndpoint", endpointName); - endpointSubscription = registerElemErrListener(endpoint); - } + webEndpoint.setMaxVideoRecvBandwidth(600); + webEndpoint.setMinVideoRecvBandwidth(300); + webEndpoint.setMaxVideoSendBandwidth(600); + webEndpoint.setMinVideoSendBandwidth(300); - @Override - public void onError(Throwable cause) throws Exception { - endpointLatch.countDown(); - log.error("EP {}: Failed to create a new RtpEndpoint", endpointName, cause); - } - }); - } - } + endpointLatch.countDown(); + log.trace("EP {}: Created a new WebRtcEndpoint", endpointName); + endpointSubscription = registerElemErrListener(webEndpoint); + } - /** - * Add a new {@link IceCandidate} received gathered by the remote peer of this - * {@link WebRtcEndpoint}. - * - * @param candidate - * the remote candidate - */ - public synchronized void addIceCandidate(IceCandidate candidate) throws OpenViduException { - if (!this.isWeb()) { - throw new OpenViduException(Code.MEDIA_NOT_A_WEB_ENDPOINT_ERROR_CODE, "Operation not supported"); - } - if (webEndpoint == null) { - candidates.addLast(candidate); - } else { - internalAddIceCandidate(candidate); - } - } + @Override + public void onError(Throwable cause) throws Exception { + endpointLatch.countDown(); + log.error("EP {}: Failed to create a new WebRtcEndpoint", endpointName, cause); + } + }); + } else { + new RtpEndpoint.Builder(pipeline).buildAsync(new Continuation() { + @Override + public void onSuccess(RtpEndpoint result) throws Exception { + endpoint = result; + endpointLatch.countDown(); + log.trace("EP {}: Created a new RtpEndpoint", endpointName); + endpointSubscription = registerElemErrListener(endpoint); + } - /** - * Registers a listener for when the {@link MediaElement} triggers an {@link ErrorEvent}. Notifies - * the owner with the error. - * - * @param element - * the {@link MediaElement} - * @return {@link ListenerSubscription} that can be used to deregister the listener - */ - protected ListenerSubscription registerElemErrListener(MediaElement element) { - return element.addErrorListener(new EventListener() { - @Override - public void onEvent(ErrorEvent event) { - owner.sendMediaError(event); - } - }); - } + @Override + public void onError(Throwable cause) throws Exception { + endpointLatch.countDown(); + log.error("EP {}: Failed to create a new RtpEndpoint", endpointName, cause); + } + }); + } + } - /** - * Unregisters the error listener from the media element using the provided subscription. - * - * @param element - * the {@link MediaElement} - * @param subscription - * the associated {@link ListenerSubscription} - */ - protected void unregisterElementErrListener(MediaElement element, - final ListenerSubscription subscription) { - if (element == null || subscription == null) { - return; - } - element.removeErrorListener(subscription); - } + /** + * Add a new {@link IceCandidate} received gathered by the remote peer of this + * {@link WebRtcEndpoint}. + * + * @param candidate + * the remote candidate + */ + public synchronized void addIceCandidate(IceCandidate candidate) throws OpenViduException { + if (!this.isWeb()) { + throw new OpenViduException(Code.MEDIA_NOT_A_WEB_ENDPOINT_ERROR_CODE, "Operation not supported"); + } + if (webEndpoint == null) { + candidates.addLast(candidate); + } else { + internalAddIceCandidate(candidate); + } + } - /** - * Orders the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) to process the - * offer String. - * - * @see SdpEndpoint#processOffer(String) - * @param offer - * String with the Sdp offer - * @return the Sdp answer - */ - protected String processOffer(String offer) throws OpenViduException { - if (this.isWeb()) { - if (webEndpoint == null) { - throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, - "Can't process offer when WebRtcEndpoint is null (ep: " + endpointName + ")"); - } - return webEndpoint.processOffer(offer); - } else { - if (endpoint == null) { - throw new OpenViduException(Code.MEDIA_RTP_ENDPOINT_ERROR_CODE, - "Can't process offer when RtpEndpoint is null (ep: " + endpointName + ")"); - } - return endpoint.processOffer(offer); - } - } + /** + * Registers a listener for when the {@link MediaElement} triggers an + * {@link ErrorEvent}. Notifies the owner with the error. + * + * @param element + * the {@link MediaElement} + * @return {@link ListenerSubscription} that can be used to deregister the + * listener + */ + protected ListenerSubscription registerElemErrListener(MediaElement element) { + return element.addErrorListener(new EventListener() { + @Override + public void onEvent(ErrorEvent event) { + owner.sendMediaError(event); + } + }); + } - /** - * Orders the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) to generate the - * offer String that can be used to initiate a connection. - * - * @see SdpEndpoint#generateOffer() - * @return the Sdp offer - */ - protected String generateOffer() throws OpenViduException { - if (this.isWeb()) { - if (webEndpoint == null) { - throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, - "Can't generate offer when WebRtcEndpoint is null (ep: " + endpointName + ")"); - } - return webEndpoint.generateOffer(); - } else { - if (endpoint == null) { - throw new OpenViduException(Code.MEDIA_RTP_ENDPOINT_ERROR_CODE, - "Can't generate offer when RtpEndpoint is null (ep: " + endpointName + ")"); - } - return endpoint.generateOffer(); - } - } + /** + * Unregisters the error listener from the media element using the provided + * subscription. + * + * @param element + * the {@link MediaElement} + * @param subscription + * the associated {@link ListenerSubscription} + */ + protected void unregisterElementErrListener(MediaElement element, final ListenerSubscription subscription) { + if (element == null || subscription == null) { + return; + } + element.removeErrorListener(subscription); + } - /** - * Orders the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) to process the - * answer String. - * - * @see SdpEndpoint#processAnswer(String) - * @param answer - * String with the Sdp answer from remote - * @return the updated Sdp offer, based on the received answer - */ - protected String processAnswer(String answer) throws OpenViduException { - if (this.isWeb()) { - if (webEndpoint == null) { - throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, - "Can't process answer when WebRtcEndpoint is null (ep: " + endpointName + ")"); - } - return webEndpoint.processAnswer(answer); - } else { - if (endpoint == null) { - throw new OpenViduException(Code.MEDIA_RTP_ENDPOINT_ERROR_CODE, - "Can't process answer when RtpEndpoint is null (ep: " + endpointName + ")"); - } - return endpoint.processAnswer(answer); - } - } + /** + * Orders the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) + * to process the offer String. + * + * @see SdpEndpoint#processOffer(String) + * @param offer + * String with the Sdp offer + * @return the Sdp answer + */ + protected String processOffer(String offer) throws OpenViduException { + if (this.isWeb()) { + if (webEndpoint == null) { + throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, + "Can't process offer when WebRtcEndpoint is null (ep: " + endpointName + ")"); + } + return webEndpoint.processOffer(offer); + } else { + if (endpoint == null) { + throw new OpenViduException(Code.MEDIA_RTP_ENDPOINT_ERROR_CODE, + "Can't process offer when RtpEndpoint is null (ep: " + endpointName + ")"); + } + return endpoint.processOffer(offer); + } + } - /** - * If supported, it registers a listener for when a new {@link IceCandidate} is gathered by the - * internal endpoint ({@link WebRtcEndpoint}) and sends it to the remote User Agent as a - * notification using the messaging capabilities of the {@link Participant}. - * - * @see WebRtcEndpoint#addOnIceCandidateListener(org.kurento.client.EventListener) - * @see Participant#sendIceCandidate(String, IceCandidate) - * @throws OpenViduException - * if thrown, unable to register the listener - */ - protected void registerOnIceCandidateEventListener() throws OpenViduException { - if (!this.isWeb()) { - return; - } - if (webEndpoint == null) { - throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, - "Can't register event listener for null WebRtcEndpoint (ep: " + endpointName + ")"); - } - webEndpoint.addOnIceCandidateListener(new EventListener() { - @Override - public void onEvent(OnIceCandidateEvent event) { - owner.sendIceCandidate(endpointName, event.getCandidate()); - } - }); - } + /** + * Orders the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) + * to generate the offer String that can be used to initiate a connection. + * + * @see SdpEndpoint#generateOffer() + * @return the Sdp offer + */ + protected String generateOffer() throws OpenViduException { + if (this.isWeb()) { + if (webEndpoint == null) { + throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, + "Can't generate offer when WebRtcEndpoint is null (ep: " + endpointName + ")"); + } + return webEndpoint.generateOffer(); + } else { + if (endpoint == null) { + throw new OpenViduException(Code.MEDIA_RTP_ENDPOINT_ERROR_CODE, + "Can't generate offer when RtpEndpoint is null (ep: " + endpointName + ")"); + } + return endpoint.generateOffer(); + } + } - /** - * If supported, it instructs the internal endpoint to start gathering {@link IceCandidate}s. - */ - protected void gatherCandidates() throws OpenViduException { - if (!this.isWeb()) { - return; - } - if (webEndpoint == null) { - throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, - "Can't start gathering ICE candidates on null WebRtcEndpoint (ep: " + endpointName + ")"); - } - webEndpoint.gatherCandidates(new Continuation() { - @Override - public void onSuccess(Void result) throws Exception { - log.trace("EP {}: Internal endpoint started to gather candidates", endpointName); - } + /** + * Orders the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}) + * to process the answer String. + * + * @see SdpEndpoint#processAnswer(String) + * @param answer + * String with the Sdp answer from remote + * @return the updated Sdp offer, based on the received answer + */ + protected String processAnswer(String answer) throws OpenViduException { + if (this.isWeb()) { + if (webEndpoint == null) { + throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, + "Can't process answer when WebRtcEndpoint is null (ep: " + endpointName + ")"); + } + return webEndpoint.processAnswer(answer); + } else { + if (endpoint == null) { + throw new OpenViduException(Code.MEDIA_RTP_ENDPOINT_ERROR_CODE, + "Can't process answer when RtpEndpoint is null (ep: " + endpointName + ")"); + } + return endpoint.processAnswer(answer); + } + } - @Override - public void onError(Throwable cause) throws Exception { - log.warn("EP {}: Internal endpoint failed to start gathering candidates", endpointName, - cause); - } - }); - } + /** + * If supported, it registers a listener for when a new {@link IceCandidate} is + * gathered by the internal endpoint ({@link WebRtcEndpoint}) and sends it to + * the remote User Agent as a notification using the messaging capabilities of + * the {@link Participant}. + * + * @see WebRtcEndpoint#addOnIceCandidateListener(org.kurento.client.EventListener) + * @see Participant#sendIceCandidate(String, IceCandidate) + * @throws OpenViduException + * if thrown, unable to register the listener + */ + protected void registerOnIceCandidateEventListener() throws OpenViduException { + if (!this.isWeb()) { + return; + } + if (webEndpoint == null) { + throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, + "Can't register event listener for null WebRtcEndpoint (ep: " + endpointName + ")"); + } + webEndpoint.addOnIceCandidateListener(new EventListener() { + @Override + public void onEvent(OnIceCandidateEvent event) { + owner.sendIceCandidate(endpointName, event.getCandidate()); + } + }); + } - private void internalAddIceCandidate(IceCandidate candidate) throws OpenViduException { - if (webEndpoint == null) { - throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, - "Can't add existing ICE candidates to null WebRtcEndpoint (ep: " + endpointName + ")"); - } - this.webEndpoint.addIceCandidate(candidate, new Continuation() { - @Override - public void onSuccess(Void result) throws Exception { - log.trace("Ice candidate added to the internal endpoint"); - } + /** + * If supported, it instructs the internal endpoint to start gathering + * {@link IceCandidate}s. + */ + protected void gatherCandidates() throws OpenViduException { + if (!this.isWeb()) { + return; + } + if (webEndpoint == null) { + throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, + "Can't start gathering ICE candidates on null WebRtcEndpoint (ep: " + endpointName + ")"); + } + webEndpoint.gatherCandidates(new Continuation() { + @Override + public void onSuccess(Void result) throws Exception { + log.trace("EP {}: Internal endpoint started to gather candidates", endpointName); + } - @Override - public void onError(Throwable cause) throws Exception { - log.warn("EP {}: Failed to add ice candidate to the internal endpoint", endpointName, cause); - } - }); - } + @Override + public void onError(Throwable cause) throws Exception { + log.warn("EP {}: Internal endpoint failed to start gathering candidates", endpointName, cause); + } + }); + } + + private void internalAddIceCandidate(IceCandidate candidate) throws OpenViduException { + if (webEndpoint == null) { + throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, + "Can't add existing ICE candidates to null WebRtcEndpoint (ep: " + endpointName + ")"); + } + this.webEndpoint.addIceCandidate(candidate, new Continuation() { + @Override + public void onSuccess(Void result) throws Exception { + log.trace("Ice candidate added to the internal endpoint"); + } + + @Override + public void onError(Throwable cause) throws Exception { + log.warn("EP {}: Failed to add ice candidate to the internal endpoint", endpointName, cause); + } + }); + } + + @SuppressWarnings("unchecked") + public JSONObject toJSON() { + JSONObject json = new JSONObject(); + json.put("webrtcTagName", this.getEndpoint().getTag("name")); + json.put("localCandidate", this.selectedLocalIceCandidate); + json.put("remoteCandidate", this.selectedRemoteIceCandidate); + return json; + } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java index 6910d7fd..6b990820 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java @@ -140,7 +140,7 @@ public class ComposedRecordingService { envs.add("RECORDING_JSON=" + recording.toJson().toJSONString()); log.info(recording.toJson().toJSONString()); - log.debug("Recorder connecting to url {}", layoutUrl); + log.info("Recorder connecting to url {}", layoutUrl); String containerId = this.runRecordingContainer(envs, "recording_" + recordingId); diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java index 28ded20f..ec529a2d 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java @@ -30,7 +30,7 @@ import io.openvidu.server.config.OpenviduConfig; * @author Pablo Fuente Pérez */ @RestController -@CrossOrigin(origins = "*") +@CrossOrigin @RequestMapping("/config") public class ConfigRestController { diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java index 666d2149..37f4319a 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java @@ -20,7 +20,6 @@ package io.openvidu.server.rest; import java.util.Collection; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.json.simple.JSONArray; @@ -54,7 +53,7 @@ import io.openvidu.server.recording.ComposedRecordingService; * @author Pablo Fuente Pérez */ @RestController -@CrossOrigin(origins = "*") +@CrossOrigin @RequestMapping("/api") public class SessionRestController { @@ -67,11 +66,6 @@ public class SessionRestController { @Autowired private OpenviduConfig openviduConfig; - @RequestMapping(value = "/sessions", method = RequestMethod.GET) - public Set getAllSessions() { - return sessionManager.getSessions(); - } - @SuppressWarnings("unchecked") @RequestMapping(value = "/sessions", method = RequestMethod.POST) public ResponseEntity getSessionId(@RequestBody(required = false) Map params) { @@ -136,9 +130,34 @@ public class SessionRestController { sessionManager.storeSessionId(sessionId, sessionProperties); JSONObject responseJson = new JSONObject(); responseJson.put("id", sessionId); + return new ResponseEntity<>(responseJson, HttpStatus.OK); } + @RequestMapping(value = "/sessions/{sessionId}", method = RequestMethod.GET) + public ResponseEntity getSession(@PathVariable("sessionId") String sessionId) { + Session session = this.sessionManager.getSession(sessionId); + if (session != null) { + return new ResponseEntity<>(session.toJSON(), HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @SuppressWarnings("unchecked") + @RequestMapping(value = "/sessions", method = RequestMethod.GET) + public ResponseEntity listSessions() { + Collection sessions = this.sessionManager.getSessionObjects(); + JSONObject json = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + sessions.forEach(s -> { + jsonArray.add(s.toJSON()); + }); + json.put("count", sessions.size()); + json.put("items", jsonArray); + return new ResponseEntity<>(json, HttpStatus.OK); + } + @SuppressWarnings("unchecked") @RequestMapping(value = "/tokens", method = RequestMethod.POST) public ResponseEntity newToken(@RequestBody Map params) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java index a27e1782..87eb95f8 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java @@ -54,7 +54,8 @@ public class RpcHandler extends DefaultJsonRpcHandler { @Autowired RpcNotificationService notificationService; - private ConcurrentMap webSocketTransportError = new ConcurrentHashMap<>(); + private ConcurrentMap webSocketEOFTransportError = new ConcurrentHashMap<>(); + // private ConcurrentMap webSocketBrokenPipeTransportError = new ConcurrentHashMap<>(); @Override public void handleRequest(Transaction transaction, Request request) throws Exception { @@ -300,24 +301,38 @@ public class RpcHandler extends DefaultJsonRpcHandler { @Override public void afterConnectionClosed(Session rpcSession, String status) throws Exception { log.info("After connection closed for WebSocket session: {} - Status: {}", rpcSession.getSessionId(), status); - + + String rpcSessionId = rpcSession.getSessionId(); + String message = ""; + if ("Close for not receive ping from client".equals(status)) { - RpcConnection rpc = this.notificationService.closeRpcSession(rpcSession.getSessionId()); + message = "Evicting participant with private id {} because of a network disconnection"; + } else if (status == null) { // && this.webSocketBrokenPipeTransportError.remove(rpcSessionId) != null)) { + try { + Participant p = sessionManager.getParticipant(rpcSession.getSessionId()); + if (p != null) { + message = "Evicting participant with private id {} because its websocket unexpectedly closed in the client side"; + } + } catch (OpenViduException exception) { + } + } + + if (!message.isEmpty()) { + RpcConnection rpc = this.notificationService.closeRpcSession(rpcSessionId); if (rpc != null && rpc.getSessionId() != null) { io.openvidu.server.core.Session session = this.sessionManager.getSession(rpc.getSessionId()); if (session != null && session.getParticipantByPrivateId(rpc.getParticipantPrivateId()) != null) { + log.info(message, rpc.getParticipantPrivateId()); leaveRoomAfterConnClosed(rpc.getParticipantPrivateId(), "networkDisconnect"); } } } - String rpcSessionId = rpcSession.getSessionId(); - if (this.webSocketTransportError.get(rpcSessionId) != null) { + if (this.webSocketEOFTransportError.remove(rpcSessionId) != null) { log.warn( "Evicting participant with private id {} because a transport error took place and its web socket connection is now closed", rpcSession.getSessionId()); this.leaveRoomAfterConnClosed(rpcSessionId, "networkDisconnect"); - this.webSocketTransportError.remove(rpcSessionId); } } @@ -325,10 +340,15 @@ public class RpcHandler extends DefaultJsonRpcHandler { public void handleTransportError(Session rpcSession, Throwable exception) throws Exception { log.error("Transport exception for WebSocket session: {} - Exception: {}", rpcSession.getSessionId(), exception); + if ("IOException".equals(exception.getClass().getSimpleName()) + && "Broken pipe".equals(exception.getCause().getMessage())) { + log.warn("Parcipant with private id {} unexpectedly closed the websocket", rpcSession.getSessionId()); + // this.webSocketBrokenPipeTransportError.put(rpcSession.getSessionId(), true); + } if ("EOFException".equals(exception.getClass().getSimpleName())) { // Store WebSocket connection interrupted exception for this web socket to // automatically evict the participant on "afterConnectionClosed" event - this.webSocketTransportError.put(rpcSession.getSessionId(), true); + this.webSocketEOFTransportError.put(rpcSession.getSessionId(), true); } }