From 390ce2224e3eaebc8dbcdf2ee4936580696dee9b Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Tue, 17 Apr 2018 15:05:44 +0200 Subject: [PATCH] CDR refactoring: now all operations are recorded in a low-level context (not in SessionEventsHandler). Added 'videoFramerate', 'reason' and recording events. New property openvidu.recording.notification. Recordings stopped upon openvidu-server stopped. --- .../client/internal/ProtocolElements.java | 15 +- .../java/io/openvidu/server/cdr/CDREvent.java | 26 +- .../openvidu/server/cdr/CallDetailRecord.java | 112 +++++---- .../server/config/OpenviduConfig.java | 34 ++- .../server/config/SecurityConfig.java | 2 +- .../io/openvidu/server/core/MediaOptions.java | 4 +- .../io/openvidu/server/core/Participant.java | 9 + .../java/io/openvidu/server/core/Session.java | 4 +- .../server/core/SessionEventsHandler.java | 224 ++++++++++++------ .../openvidu/server/core/SessionManager.java | 41 +++- .../kurento/core/KurentoMediaOptions.java | 4 +- .../kurento/core/KurentoParticipant.java | 46 +++- .../server/kurento/core/KurentoSession.java | 35 ++- .../kurento/core/KurentoSessionManager.java | 100 ++++---- .../recording/ComposedRecordingService.java | 25 +- .../server/rest/SessionRestController.java | 25 +- .../io/openvidu/server/rpc/RpcHandler.java | 17 +- ...itional-spring-configuration-metadata.json | 11 +- .../src/main/resources/application.properties | 3 +- 19 files changed, 487 insertions(+), 250 deletions(-) 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 d56e6cf7..1df8060d 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 @@ -44,6 +44,7 @@ public class ProtocolElements { public static final String JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM = "audioActive"; public static final String JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM = "videoActive"; public static final String JOINROOM_PEERSTREAMTYPEOFVIDEO_PARAM = "typeOfVideo"; + public static final String JOINROOM_PEERSTREAMFRAMERATE_PARAM = "frameRate"; public static final String PUBLISHVIDEO_METHOD = "publishVideo"; public static final String PUBLISHVIDEO_SDPOFFER_PARAM = "sdpOffer"; @@ -52,6 +53,7 @@ public class ProtocolElements { public static final String PUBLISHVIDEO_AUDIOACTIVE_PARAM = "audioActive"; public static final String PUBLISHVIDEO_VIDEOACTIVE_PARAM = "videoActive"; public static final String PUBLISHVIDEO_TYPEOFVIDEO_PARAM = "typeOfVideo"; + public static final String PUBLISHVIDEO_FRAMERATE_PARAM = "frameRate"; public static final String UNPUBLISHVIDEO_METHOD = "unpublishVideo"; @@ -79,6 +81,7 @@ public class ProtocolElements { public static final String PARTICIPANTLEFT_METHOD = "participantLeft"; public static final String PARTICIPANTLEFT_NAME_PARAM = "name"; + public static final String PARTICIPANTLEFT_REASON_PARAM = "reason"; public static final String PARTICIPANTEVICTED_METHOD = "participantEvicted"; @@ -89,9 +92,11 @@ public class ProtocolElements { public static final String PARTICIPANTPUBLISHED_AUDIOACTIVE_PARAM = "audioActive"; public static final String PARTICIPANTPUBLISHED_VIDEOACTIVE_PARAM = "videoActive"; public static final String PARTICIPANTPUBLISHED_TYPEOFVIDEO_PARAM = "typeOfVideo"; - + public static final String PARTICIPANTPUBLISHED_FRAMERATE_PARAM = "frameRate"; + public static final String PARTICIPANTUNPUBLISHED_METHOD = "participantUnpublished"; public static final String PARTICIPANTUNPUBLISHED_NAME_PARAM = "name"; + public static final String PARTICIPANTUNPUBLISHED_REASON_PARAM = "reason"; public static final String PARTICIPANTSENDMESSAGE_METHOD = "sendMessage"; public static final String PARTICIPANTSENDMESSAGE_DATA_PARAM = "data"; @@ -109,8 +114,14 @@ public class ProtocolElements { public static final String ICECANDIDATE_CANDIDATE_PARAM = "candidate"; public static final String ICECANDIDATE_SDPMID_PARAM = "sdpMid"; public static final String ICECANDIDATE_SDPMLINEINDEX_PARAM = "sdpMLineIndex"; + + public static final String RECORDINGSTARTED_METHOD = "recordingStarted"; + public static final String RECORDINGSTARTED_ID_PARAM = "id"; + + public static final String RECORDINGSTOPPED_METHOD = "recordingStopped"; + public static final String RECORDINGSTOPPED_ID_PARAM = "id"; public static final String CUSTOM_NOTIFICATION = "custonNotification"; - public static final String RECORDER_PARTICIPANT_ID_PUBLICID = "RECORDER"; + public static final String RECORDER_PARTICIPANT_PUBLICID = "RECORDER"; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java index 6b6093e8..2613469d 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java @@ -13,18 +13,26 @@ public class CDREvent implements Comparable { static final String PARTICIPANT_LEFT = "participantLeft"; static final String CONNECTION_CREATED = "webrtcConnectionCreated"; static final String CONNECTION_DESTROYED = "webrtcConnectionDestroyed"; + static final String RECORDING_STARTED = "recordingStarted"; + static final String RECORDING_STOPPED = "recordingStopped"; protected String eventName; protected String sessionId; + protected Long timeStamp; + private Long startTime; + private Integer duration; private Participant participant; private MediaOptions mediaOptions; private String receivingFrom; - private Long startTime; - private Integer duration; - protected Long timeStamp; + private String reason; public CDREvent(String eventName, CDREvent event) { - this(eventName, event.participant, event.sessionId, event.mediaOptions, event.receivingFrom, event.startTime); + this(eventName, event.participant, event.sessionId, event.mediaOptions, event.receivingFrom, event.startTime, event.reason); + this.duration = (int) (this.timeStamp - this.startTime / 1000); + } + + public CDREvent(String eventName, CDREvent event, String reason) { + this(eventName, event.participant, event.sessionId, event.mediaOptions, event.receivingFrom, event.startTime, reason); this.duration = (int) (this.timeStamp - this.startTime / 1000); } @@ -46,12 +54,13 @@ public class CDREvent implements Comparable { } public CDREvent(String eventName, Participant participant, String sessionId, MediaOptions mediaOptions, - String receivingFrom, Long startTime) { + String receivingFrom, Long startTime, String reason) { this(eventName, sessionId); this.participant = participant; this.mediaOptions = mediaOptions; this.receivingFrom = receivingFrom; this.startTime = startTime; + this.reason = reason; } public MediaOptions getMediaOptions() { @@ -82,6 +91,7 @@ public class CDREvent implements Comparable { json.put("videoEnabled", this.mediaOptions.videoActive); if (this.mediaOptions.videoActive) { json.put("videoSource", this.mediaOptions.typeOfVideo); + json.put("videoFramerate", this.mediaOptions.frameRate); } if (this.receivingFrom != null) { json.put("receivingFrom", this.receivingFrom); @@ -92,11 +102,15 @@ public class CDREvent implements Comparable { json.put("endTime", this.timeStamp); json.put("duration", (this.timeStamp - this.startTime) / 1000); } + + if (this.reason != null) { + json.put("reason", this.reason); + } JSONObject root = new JSONObject(); root.put(this.eventName, json); - return root.toString(); + return root.toJSONString(); } @Override diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java index abf8d3b4..46abf5de 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java @@ -13,78 +13,101 @@ import io.openvidu.server.core.MediaOptions; import io.openvidu.server.core.Participant; /** - * CDR logger to register all information of each WebRTC connection: + * CDR logger to register all information of a Session. * Enabled by property 'openvidu.cdr=true' * - * - Participant unique identifier - * - Session unique identifier - * - Inbound or Outbound WebRTC connection - * - Sender unique identifier - * - Audio media stream enabled - * - Video media stream enabled - * - Video source [CAMERA, SCREEN] - * - Time of start of the call - * - Time of end of the call - * - Total time duration + * - 'sessionCreated': {sessionId, timestamp} + * - 'sessionDestroyed': {sessionId, timestamp, startTime, endTime, duration } + * - 'participantJoined': {sessionId, timestamp, participantId} + * - 'participantLeft': {sessionId, timestamp, participantId, startTime, endTime, duration, reason} + * - 'webrtcConnectionCreated' {sessionId, timestamp, participantId, connection, [receivingFrom], audioEnabled, videoEnabled, [videoSource], [videoFramerate] } + * - 'webrtcConnectionDestroyed' {sessionId, timestamp, participantId, connection, [receivingFrom], audioEnabled, videoEnabled, [videoSource], [videoFramerate], reason } + * - 'recordingStarted' {sessionId, timestamp} + * - 'recordingStopped' {sessionId, timestamp} + * + * PROPERTIES VALUES: + * + * - sessionId: string + * - timestamp: number + * - startTime: number + * - endTime: number + * - duration: number + * - participantId: string + * - connection: "INBOUND", "OUTBOUND" + * - receivingFrom: string + * - audioEnabled: boolean + * - videoEnabled: boolean + * - videoSource: "CAMERA", "SCREEN" + * - videoFramerate: number + * - webrtcConnectionDestroyed.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerDestroyed" + * - participantLeft.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerDestroyed" + * - sessionDestroyed.reason: "lastParticipantLeft", "openviduServerDestroyed" + * + * [OPTIONAL_PROPERTIES]: + * - receivingFrom: only if connection = "INBOUND" + * - videoSource: only if videoEnabled = true + * - videoFramerate: only if videoEnabled = true * * @author Pablo Fuente (pablofuenteperez@gmail.com) */ public class CallDetailRecord { private Logger log = LoggerFactory.getLogger(CallDetailRecord.class); - + private Map sessions = new ConcurrentHashMap<>(); private Map participants = new ConcurrentHashMap<>(); private Map publications = new ConcurrentHashMap<>(); private Map> subscriptions = new ConcurrentHashMap<>(); - + public void recordSessionCreated(String sessionId) { CDREvent e = new CDREvent(CDREvent.SESSION_CREATED, sessionId); this.sessions.put(sessionId, e); log.info("{}", e); } - - public void recordSessionDestroyed(String sessionId) { + + public void recordSessionDestroyed(String sessionId, String reason) { CDREvent e = this.sessions.remove(sessionId); - log.info("{}", new CDREvent(CDREvent.SESSION_DESTROYED, e)); + log.info("{}", new CDREvent(CDREvent.SESSION_DESTROYED, e, reason)); } - + public void recordParticipantJoined(Participant participant, String sessionId) { CDREvent e = new CDREvent(CDREvent.PARTICIPANT_JOINED, participant, sessionId); this.participants.put(participant.getParticipantPublicId(), e); log.info("{}", e); } - - public void recordParticipantLeft(Participant participant, String sessionId) { + + public void recordParticipantLeft(Participant participant, String sessionId, String reason) { CDREvent e = this.participants.remove(participant.getParticipantPublicId()); - log.info("{}", new CDREvent(CDREvent.PARTICIPANT_LEFT, e)); + log.info("{}", new CDREvent(CDREvent.PARTICIPANT_LEFT, e, reason)); } - + public void recordNewPublisher(Participant participant, String sessionId, MediaOptions mediaOptions) { - CDREvent publisher = new CDREvent(CDREvent.CONNECTION_CREATED, participant, sessionId, mediaOptions, null, System.currentTimeMillis()); + CDREvent publisher = new CDREvent(CDREvent.CONNECTION_CREATED, participant, sessionId, mediaOptions, null, + System.currentTimeMillis(), null); this.publications.put(participant.getParticipantPublicId(), publisher); log.info("{}", publisher); } - public void recordNewSubscriber(Participant participant, String sessionId, String senderPublicId) { - CDREvent publisher = this.publications.get(senderPublicId); - CDREvent subscriber = new CDREvent(CDREvent.CONNECTION_CREATED, participant, sessionId, publisher.getMediaOptions(), publisher.getParticipantPublicId(), System.currentTimeMillis()); - this.subscriptions.putIfAbsent(participant.getParticipantPublicId(), new ConcurrentSkipListSet<>()); - this.subscriptions.get(participant.getParticipantPublicId()).add(subscriber); - log.info("{}", subscriber); - } - - public boolean stopPublisher(String participantPublicId) { + public boolean stopPublisher(String participantPublicId, String reason) { CDREvent publisher = this.publications.remove(participantPublicId); if (publisher != null) { - publisher = new CDREvent(CDREvent.CONNECTION_DESTROYED, publisher); + publisher = new CDREvent(CDREvent.CONNECTION_DESTROYED, publisher, reason); log.info("{}", publisher); return true; } return false; } - public boolean stopSubscriber(String participantPublicId, String senderPublicId) { + public void recordNewSubscriber(Participant participant, String sessionId, String senderPublicId) { + CDREvent publisher = this.publications.get(senderPublicId); + CDREvent subscriber = new CDREvent(CDREvent.CONNECTION_CREATED, participant, sessionId, + publisher.getMediaOptions(), publisher.getParticipantPublicId(), System.currentTimeMillis(), null); + this.subscriptions.putIfAbsent(participant.getParticipantPublicId(), new ConcurrentSkipListSet<>()); + this.subscriptions.get(participant.getParticipantPublicId()).add(subscriber); + log.info("{}", subscriber); + } + + public boolean stopSubscriber(String participantPublicId, String senderPublicId, String reason) { Set participantSubscriptions = this.subscriptions.get(participantPublicId); if (participantSubscriptions != null) { CDREvent subscription; @@ -92,7 +115,7 @@ public class CallDetailRecord { subscription = it.next(); if (subscription.getReceivingFrom().equals(senderPublicId)) { it.remove(); - subscription = new CDREvent(CDREvent.CONNECTION_DESTROYED, subscription); + subscription = new CDREvent(CDREvent.CONNECTION_DESTROYED, subscription, reason); log.info("{}", subscription); return true; } @@ -101,17 +124,14 @@ public class CallDetailRecord { return false; } - public void stopAllSubscriptions(String participantPublicId) { - Set participantSubscriptions = this.subscriptions.get(participantPublicId); - if (participantSubscriptions != null) { - CDREvent subscription; - for (Iterator it = participantSubscriptions.iterator(); it.hasNext();) { - subscription = it.next(); - subscription = new CDREvent(CDREvent.CONNECTION_DESTROYED, subscription); - log.info("{}", subscription); - } - this.subscriptions.remove(participantPublicId).clear(); - } + public void recordRecordingStarted(String sessionId) { + CDREvent recording = new CDREvent(CDREvent.RECORDING_STARTED, sessionId); + log.info("{}", recording); } - + + public void recordRecordingStopped(String sessionId) { + CDREvent recording = new CDREvent(CDREvent.RECORDING_STOPPED, sessionId); + log.info("{}", recording); + } + } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index 96285843..82299eb5 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -3,6 +3,8 @@ package io.openvidu.server.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import io.openvidu.server.core.ParticipantRole; + @Component public class OpenviduConfig { @@ -24,12 +26,15 @@ public class OpenviduConfig { @Value("${openvidu.recording.path}") private String openviduRecordingPath; - @Value("${openvidu.recording.free-access}") - boolean openviduRecordingFreeAccess; + @Value("${openvidu.recording.public-access}") + boolean openviduRecordingPublicAccess; + + @Value("${openvidu.recording.notification}") + String openviduRecordingNotification; @Value("${openvidu.recording.version}") String openviduRecordingVersion; - + @Value("#{'${spring.profiles.active:}'.length() > 0 ? '${spring.profiles.active:}'.split(',') : \"default\"}") private String springProfile; @@ -63,8 +68,8 @@ public class OpenviduConfig { return this.openviduRecordingPath; } - public boolean getOpenViduRecordingFreeAccess() { - return this.openviduRecordingFreeAccess; + public boolean getOpenViduRecordingPublicAccess() { + return this.openviduRecordingPublicAccess; } public void setOpenViduRecordingPath(String recordingPath) { @@ -87,4 +92,23 @@ public class OpenviduConfig { return springProfile; } + public ParticipantRole[] getRolesFromRecordingNotification() { + ParticipantRole[] roles; + switch (this.openviduRecordingNotification) { + case "none": + roles = new ParticipantRole[0]; + break; + case "publisher_moderator": + roles = new ParticipantRole[] { ParticipantRole.PUBLISHER, ParticipantRole.MODERATOR }; + break; + case "all": + roles = new ParticipantRole[] { ParticipantRole.SUBSCRIBER, ParticipantRole.PUBLISHER, + ParticipantRole.MODERATOR }; + break; + default: + roles = new ParticipantRole[] { ParticipantRole.PUBLISHER, ParticipantRole.MODERATOR }; + } + return roles; + } + } 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 ac1b3f6d..5b8fbe6d 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 @@ -29,7 +29,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers(HttpMethod.GET, "/config/**").authenticated() .antMatchers("/").authenticated(); - if (openviduConf.getOpenViduRecordingFreeAccess()) { + if (openviduConf.getOpenViduRecordingPublicAccess()) { conf = conf.antMatchers("/recordings/*").permitAll(); } else { conf = conf.antMatchers("/recordings/*").authenticated(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/MediaOptions.java b/openvidu-server/src/main/java/io/openvidu/server/core/MediaOptions.java index ef1d9a1b..b488c8e7 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/MediaOptions.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/MediaOptions.java @@ -5,11 +5,13 @@ public class MediaOptions { public boolean audioActive; public boolean videoActive; public String typeOfVideo; + public int frameRate; - public MediaOptions(boolean audioActive, boolean videoActive, String typeOfVideo) { + public MediaOptions(boolean audioActive, boolean videoActive, String typeOfVideo, int frameRate) { this.audioActive = audioActive; this.videoActive = videoActive; this.typeOfVideo = typeOfVideo; + this.frameRate = frameRate; } } 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 91b445f6..c77ff5ad 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 @@ -11,6 +11,7 @@ public class Participant { protected boolean audioActive = true; protected boolean videoActive = true; protected String typeOfVideo; // CAMERA, SCREEN + protected int frameRate; protected boolean streaming = false; protected volatile boolean closed; @@ -101,6 +102,14 @@ public class Participant { public void setTypeOfVideo(String typeOfVideo) { this.typeOfVideo = typeOfVideo; } + + public int getFrameRate() { + return this.frameRate; + } + + public void setFrameRate(int frameRate) { + this.frameRate = frameRate; + } public String getFullMetadata() { String fullMetadata; 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 204784bd..51642969 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 @@ -12,9 +12,9 @@ public interface Session { void join(Participant participant); - void leave(String participantPrivateId); + void leave(String participantPrivateId, String reason); - boolean close(); + boolean close(String reason); boolean isClosed(); 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 123226d4..02bfc99a 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 @@ -1,10 +1,15 @@ package io.openvidu.server.core; import java.util.HashSet; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.google.gson.JsonArray; @@ -17,10 +22,13 @@ import io.openvidu.client.internal.ProtocolElements; import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.InfoHandler; import io.openvidu.server.config.OpenviduConfig; +import io.openvidu.server.recording.Recording; import io.openvidu.server.rpc.RpcNotificationService; public class SessionEventsHandler { + private static final Logger log = LoggerFactory.getLogger(SessionEventsHandler.class); + @Autowired protected RpcNotificationService rpcNotificationService; @@ -29,24 +37,28 @@ public class SessionEventsHandler { @Autowired protected CallDetailRecord CDR; - + @Autowired protected OpenviduConfig openviduConfig; - + + Map recordingsStarted = new ConcurrentHashMap<>(); + + ReentrantLock lock = new ReentrantLock(); + public void onSessionCreated(String sessionId) { if (openviduConfig.isCdrEnabled()) { CDR.recordSessionCreated(sessionId); } } - public void onSessionClosed(String sessionId) { + public void onSessionClosed(String sessionId, String reason) { if (openviduConfig.isCdrEnabled()) { - CDR.recordSessionDestroyed(sessionId); + CDR.recordSessionDestroyed(sessionId, reason); } } - public void onParticipantJoined(Participant participant, String sessionId, - Set existingParticipants, Integer transactionId, OpenViduException error) { + public void onParticipantJoined(Participant participant, String sessionId, Set existingParticipants, + Integer transactionId, OpenViduException error) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; @@ -54,80 +66,92 @@ public class SessionEventsHandler { JsonObject result = new JsonObject(); JsonArray resultArray = new JsonArray(); - for (Participant p : existingParticipants) { + + for (Participant existingParticipant : existingParticipants) { JsonObject participantJson = new JsonObject(); - participantJson.addProperty(ProtocolElements.JOINROOM_PEERID_PARAM, p.getParticipantPublicId()); + participantJson.addProperty(ProtocolElements.JOINROOM_PEERID_PARAM, + existingParticipant.getParticipantPublicId()); // Metadata associated to each existing participant - participantJson.addProperty(ProtocolElements.JOINROOM_METADATA_PARAM, p.getFullMetadata()); + participantJson.addProperty(ProtocolElements.JOINROOM_METADATA_PARAM, + existingParticipant.getFullMetadata()); - if (p.isStreaming()) { + if (existingParticipant.isStreaming()) { String streamId = ""; - if ("SCREEN".equals(p.getTypeOfVideo())) { + if ("SCREEN".equals(existingParticipant.getTypeOfVideo())) { streamId = "SCREEN"; - } else if (p.isVideoActive()) { + } else if (existingParticipant.isVideoActive()) { streamId = "CAMERA"; - } else if (p.isAudioActive()) { + } else if (existingParticipant.isAudioActive()) { streamId = "MICRO"; } JsonObject stream = new JsonObject(); stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMID_PARAM, - p.getParticipantPublicId() + "_" + streamId); - stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM, p.isAudioActive()); - stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM, p.isVideoActive()); - stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMTYPEOFVIDEO_PARAM, p.getTypeOfVideo()); + existingParticipant.getParticipantPublicId() + "_" + streamId); + stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM, + existingParticipant.isAudioActive()); + stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM, + existingParticipant.isVideoActive()); + stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMTYPEOFVIDEO_PARAM, + existingParticipant.getTypeOfVideo()); + stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMFRAMERATE_PARAM, + existingParticipant.getFrameRate()); JsonArray streamsArray = new JsonArray(); streamsArray.add(stream); participantJson.add(ProtocolElements.JOINROOM_PEERSTREAMS_PARAM, streamsArray); } - resultArray.add(participantJson); - JsonObject notifParams = new JsonObject(); + // Avoid emitting 'connectionCreated' event of existing RECORDER participant in + // openvidu-browser in newly joined participants + if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(existingParticipant.getParticipantPublicId())) { + resultArray.add(participantJson); + } - // Metadata associated to new participant - notifParams.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, - participant.getParticipantPublicId()); - notifParams.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata()); + // If RECORDER participant has joined do NOT send 'participantJoined' + // notification to existing participants. 'recordingStarted' will be sent to all + // existing participants when recorder first subscribe to a stream + if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) { + JsonObject notifParams = new JsonObject(); - rpcNotificationService.sendNotification(p.getParticipantPrivateId(), - ProtocolElements.PARTICIPANTJOINED_METHOD, notifParams); + // Metadata associated to new participant + notifParams.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, + participant.getParticipantPublicId()); + notifParams.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, + participant.getFullMetadata()); + + rpcNotificationService.sendNotification(existingParticipant.getParticipantPrivateId(), + ProtocolElements.PARTICIPANTJOINED_METHOD, notifParams); + } } result.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, participant.getParticipantPublicId()); result.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata()); result.add("value", resultArray); rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, result); - - if (openviduConfig.isCdrEnabled()) { - CDR.recordParticipantJoined(participant, sessionId); - } } - public void onParticipantLeft(Participant participant, String sessionId, - Set remainingParticipants, Integer transactionId, OpenViduException error) { + public void onParticipantLeft(Participant participant, String sessionId, Set remainingParticipants, + Integer transactionId, OpenViduException error, String reason) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; } - - boolean isPublishing = false; - if (openviduConfig.isCdrEnabled()) { - isPublishing = CDR.stopPublisher(participant.getParticipantPublicId()); - CDR.stopAllSubscriptions(participant.getParticipantPublicId()); + + if (ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) { + // RECORDER participant + return; } JsonObject params = new JsonObject(); params.addProperty(ProtocolElements.PARTICIPANTLEFT_NAME_PARAM, participant.getParticipantPublicId()); + params.addProperty(ProtocolElements.PARTICIPANTLEFT_REASON_PARAM, reason); + for (Participant p : remainingParticipants) { rpcNotificationService.sendNotification(p.getParticipantPrivateId(), ProtocolElements.PARTICIPANTLEFT_METHOD, params); - - if (isPublishing && openviduConfig.isCdrEnabled()) { - CDR.stopSubscriber(p.getParticipantPublicId(), participant.getParticipantPublicId()); - } } if (transactionId != null) { @@ -135,16 +159,10 @@ public class SessionEventsHandler { // leaving the session rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject()); } - rpcNotificationService.closeRpcSession(participant.getParticipantPrivateId()); - - if (openviduConfig.isCdrEnabled()) { - CDR.recordParticipantLeft(participant, sessionId); - } - } - public void onPublishMedia(Participant participant, String sessionId, MediaOptions mediaOptions, - String sdpAnswer, Set participants, Integer transactionId, OpenViduException error) { + public void onPublishMedia(Participant participant, String sessionId, MediaOptions mediaOptions, String sdpAnswer, + Set participants, Integer transactionId, OpenViduException error) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; @@ -171,6 +189,7 @@ public class SessionEventsHandler { stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_AUDIOACTIVE_PARAM, mediaOptions.audioActive); stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_VIDEOACTIVE_PARAM, mediaOptions.videoActive); stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_TYPEOFVIDEO_PARAM, mediaOptions.typeOfVideo); + stream.addProperty(ProtocolElements.PARTICIPANTPUBLISHED_FRAMERATE_PARAM, mediaOptions.frameRate); JsonArray streamsArray = new JsonArray(); streamsArray.add(stream); @@ -184,26 +203,19 @@ public class SessionEventsHandler { ProtocolElements.PARTICIPANTPUBLISHED_METHOD, params); } } - - if (openviduConfig.isCdrEnabled()) { - CDR.recordNewPublisher(participant, sessionId, mediaOptions); - } } public void onUnpublishMedia(Participant participant, Set participants, Integer transactionId, - OpenViduException error) { + OpenViduException error, String reason) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; } rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject()); - - if (openviduConfig.isCdrEnabled()) { - CDR.stopPublisher(participant.getParticipantPublicId()); - } JsonObject params = new JsonObject(); params.addProperty(ProtocolElements.PARTICIPANTUNPUBLISHED_NAME_PARAM, participant.getParticipantPublicId()); + params.addProperty(ProtocolElements.PARTICIPANTUNPUBLISHED_REASON_PARAM, reason); for (Participant p : participants) { if (p.getParticipantPrivateId().equals(participant.getParticipantPrivateId())) { @@ -211,15 +223,12 @@ public class SessionEventsHandler { } else { rpcNotificationService.sendNotification(p.getParticipantPrivateId(), ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD, params); - if (openviduConfig.isCdrEnabled()) { - CDR.stopSubscriber(p.getParticipantPublicId(), participant.getParticipantPublicId()); - } } } } - public void onSubscribe(Participant participant, String sessionId, String senderName, String sdpAnswer, Integer transactionId, - OpenViduException error) { + public void onSubscribe(Participant participant, Session session, String senderName, String sdpAnswer, + Integer transactionId, OpenViduException error) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; @@ -228,21 +237,27 @@ public class SessionEventsHandler { result.addProperty(ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM, sdpAnswer); rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, result); - if (openviduConfig.isCdrEnabled()) { - CDR.recordNewSubscriber(participant, sessionId, senderName); + if (ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) { + lock.lock(); + try { + Recording recording = this.recordingsStarted.remove(session.getSessionId()); + if (recording != null) { + // RECORDER participant is now receiving video from the first publisher + this.sendRecordingStartedNotification(session, recording); + } + } finally { + lock.unlock(); + } } } - public void onUnsubscribe(Participant participant, String senderName, Integer transactionId, OpenViduException error) { + public void onUnsubscribe(Participant participant, String senderName, Integer transactionId, + OpenViduException error) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; } rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject()); - - if (openviduConfig.isCdrEnabled()) { - CDR.stopSubscriber(participant.getParticipantPublicId(), senderName); - } } public void onSendMessage(Participant participant, JsonObject message, Set participants, @@ -308,7 +323,78 @@ public class SessionEventsHandler { ProtocolElements.PARTICIPANTEVICTED_METHOD, new JsonObject()); } + public void sendRecordingStartedNotification(Session session, Recording recording) { + + if (openviduConfig.isCdrEnabled()) { + CDR.recordRecordingStarted(session.getSessionId()); + } + + // Filter participants by roles according to "openvidu.recording.notification" + Set filteredParticipants = this.filterParticipantsByRole( + this.openviduConfig.getRolesFromRecordingNotification(), session.getParticipants()); + + JsonObject params = new JsonObject(); + params.addProperty(ProtocolElements.RECORDINGSTARTED_ID_PARAM, recording.getId()); + + for (Participant p : filteredParticipants) { + rpcNotificationService.sendNotification(p.getParticipantPrivateId(), + ProtocolElements.RECORDINGSTARTED_METHOD, params); + } + } + + public void sendRecordingStoppedNotification(Session session, Recording recording) { + + if (openviduConfig.isCdrEnabled()) { + CDR.recordRecordingStopped(session.getSessionId()); + } + + // Be sure to clean this map (this should return null) + this.recordingsStarted.remove(session.getSessionId()); + + // Filter participants by roles according to "openvidu.recording.notification" + Set existingParticipants; + try { + existingParticipants = session.getParticipants(); + } catch (OpenViduException exception) { + // Session is already closed. This happens when ArchiveMode.ALWAYS and last + // participant has left the session. No notification needs to be sent + log.warn("Session already closed when trying to send 'recordingStopped' notification"); + return; + } + Set filteredParticipants = this.filterParticipantsByRole( + this.openviduConfig.getRolesFromRecordingNotification(), existingParticipants); + + JsonObject params = new JsonObject(); + params.addProperty(ProtocolElements.RECORDINGSTOPPED_ID_PARAM, recording.getId()); + + for (Participant p : filteredParticipants) { + rpcNotificationService.sendNotification(p.getParticipantPrivateId(), + ProtocolElements.RECORDINGSTOPPED_METHOD, params); + } + } + + public void closeRpcSession(String participantPrivateId) { + this.rpcNotificationService.closeRpcSession(participantPrivateId); + } + + public void setRecordingStarted(String sessionId, Recording recording) { + this.recordingsStarted.put(sessionId, recording); + } + public InfoHandler getInfoHandler() { return this.infoHandler; } + + private Set filterParticipantsByRole(ParticipantRole[] roles, Set participants) { + return participants.stream().filter(part -> { + boolean isRole = false; + for (ParticipantRole role : roles) { + isRole = role.equals(part.getToken().getRole()); + if (isRole) + break; + } + return isRole; + }).collect(Collectors.toSet()); + } + } 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 5d0e23b9..03d30689 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 @@ -12,6 +12,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.kurento.jsonrpc.message.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import com.google.gson.JsonObject; @@ -20,10 +21,25 @@ import io.openvidu.client.OpenViduException.Code; import io.openvidu.client.internal.ProtocolElements; import io.openvidu.java.client.SessionProperties; import io.openvidu.server.OpenViduServer; +import io.openvidu.server.cdr.CallDetailRecord; +import io.openvidu.server.config.OpenviduConfig; +import io.openvidu.server.recording.ComposedRecordingService; public abstract class SessionManager { private static final Logger log = LoggerFactory.getLogger(SessionManager.class); + + @Autowired + protected SessionEventsHandler sessionEventsHandler; + + @Autowired + protected ComposedRecordingService recordingService; + + @Autowired + protected CallDetailRecord CDR; + + @Autowired + protected OpenviduConfig openviduConfig; protected ConcurrentMap sessions = new ConcurrentHashMap<>(); protected ConcurrentMap sessionProperties = new ConcurrentHashMap<>(); @@ -35,11 +51,11 @@ public abstract class SessionManager { public abstract void joinRoom(Participant participant, String sessionId, Integer transactionId); - public abstract void leaveRoom(Participant participant, Integer transactionId); + public abstract void leaveRoom(Participant participant, Integer transactionId, String reason); public abstract void publishVideo(Participant participant, MediaOptions mediaOptions, Integer transactionId); - public abstract void unpublishVideo(Participant participant, Integer transactionId); + public abstract void unpublishVideo(Participant participant, Integer transactionId, String reason); public abstract void subscribe(Participant participant, String senderName, String sdpOffer, Integer transactionId); @@ -57,7 +73,7 @@ public abstract class SessionManager { * other participants about the one that's just been evicted. * */ - public void evictParticipant(String participantPrivateId) throws OpenViduException { + public void evictParticipant(String participantPrivateId, String reason) throws OpenViduException { } /** @@ -187,7 +203,7 @@ public abstract class SessionManager { } else { this.sessionidParticipantpublicidParticipant.putIfAbsent(sessionId, new ConcurrentHashMap<>()); this.sessionidTokenTokenobj.putIfAbsent(sessionId, new ConcurrentHashMap<>()); - this.sessionidTokenTokenobj.get(sessionId).putIfAbsent(token, new Token(token)); + this.sessionidTokenTokenobj.get(sessionId).putIfAbsent(token, new Token(token, ParticipantRole.PUBLISHER, "")); return true; } } @@ -252,7 +268,7 @@ public abstract class SessionManager { public Participant newRecorderParticipant(String sessionId, String participantPrivatetId, Token token, String clientMetadata) { if (this.sessionidParticipantpublicidParticipant.get(sessionId) != null) { - String participantPublicId = ProtocolElements.RECORDER_PARTICIPANT_ID_PUBLICID; + String participantPublicId = ProtocolElements.RECORDER_PARTICIPANT_PUBLICID; Participant p = new Participant(participantPrivatetId, participantPublicId, token, clientMetadata); this.sessionidParticipantpublicidParticipant.get(sessionId).put(participantPublicId, p); return p; @@ -305,7 +321,7 @@ public abstract class SessionManager { log.info("Closing all sessions"); for (String sessionId : sessions.keySet()) { try { - closeSession(sessionId); + closeSession(sessionId, "openviduServerDestroyed"); } catch (Exception e) { log.warn("Error closing session '{}'", sessionId, e); } @@ -328,7 +344,7 @@ public abstract class SessionManager { * @throws OpenViduException * in case the session doesn't exist or has been already closed */ - private Set closeSession(String sessionId) { + private Set closeSession(String sessionId, String reason) { Session session = sessions.get(sessionId); if (session == null) { throw new OpenViduException(Code.ROOM_NOT_FOUND_ERROR_CODE, "Session '" + sessionId + "' not found"); @@ -341,12 +357,14 @@ public abstract class SessionManager { Set pids = participants.stream().map(Participant::getParticipantPrivateId).collect(Collectors.toSet()); for (String pid : pids) { try { - session.leave(pid); + session.leave(pid, reason); } catch (OpenViduException e) { log.warn("Error evicting participant with id '{}' from session '{}'", pid, sessionId, e); } } - session.close(); + if (session.close(reason)) { + sessionEventsHandler.onSessionClosed(sessionId, reason); + } sessions.remove(sessionId); sessionProperties.remove(sessionId); @@ -354,6 +372,11 @@ public abstract class SessionManager { sessionidTokenTokenobj.remove(sessionId); log.warn("Session '{}' removed and closed", sessionId); + + if (recordingService.sessionIsBeingRecorded(session.getSessionId())) { + recordingService.stopRecording(session); + } + return participants; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoMediaOptions.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoMediaOptions.java index e8b8b19b..ecfd7bf3 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoMediaOptions.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoMediaOptions.java @@ -16,8 +16,8 @@ public class KurentoMediaOptions extends MediaOptions { public KurentoMediaOptions(boolean isOffer, String sdpOffer, MediaElement loopbackAlternativeSrc, MediaType loopbackConnectionType, boolean audioActive, boolean videoActive, String typeOfVideo, - boolean doLoopback, MediaElement... mediaElements) { - super(audioActive, videoActive, typeOfVideo); + int frameRate, boolean doLoopback, MediaElement... mediaElements) { + super(audioActive, videoActive, typeOfVideo, frameRate); this.isOffer = isOffer; this.sdpOffer = sdpOffer; this.loopbackAlternativeSrc = loopbackAlternativeSrc; 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 4b73d083..8b5f8347 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 @@ -21,7 +21,10 @@ import org.slf4j.LoggerFactory; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; +import io.openvidu.client.internal.ProtocolElements; +import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.InfoHandler; +import io.openvidu.server.core.MediaOptions; import io.openvidu.server.core.Participant; import io.openvidu.server.kurento.MutedMediaType; import io.openvidu.server.kurento.endpoint.MediaEndpoint; @@ -34,6 +37,7 @@ public class KurentoParticipant extends Participant { private static final Logger log = LoggerFactory.getLogger(KurentoParticipant.class); private InfoHandler infoHandler; + private CallDetailRecord CDR; private boolean webParticipant = true; @@ -46,7 +50,7 @@ public class KurentoParticipant extends Participant { private final ConcurrentMap filters = new ConcurrentHashMap<>(); private final ConcurrentMap subscribers = new ConcurrentHashMap(); - public KurentoParticipant(Participant participant, KurentoSession kurentoSession, MediaPipeline pipeline, InfoHandler infoHandler) { + public KurentoParticipant(Participant participant, KurentoSession kurentoSession, MediaPipeline pipeline, InfoHandler infoHandler, CallDetailRecord CDR) { super(participant.getParticipantPrivateId(), participant.getParticipantPublicId(), participant.getToken(), participant.getClientMetadata()); this.session = kurentoSession; @@ -60,9 +64,10 @@ public class KurentoParticipant extends Participant { } } this.infoHandler = infoHandler; + this.CDR = CDR; } - public void createPublishingEndpoint() { + 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"); @@ -70,6 +75,10 @@ public class KurentoParticipant extends Participant { this.publisher.getEndpoint().addTag("name", "PUBLISHER " + this.getParticipantPublicId()); addEndpointListeners(this.publisher); + + + CDR.recordNewPublisher(this, this.session.getSessionId(), mediaOptions); + } public void shapePublisherMedia(MediaElement element, MediaType type) { @@ -194,10 +203,10 @@ public class KurentoParticipant extends Participant { return sdpResponse; } - public void unpublishMedia() { + public void unpublishMedia(String reason) { log.info("PARTICIPANT {}: unpublishing media stream from room {}", this.getParticipantPublicId(), this.session.getSessionId()); - releasePublisherEndpoint(); + releasePublisherEndpoint(reason); this.publisher = new PublisherEndpoint(webParticipant, this, this.getParticipantPublicId(), pipeline); log.info( @@ -269,6 +278,11 @@ public class KurentoParticipant extends Participant { log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer); log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(), senderName, this.session.getSessionId()); + + if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { + CDR.recordNewSubscriber(this, this.session.getSessionId(), sender.getParticipantPublicId()); + } + return sdpAnswer; } catch (KurentoServerException e) { // TODO Check object status when KurentoClient sets this info in the object @@ -279,19 +293,19 @@ public class KurentoParticipant extends Participant { log.error("Exception connecting subscriber endpoint " + "to publisher endpoint", e); } this.subscribers.remove(senderName); - releaseSubscriberEndpoint(senderName, subscriber); + releaseSubscriberEndpoint(senderName, subscriber, ""); } return null; } - public void cancelReceivingMedia(String senderName) { + public void cancelReceivingMedia(String senderName, String reason) { log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(), senderName); SubscriberEndpoint subscriberEndpoint = subscribers.remove(senderName); if (subscriberEndpoint == null || subscriberEndpoint.getEndpoint() == null) { log.warn("PARTICIPANT {}: Trying to cancel receiving video from user {}. " + "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName); } else { - releaseSubscriberEndpoint(senderName, subscriberEndpoint); + releaseSubscriberEndpoint(senderName, subscriberEndpoint, reason); log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", this.getParticipantPublicId(), senderName, this.session.getSessionId()); } @@ -347,7 +361,7 @@ public class KurentoParticipant extends Participant { } } - public void close() { + public void close(String reason) { log.debug("PARTICIPANT {}: Closing user", this.getParticipantPublicId()); if (isClosed()) { log.warn("PARTICIPANT {}: Already closed", this.getParticipantPublicId()); @@ -357,7 +371,7 @@ public class KurentoParticipant extends Participant { for (String remoteParticipantName : subscribers.keySet()) { SubscriberEndpoint subscriber = this.subscribers.get(remoteParticipantName); if (subscriber != null && subscriber.getEndpoint() != null) { - releaseSubscriberEndpoint(remoteParticipantName, subscriber); + releaseSubscriberEndpoint(remoteParticipantName, subscriber, reason); log.debug("PARTICIPANT {}: Released subscriber endpoint to {}", this.getParticipantPublicId(), remoteParticipantName); } else { @@ -367,7 +381,7 @@ public class KurentoParticipant extends Participant { this.getParticipantPublicId(), remoteParticipantName); } } - releasePublisherEndpoint(); + releasePublisherEndpoint(reason); } /** @@ -410,7 +424,7 @@ public class KurentoParticipant extends Participant { session.sendMediaError(this.getParticipantPrivateId(), desc); } - private void releasePublisherEndpoint() { + private void releasePublisherEndpoint(String reason) { if (publisher != null && publisher.getEndpoint() != null) { publisher.unregisterErrorListeners(); for (MediaElement el : publisher.getMediaElements()) { @@ -419,15 +433,23 @@ public class KurentoParticipant extends Participant { releaseElement(getParticipantPublicId(), publisher.getEndpoint()); this.streaming = false; publisher = null; + + CDR.stopPublisher(this.getParticipantPublicId(), reason); + } else { log.warn("PARTICIPANT {}: Trying to release publisher endpoint but is null", getParticipantPublicId()); } } - private void releaseSubscriberEndpoint(String senderName, SubscriberEndpoint subscriber) { + private void releaseSubscriberEndpoint(String senderName, SubscriberEndpoint subscriber, String reason) { if (subscriber != null) { subscriber.unregisterErrorListeners(); releaseElement(senderName, subscriber.getEndpoint()); + + if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { + CDR.stopSubscriber(this.getParticipantPublicId(), senderName, reason); + } + } else { log.warn("PARTICIPANT {}: Trying to release subscriber endpoint for '{}' but is null", this.getParticipantPublicId(), senderName); 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 e0dc7e61..84164576 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 @@ -19,7 +19,9 @@ import org.slf4j.LoggerFactory; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; +import io.openvidu.client.internal.ProtocolElements; import io.openvidu.java.client.SessionProperties; +import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.core.Participant; import io.openvidu.server.core.Session; @@ -50,14 +52,17 @@ public class KurentoSession implements Session { private Object pipelineReleaseLock = new Object(); private volatile boolean pipelineReleased = false; private boolean destroyKurentoClient; + + private CallDetailRecord CDR; public KurentoSession(String sessionId, SessionProperties sessionProperties, KurentoClient kurentoClient, KurentoSessionEventsHandler kurentoSessionHandler, - boolean destroyKurentoClient) { + boolean destroyKurentoClient, CallDetailRecord CDR) { this.sessionId = sessionId; this.sessionProperties = sessionProperties; this.kurentoClient = kurentoClient; this.destroyKurentoClient = destroyKurentoClient; this.kurentoSessionHandler = kurentoSessionHandler; + this.CDR = CDR; log.debug("New SESSION instance with id '{}'", sessionId); } @@ -76,7 +81,7 @@ public class KurentoSession implements Session { checkClosed(); createPipeline(); - KurentoParticipant kurentoParticipant = new KurentoParticipant(participant, this, getPipeline(), kurentoSessionHandler.getInfoHandler()); + KurentoParticipant kurentoParticipant = new KurentoParticipant(participant, this, getPipeline(), kurentoSessionHandler.getInfoHandler(), this.CDR); participants.put(participant.getParticipantPrivateId(), kurentoParticipant); filterStates.forEach((filterId, state) -> { @@ -85,6 +90,10 @@ public class KurentoSession implements Session { }); log.info("SESSION {}: Added participant {}", sessionId, participant); + + if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) { + CDR.recordParticipantJoined(participant, sessionId); + } } public void newPublisher(Participant participant) { @@ -102,7 +111,7 @@ public class KurentoSession implements Session { participants.values(), participant.getParticipantPublicId()); } - public void cancelPublisher(Participant participant) { + public void cancelPublisher(Participant participant, String reason) { deregisterPublisher(); // cancel recv video from this publisher @@ -110,7 +119,7 @@ public class KurentoSession implements Session { if (participant.equals(subscriber)) { continue; } - subscriber.cancelReceivingMedia(participant.getParticipantPublicId()); + subscriber.cancelReceivingMedia(participant.getParticipantPublicId(), reason); } @@ -120,7 +129,7 @@ public class KurentoSession implements Session { } @Override - public void leave(String participantPrivateId) throws OpenViduException { + public void leave(String participantPrivateId, String reason) throws OpenViduException { checkClosed(); @@ -135,8 +144,12 @@ public class KurentoSession implements Session { if (participant.isStreaming()) { this.deregisterPublisher(); } - this.removeParticipant(participant); - participant.close(); + this.removeParticipant(participant, reason); + participant.close(reason); + + if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) { + CDR.recordParticipantLeft(participant, participant.getSession().getSessionId(), reason); + } } @Override @@ -163,12 +176,12 @@ public class KurentoSession implements Session { } @Override - public boolean close() { + public boolean close(String reason) { if (!closed) { for (KurentoParticipant participant : participants.values()) { participant.releaseAllFilters(); - participant.close(); + participant.close(reason); } participants.clear(); @@ -208,7 +221,7 @@ public class KurentoSession implements Session { } } - private void removeParticipant(Participant participant) { + private void removeParticipant(Participant participant, String reason) { checkClosed(); @@ -216,7 +229,7 @@ public class KurentoSession implements Session { log.debug("SESSION {}: Cancel receiving media from participant '{}' for other participant", this.sessionId, participant.getParticipantPublicId()); for (KurentoParticipant other : participants.values()) { - other.cancelReceivingMedia(participant.getParticipantPublicId()); + other.cancelReceivingMedia(participant.getParticipantPublicId(), reason); } } 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 9a215fa3..e4b80bb0 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 @@ -27,9 +27,7 @@ import io.openvidu.server.kurento.KurentoClientProvider; import io.openvidu.server.kurento.KurentoClientSessionInfo; import io.openvidu.server.kurento.OpenViduKurentoClientSessionInfo; import io.openvidu.server.kurento.endpoint.SdpType; -import io.openvidu.server.recording.ComposedRecordingService; import io.openvidu.server.rpc.RpcHandler; -import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.MediaOptions; import io.openvidu.server.core.Participant; import io.openvidu.server.core.Session; @@ -42,13 +40,7 @@ public class KurentoSessionManager extends SessionManager { private KurentoClientProvider kcProvider; @Autowired - private KurentoSessionEventsHandler sessionHandler; - - @Autowired - private ComposedRecordingService recordingService; - - @Autowired - OpenviduConfig openviduConfig; + private KurentoSessionEventsHandler kurentoSessionEventsHandler; @Override public synchronized void joinRoom(Participant participant, String sessionId, Integer transactionId) { @@ -57,8 +49,8 @@ public class KurentoSessionManager extends SessionManager { KurentoClientSessionInfo kcSessionInfo = new OpenViduKurentoClientSessionInfo( participant.getParticipantPrivateId(), sessionId); - KurentoSession session = (KurentoSession) sessions.get(sessionId); + if (session == null && kcSessionInfo != null) { SessionProperties properties = sessionProperties.get(sessionId); if (properties == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) { @@ -85,27 +77,30 @@ public class KurentoSessionManager extends SessionManager { } catch (OpenViduException e) { log.warn("PARTICIPANT {}: Error joining/creating session {}", participant.getParticipantPublicId(), sessionId, e); - sessionHandler.onParticipantJoined(participant, sessionId, null, transactionId, e); + sessionEventsHandler.onParticipantJoined(participant, sessionId, null, transactionId, e); } if (existingParticipants != null) { - sessionHandler.onParticipantJoined(participant, sessionId, existingParticipants, transactionId, null); + sessionEventsHandler.onParticipantJoined(participant, sessionId, existingParticipants, transactionId, null); } } @Override - public void leaveRoom(Participant participant, Integer transactionId) { + public void leaveRoom(Participant participant, Integer transactionId, String reason) { log.debug("Request [LEAVE_ROOM] ({})", participant.getParticipantPublicId()); KurentoParticipant kParticipant = (KurentoParticipant) participant; KurentoSession session = kParticipant.getSession(); String sessionId = session.getSessionId(); + if (session.isClosed()) { log.warn("'{}' is trying to leave from session '{}' but it is closing", participant.getParticipantPublicId(), sessionId); throw new OpenViduException(Code.ROOM_CLOSED_ERROR_CODE, "'" + participant.getParticipantPublicId() + "' is trying to leave from session '" + sessionId + "' but it is closing"); } - session.leave(participant.getParticipantPrivateId()); + session.leave(participant.getParticipantPrivateId(), reason); + + // Update control data structures if (sessionidParticipantpublicidParticipant.get(sessionId) != null) { Participant p = sessionidParticipantpublicidParticipant.get(sessionId) @@ -127,6 +122,8 @@ public class KurentoSessionManager extends SessionManager { showTokens(); + // Close Session if no more participants + Set remainingParticipants = null; try { remainingParticipants = getParticipants(sessionId); @@ -134,10 +131,15 @@ public class KurentoSessionManager extends SessionManager { log.debug("Possible collision when closing the session '{}' (not found)"); remainingParticipants = Collections.emptySet(); } + + sessionEventsHandler.onParticipantLeft(participant, sessionId, remainingParticipants, transactionId, null, + reason); + if (remainingParticipants.isEmpty()) { + log.info("No more participants in session '{}', removing it and closing it", sessionId); - if (session.close()) { - sessionHandler.onSessionClosed(sessionId); + if (session.close(reason)) { + sessionEventsHandler.onSessionClosed(sessionId, "lastParticipantLeft"); } sessions.remove(sessionId); @@ -148,17 +150,21 @@ public class KurentoSessionManager extends SessionManager { showTokens(); log.warn("Session '{}' removed and closed", sessionId); - } - if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled() + + } else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled() && MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) && ArchiveMode.ALWAYS.equals(session.getSessionProperties().archiveMode()) - && ProtocolElements.RECORDER_PARTICIPANT_ID_PUBLICID + && ProtocolElements.RECORDER_PARTICIPANT_PUBLICID .equals(remainingParticipants.iterator().next().getParticipantPublicId())) { + log.info("Last participant left. Stopping recording for session {}", sessionId); - evictParticipant(session.getParticipantByPublicId("RECORDER").getParticipantPrivateId()); recordingService.stopRecording(session); + evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID) + .getParticipantPrivateId(), "EVICT_RECORDER"); } - sessionHandler.onParticipantLeft(participant, sessionId, remainingParticipants, transactionId, null); + + // Finally close websocket session + sessionEventsHandler.closeRpcSession(participant.getParticipantPrivateId()); } /** @@ -205,7 +211,7 @@ public class KurentoSessionManager extends SessionManager { SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER; KurentoSession session = kurentoParticipant.getSession(); - kurentoParticipant.createPublishingEndpoint(); + kurentoParticipant.createPublishingEndpoint(mediaOptions); for (MediaElement elem : kurentoOptions.mediaElements) { kurentoParticipant.getPublisher().apply(elem); @@ -218,13 +224,14 @@ 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); - sessionHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, participants, - transactionId, e); + sessionEventsHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, + participants, transactionId, e); } if (this.openviduConfig.isRecordingModuleEnabled() && MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) && ArchiveMode.ALWAYS.equals(session.getSessionProperties().archiveMode()) + && !recordingService.sessionIsBeingRecorded(session.getSessionId()) && session.getActivePublishers() == 0) { recordingService.startRecording(session); } @@ -234,17 +241,18 @@ public class KurentoSessionManager extends SessionManager { kurentoParticipant.setAudioActive(kurentoOptions.audioActive); kurentoParticipant.setVideoActive(kurentoOptions.videoActive); kurentoParticipant.setTypeOfVideo(kurentoOptions.typeOfVideo); + kurentoParticipant.setFrameRate(kurentoOptions.frameRate); participants = kurentoParticipant.getSession().getParticipants(); if (sdpAnswer != null) { - sessionHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, participants, - transactionId, null); + sessionEventsHandler.onPublishMedia(participant, session.getSessionId(), mediaOptions, sdpAnswer, + participants, transactionId, null); } } @Override - public void unpublishVideo(Participant participant, Integer transactionId) { + public void unpublishVideo(Participant participant, Integer transactionId, String reason) { try { KurentoParticipant kParticipant = (KurentoParticipant) participant; KurentoSession session = kParticipant.getSession(); @@ -254,16 +262,16 @@ public class KurentoSessionManager extends SessionManager { throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Participant '" + participant.getParticipantPublicId() + "' is not streaming media"); } - kParticipant.unpublishMedia(); - session.cancelPublisher(participant); + kParticipant.unpublishMedia(reason); + session.cancelPublisher(participant, reason); Set participants = session.getParticipants(); - sessionHandler.onUnpublishMedia(participant, participants, transactionId, null); + sessionEventsHandler.onUnpublishMedia(participant, participants, transactionId, null, reason); } catch (OpenViduException e) { log.warn("PARTICIPANT {}: Error unpublishing media", participant.getParticipantPublicId(), e); - sessionHandler.onUnpublishMedia(participant, null, transactionId, e); + sessionEventsHandler.onUnpublishMedia(participant, null, transactionId, e, ""); } } @@ -304,10 +312,10 @@ public class KurentoSessionManager extends SessionManager { } } catch (OpenViduException e) { log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, e); - sessionHandler.onSubscribe(participant, session.getSessionId(), senderName, null, transactionId, e); + sessionEventsHandler.onSubscribe(participant, session, senderName, null, transactionId, e); } if (sdpAnswer != null) { - sessionHandler.onSubscribe(participant, session.getSessionId(), senderName, sdpAnswer, transactionId, null); + sessionEventsHandler.onSubscribe(participant, session, senderName, sdpAnswer, transactionId, null); } } @@ -328,9 +336,9 @@ public class KurentoSessionManager extends SessionManager { "User " + senderName + " not found in room " + session.getSessionId()); } - kParticipant.cancelReceivingMedia(senderName); + kParticipant.cancelReceivingMedia(senderName, "unsubscribe"); - sessionHandler.onUnsubscribe(participant, senderName, transactionId, null); + sessionEventsHandler.onUnsubscribe(participant, senderName, transactionId, null); } @Override @@ -338,7 +346,7 @@ public class KurentoSessionManager extends SessionManager { try { JsonObject messageJSON = new JsonParser().parse(message).getAsJsonObject(); KurentoParticipant kParticipant = (KurentoParticipant) participant; - sessionHandler.onSendMessage(participant, messageJSON, + sessionEventsHandler.onSendMessage(participant, messageJSON, getParticipants(kParticipant.getSession().getSessionId()), transactionId, null); } catch (JsonSyntaxException | IllegalStateException e) { throw new OpenViduException(Code.SIGNAL_FORMAT_INVALID_ERROR_CODE, @@ -354,11 +362,11 @@ public class KurentoSessionManager extends SessionManager { log.debug("Request [ICE_CANDIDATE] endpoint={} candidate={} " + "sdpMLineIdx={} sdpMid={} ({})", endpointName, candidate, sdpMLineIndex, sdpMid, participant.getParticipantPublicId()); kParticipant.addIceCandidate(endpointName, new IceCandidate(candidate, sdpMid, sdpMLineIndex)); - sessionHandler.onRecvIceCandidate(participant, transactionId, null); + sessionEventsHandler.onRecvIceCandidate(participant, transactionId, null); } catch (OpenViduException e) { log.error("PARTICIPANT {}: Error receiving ICE " + "candidate (epName={}, candidate={})", participant.getParticipantPublicId(), endpointName, candidate, e); - sessionHandler.onRecvIceCandidate(participant, transactionId, e); + sessionEventsHandler.onRecvIceCandidate(participant, transactionId, e); } } @@ -382,8 +390,8 @@ public class KurentoSessionManager extends SessionManager { "Session '" + sessionId + "' already exists"); } KurentoClient kurentoClient = kcProvider.getKurentoClient(kcSessionInfo); - session = new KurentoSession(sessionId, sessionProperties, kurentoClient, sessionHandler, - kcProvider.destroyWhenUnused()); + session = new KurentoSession(sessionId, sessionProperties, kurentoClient, kurentoSessionEventsHandler, + kcProvider.destroyWhenUnused(), this.CDR); KurentoSession oldSession = (KurentoSession) sessions.putIfAbsent(sessionId, session); if (oldSession != null) { @@ -396,7 +404,7 @@ public class KurentoSessionManager extends SessionManager { } log.warn("No session '{}' exists yet. Created one using KurentoClient '{}'.", sessionId, kcName); - sessionHandler.onSessionCreated(sessionId); + sessionEventsHandler.onSessionCreated(sessionId); } /** @@ -407,10 +415,10 @@ public class KurentoSessionManager extends SessionManager { * */ @Override - public void evictParticipant(String participantPrivateId) throws OpenViduException { + public void evictParticipant(String participantPrivateId, String reason) throws OpenViduException { Participant participant = this.getParticipant(participantPrivateId); - this.leaveRoom(participant, null); - sessionHandler.onParticipantEvicted(participant); + this.leaveRoom(participant, null, reason); + sessionEventsHandler.onParticipantEvicted(participant); } @Override @@ -420,9 +428,11 @@ public class KurentoSessionManager extends SessionManager { boolean audioActive = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_AUDIOACTIVE_PARAM); boolean videoActive = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_VIDEOACTIVE_PARAM); String typeOfVideo = RpcHandler.getStringParam(request, ProtocolElements.PUBLISHVIDEO_TYPEOFVIDEO_PARAM); + int frameRate = RpcHandler.getIntParam(request, ProtocolElements.PUBLISHVIDEO_FRAMERATE_PARAM); boolean doLoopback = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_DOLOOPBACK_PARAM); - return new KurentoMediaOptions(true, sdpOffer, null, null, audioActive, videoActive, typeOfVideo, doLoopback); + return new KurentoMediaOptions(true, sdpOffer, null, null, audioActive, videoActive, typeOfVideo, frameRate, + doLoopback); } } 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 a79d44e4..637f8354 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 @@ -51,6 +51,7 @@ import io.openvidu.server.CommandExecutor; import io.openvidu.server.OpenViduServer; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.Session; +import io.openvidu.server.core.SessionEventsHandler; @Service public class ComposedRecordingService { @@ -59,6 +60,9 @@ public class ComposedRecordingService { @Autowired OpenviduConfig openviduConfig; + + @Autowired + private SessionEventsHandler sessionHandler; private Map containers = new ConcurrentHashMap<>(); private Map sessionsContainers = new ConcurrentHashMap<>(); @@ -81,12 +85,13 @@ public class ComposedRecordingService { List envs = new ArrayList<>(); String shortSessionId = session.getSessionId().substring(session.getSessionId().lastIndexOf('/') + 1, session.getSessionId().length()); - String videoId = this.getFreeRecordingId(session.getSessionId(), shortSessionId); + String recordingId = this.getFreeRecordingId(session.getSessionId(), shortSessionId); String secret = openviduConfig.getOpenViduSecret(); - Recording recording = new Recording(session.getSessionId(), videoId, videoId); + Recording recording = new Recording(session.getSessionId(), recordingId, recordingId); this.sessionsRecordings.put(session.getSessionId(), recording); + this.sessionHandler.setRecordingStarted(session.getSessionId(), recording); this.startingRecordings.put(recording.getId(), recording); String uid = null; @@ -106,7 +111,7 @@ public class ComposedRecordingService { + "/" + secret); envs.add("RESOLUTION=1920x1080"); envs.add("FRAMERATE=30"); - envs.add("VIDEO_NAME=" + videoId); + envs.add("VIDEO_NAME=" + recordingId); envs.add("VIDEO_FORMAT=mp4"); envs.add("USER_ID=" + uid); envs.add("RECORDING_JSON=" + recording.toJson().toJSONString()); @@ -115,9 +120,9 @@ public class ComposedRecordingService { log.debug("Recorder connecting to url {}", "https://OPENVIDUAPP:" + secret + "@localhost:8443/#/layout-best-fit/" + shortSessionId + "/" + secret); - String containerId = this.runRecordingContainer(envs, "recording_" + videoId); + String containerId = this.runRecordingContainer(envs, "recording_" + recordingId); - this.waitForVideoFileNotEmpty(videoId); + this.waitForVideoFileNotEmpty(recordingId); this.sessionsContainers.put(session.getSessionId(), containerId); @@ -171,7 +176,7 @@ public class ComposedRecordingService { RecordingInfoUtils infoUtils = new RecordingInfoUtils( this.openviduConfig.getOpenViduRecordingPath() + recording.getName() + ".info"); - if (openviduConfig.getOpenViduRecordingFreeAccess()) { + if (openviduConfig.getOpenViduRecordingPublicAccess()) { recording.setStatus(Recording.Status.available); } else { recording.setStatus(Recording.Status.stopped); @@ -181,7 +186,7 @@ public class ComposedRecordingService { recording.setHasAudio(infoUtils.hasAudio()); recording.setHasVideo(infoUtils.hasVideo()); - if (openviduConfig.getOpenViduRecordingFreeAccess()) { + if (openviduConfig.getOpenViduRecordingPublicAccess()) { recording.setUrl(this.openviduConfig.getFinalUrl() + "recordings/" + recording.getName() + ".mp4"); } @@ -189,7 +194,9 @@ public class ComposedRecordingService { throw new OpenViduException(Code.RECORDING_REPORT_ERROR_CODE, "There was an error generating the metadata report file for the recording"); } - + + this.sessionHandler.sendRecordingStoppedNotification(session, recording); + return recording; } @@ -341,7 +348,7 @@ public class ComposedRecordingService { for (int i = 0; i < files.length; i++) { Recording recording = this.getRecordingFromFile(files[i]); if (recording != null) { - if (openviduConfig.getOpenViduRecordingFreeAccess()) { + if (openviduConfig.getOpenViduRecordingPublicAccess()) { if (Recording.Status.stopped.equals(recording.getStatus())) { recording.setStatus(Recording.Status.available); recording.setUrl( 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 80548235..fa0dbded 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 @@ -16,8 +16,6 @@ */ package io.openvidu.server.rest; -import static org.kurento.commons.PropertiesManager.getProperty; - import java.util.Collection; import java.util.Map; import java.util.NoSuchElementException; @@ -36,6 +34,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import io.openvidu.client.OpenViduException; +import io.openvidu.client.internal.ProtocolElements; import io.openvidu.java.client.ArchiveLayout; import io.openvidu.java.client.ArchiveMode; import io.openvidu.java.client.MediaMode; @@ -55,9 +54,6 @@ import io.openvidu.server.recording.ComposedRecordingService; @RequestMapping("/api") public class SessionRestController { - private static final int UPDATE_SPEAKER_INTERVAL_DEFAULT = 1800; - private static final int THRESHOLD_SPEAKER_DEFAULT = -50; - @Autowired private SessionManager sessionManager; @@ -69,16 +65,6 @@ public class SessionRestController { return sessionManager.getSessions(); } - @RequestMapping("/getUpdateSpeakerInterval") - public Integer getUpdateSpeakerInterval() { - return Integer.valueOf(getProperty("updateSpeakerInterval", UPDATE_SPEAKER_INTERVAL_DEFAULT)); - } - - @RequestMapping("/getThresholdSpeaker") - public Integer getThresholdSpeaker() { - return Integer.valueOf(getProperty("thresholdSpeaker", THRESHOLD_SPEAKER_DEFAULT)); - } - @SuppressWarnings("unchecked") @RequestMapping(value = "/sessions", method = RequestMethod.POST) public ResponseEntity getSessionId(@RequestBody(required = false) Map params) { @@ -197,9 +183,14 @@ public class SessionRestController { // Session is not being recorded return new ResponseEntity(HttpStatus.CONFLICT); } - + + Session session = sessionManager.getSession(recording.getSessionId()); + Recording stoppedRecording = this.recordingService - .stopRecording(sessionManager.getSession(recording.getSessionId())); + .stopRecording(session); + + sessionManager.evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID).getParticipantPrivateId(), "EVICT_RECORDER"); + return new ResponseEntity<>(stoppedRecording.toJson(), HttpStatus.OK); } 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 0e44c76d..005b2ab8 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 @@ -21,7 +21,6 @@ import io.openvidu.client.internal.ProtocolElements; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.MediaOptions; import io.openvidu.server.core.Participant; -import io.openvidu.server.core.ParticipantRole; import io.openvidu.server.core.SessionManager; import io.openvidu.server.core.Token; @@ -176,18 +175,18 @@ public class RpcHandler extends DefaultJsonRpcHandler { if (sessionId == null) { // null when afterConnectionClosed log.warn("No session information found for participant with privateId {}. " + "Using the admin method to evict the user.", participantPrivateId); - leaveRoomAfterConnClosed(participantPrivateId); + leaveRoomAfterConnClosed(participantPrivateId, ""); } else { // Sanity check: don't call leaveRoom unless the id checks out Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId); if (participant != null) { log.info("Participant {} is leaving session {}", participant.getParticipantPublicId(), sessionId); - sessionManager.leaveRoom(participant, request.getId()); + sessionManager.leaveRoom(participant, request.getId(), "disconnect"); log.info("Participant {} has left session {}", participant.getParticipantPublicId(), sessionId); } else { log.warn("Participant with private id {} not found in session {}. " + "Using the admin method to evict the user.", participantPrivateId, sessionId); - leaveRoomAfterConnClosed(participantPrivateId); + leaveRoomAfterConnClosed(participantPrivateId, ""); } } } @@ -263,12 +262,12 @@ public class RpcHandler extends DefaultJsonRpcHandler { String sessionId = rpcConnection.getSessionId(); Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId); - sessionManager.unpublishVideo(participant, request.getId()); + sessionManager.unpublishVideo(participant, request.getId(), "unpublish"); } - public void leaveRoomAfterConnClosed(String participantPrivateId) { + public void leaveRoomAfterConnClosed(String participantPrivateId, String reason) { try { - sessionManager.evictParticipant(participantPrivateId); + sessionManager.evictParticipant(participantPrivateId, reason); log.info("Evicted participant with privateId {}", participantPrivateId); } catch (OpenViduException e) { log.warn("Unable to evict: {}", e.getMessage()); @@ -290,7 +289,7 @@ public class RpcHandler extends DefaultJsonRpcHandler { 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) { - leaveRoomAfterConnClosed(rpc.getParticipantPrivateId()); + leaveRoomAfterConnClosed(rpc.getParticipantPrivateId(), "networkDisconnect"); } } @@ -300,7 +299,7 @@ public class RpcHandler extends DefaultJsonRpcHandler { 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); + this.leaveRoomAfterConnClosed(rpcSessionId, "networkDisconnect"); this.webSocketTransportError.remove(rpcSessionId); } } diff --git a/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index b6c66709..ccd5b5c1 100644 --- a/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -30,11 +30,16 @@ "description": "Where to store the recorded video files" }, { - "name": "openvidu.recording.free-access", + "name": "openvidu.recording.public-access", "type": "java.lang.Boolean", - "description": "'true' to allow free access to the video files specified in 'openviu.recording.path'. 'false' to only allow access to authenticated users" + "description": "'true' to allow public access to the video files specified in 'openviu.recording.path'. 'false' to only allow access to authenticated users" }, - { + { + "name": "openvidu.recording.notification", + "type": "java.lang.String", + "description": "Which users will receive a notfication (client events 'recordingStarted' and 'recordingStopped') when recording starts and stops: 'none', 'publisher_moderator', 'all'" + }, + { "name": "openvidu.recording.version", "type": "java.lang.String", "description": "Tag for openvidu/openvidu-recording Docker image" diff --git a/openvidu-server/src/main/resources/application.properties b/openvidu-server/src/main/resources/application.properties index 754683f0..60eabdc1 100644 --- a/openvidu-server/src/main/resources/application.properties +++ b/openvidu-server/src/main/resources/application.properties @@ -16,4 +16,5 @@ openvidu.publicurl: local openvidu.cdr: false openvidu.recording: false openvidu.recording.path: /opt/openvidu/recordings -openvidu.recording.free-access: false \ No newline at end of file +openvidu.recording.public-access: false +openvidu.recording.notification: publisher_moderator \ No newline at end of file