diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index d9aa3343..a9780cae 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -1436,6 +1436,10 @@ export class Session extends EventDispatcher { this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', stream, '')]); }); + if (!!response.recordingId && !!response.recordingName) { + this.ee.emitEvent('recordingStarted', [new RecordingEvent(this, 'recordingStarted', response.recordingId, response.recordingName)]); + } + return resolve(); } }); diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts index 9f52796c..ef5fc3ea 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts @@ -36,5 +36,7 @@ export interface LocalConnectionOptions { mediaServer: string; videoSimulcast: boolean; life: number; - customIceServers?: IceServerProperties[] + customIceServers?: IceServerProperties[]; + recordingId?: string; // Defined if the session is being recorded and the client must be notified + recordingName?: string; // Defined if the session is being recorded and the client must be notified } 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 861f874d..2e0d3e88 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 @@ -166,6 +166,8 @@ public class ProtocolElements { public static final String PARTICIPANTJOINED_CUSTOM_ICE_SERVERS = "customIceServers"; public static final String PARTICIPANTJOINED_TURNUSERNAME_PARAM = "turnUsername"; public static final String PARTICIPANTJOINED_TURNCREDENTIAL_PARAM = "turnCredential"; + public static final String PARTICIPANTJOINED_RECORDINGID_PARAM = "recordingId"; + public static final String PARTICIPANTJOINED_RECORDINGNAME_PARAM = "recordingName"; public static final String PARTICIPANTLEFT_METHOD = "participantLeft"; public static final String PARTICIPANTLEFT_NAME_PARAM = "connectionId"; 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 30b0a217..8e18f262 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 @@ -32,10 +32,11 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import javax.annotation.PostConstruct; @@ -413,23 +414,26 @@ public class OpenviduConfig { return this.mediaNodesPublicIps; } - public OpenViduRole[] getRolesFromRecordingNotification() { - OpenViduRole[] roles; + public Set getRolesFromRecordingNotification() { + Set roles = new HashSet<>(); switch (this.openviduRecordingNotification) { case none: - roles = new OpenViduRole[0]; break; case moderator: - roles = new OpenViduRole[] { OpenViduRole.MODERATOR }; + roles.add(OpenViduRole.MODERATOR); break; case publisher_moderator: - roles = new OpenViduRole[] { OpenViduRole.PUBLISHER, OpenViduRole.MODERATOR }; + roles.add(OpenViduRole.PUBLISHER); + roles.add(OpenViduRole.MODERATOR); break; case all: - roles = new OpenViduRole[] { OpenViduRole.SUBSCRIBER, OpenViduRole.PUBLISHER, OpenViduRole.MODERATOR }; + roles.add(OpenViduRole.SUBSCRIBER); + roles.add(OpenViduRole.PUBLISHER); + roles.add(OpenViduRole.MODERATOR); break; default: - roles = new OpenViduRole[] { OpenViduRole.PUBLISHER, OpenViduRole.MODERATOR }; + roles.add(OpenViduRole.PUBLISHER); + roles.add(OpenViduRole.MODERATOR); } return roles; } @@ -547,7 +551,8 @@ public class OpenviduConfig { protected List getNonUserProperties() { return Arrays.asList("server.port", "SERVER_PORT", "DOTENV_PATH", "COTURN_IP", "COTURN_PORT", - "COTURN_INTERNAL_RELAY", "COTURN_SHARED_SECRET_KEY", "OPENVIDU_RECORDING_IMAGE", "OPENVIDU_RECORDING_ENABLE_GPU"); + "COTURN_INTERNAL_RELAY", "COTURN_SHARED_SECRET_KEY", "OPENVIDU_RECORDING_IMAGE", + "OPENVIDU_RECORDING_ENABLE_GPU"); } protected List getNonPrintablePropertiesIfEmpty() { 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 768ea975..3f82f40f 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 @@ -74,8 +74,8 @@ public class SessionEventsHandler { CDR.recordSessionDestroyed(session, reason); } - public void onParticipantJoined(Participant participant, String sessionId, String coturnIp, Set existingParticipants, - Integer transactionId, OpenViduException error) { + public void onParticipantJoined(Participant participant, Recording recording, String coturnIp, + Set existingParticipants, Integer transactionId, OpenViduException error) { if (error != null) { rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error); return; @@ -179,7 +179,9 @@ public class SessionEventsHandler { } if (participant.getToken() != null) { + result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record()); + if (participant.getToken().getRole() != null) { result.addProperty(ProtocolElements.PARTICIPANTJOINED_ROLE_PARAM, participant.getToken().getRole().name()); @@ -198,6 +200,11 @@ public class SessionEventsHandler { result.addProperty(ProtocolElements.PARTICIPANTJOINED_TURNCREDENTIAL_PARAM, participant.getToken().getTurnCredentials().getCredential()); } + if (recording != null) { + result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORDINGID_PARAM, recording.getId()); + result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORDINGNAME_PARAM, recording.getName()); + } + } rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, result); @@ -680,18 +687,12 @@ public class SessionEventsHandler { recordingsToSendClientEvents.put(recording.getSessionId(), recording); } - protected Set filterParticipantsByRole(OpenViduRole[] roles, Set participants) { + protected Set filterParticipantsByRole(Set roles, Set participants) { return participants.stream().filter(part -> { if (ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(part.getParticipantPublicId())) { return false; } - boolean isRole = false; - for (OpenViduRole role : roles) { - isRole = role.equals(part.getToken().getRole()); - if (isRole) - break; - } - return isRole; + return roles.contains(part.getToken().getRole()); }).collect(Collectors.toSet()); } 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 ec339ea0..b60b9d8f 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 @@ -136,7 +136,7 @@ public class KurentoSessionManager extends SessionManager { String error = "Timeout of " + KmsManager.MAX_SECONDS_LOCK_WAIT + " seconds waiting to acquire lock"; log.error(error); - sessionEventsHandler.onParticipantJoined(participant, sessionId, null, null, transactionId, + sessionEventsHandler.onParticipantJoined(participant, null, null, null, transactionId, new OpenViduException(Code.ROOM_CANNOT_BE_CREATED_ERROR_CODE, error)); return; } @@ -169,7 +169,11 @@ public class KurentoSessionManager extends SessionManager { existingParticipants = getParticipants(sessionId); kSession.join(participant); String coturnIp = openviduConfig.getCoturnIp(kSession.getKms().getUri()); - sessionEventsHandler.onParticipantJoined(participant, sessionId, coturnIp, existingParticipants, + + io.openvidu.server.recording.Recording recording = getActiveRecordingIfAllowedByParticipantRole( + participant); + + sessionEventsHandler.onParticipantJoined(participant, recording, coturnIp, existingParticipants, transactionId, null); } finally { kSession.joinLeaveLock.unlock(); @@ -178,21 +182,21 @@ public class KurentoSessionManager extends SessionManager { log.error( "Timeout waiting for join-leave Session lock to be available for participant {} of session {} in joinRoom", participant.getParticipantPublicId(), sessionId); - sessionEventsHandler.onParticipantJoined(participant, sessionId, null, null, transactionId, + sessionEventsHandler.onParticipantJoined(participant, null, null, null, transactionId, new OpenViduException(Code.GENERIC_ERROR_CODE, "Timeout waiting for Session lock")); } } catch (InterruptedException e) { log.error( "InterruptedException waiting for join-leave Session lock to be available for participant {} of session {} in joinRoom", participant.getParticipantPublicId(), sessionId); - sessionEventsHandler.onParticipantJoined(participant, sessionId, null,null, transactionId, + sessionEventsHandler.onParticipantJoined(participant, null, null, null, transactionId, new OpenViduException(Code.GENERIC_ERROR_CODE, "InterruptedException waiting for Session lock")); } } catch (OpenViduException e) { log.error("PARTICIPANT {}: Error joining/creating session {}", participant.getParticipantPublicId(), sessionId, e); - sessionEventsHandler.onParticipantJoined(participant, sessionId, null,null, transactionId, e); + sessionEventsHandler.onParticipantJoined(participant, null, null, null, transactionId, e); } } @@ -1406,6 +1410,16 @@ public class KurentoSessionManager extends SessionManager { return lessLoadedKms; } + private io.openvidu.server.recording.Recording getActiveRecordingIfAllowedByParticipantRole( + Participant participant) { + io.openvidu.server.recording.Recording recording = null; + if (participant.getToken() != null && this.recordingManager.sessionIsBeingRecorded(participant.getSessionId()) + && this.openviduConfig.getRolesFromRecordingNotification().contains(participant.getToken().getRole())) { + recording = this.recordingManager.getActiveRecordingForSession(participant.getSessionId()); + } + return recording; + } + @PreDestroy @Override public void close() { diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java index 67d6c4ee..8e248936 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java @@ -492,6 +492,14 @@ public class RecordingManager { || this.sessionsRecordingsStarting.get(sessionId) != null); } + public Recording getActiveRecordingForSession(String sessionId) { + Recording recording = this.sessionsRecordings.get(sessionId); + if (recording == null) { + recording = this.sessionsRecordingsStarting.get(sessionId); + } + return recording; + } + public boolean sessionIsBeingRecordedIndividual(String sessionId) { if (!sessionIsBeingRecorded(sessionId)) { return false; diff --git a/openvidu-server/src/test/java/io/openvidu/server/test/integration/WebhookIntegrationTest.java b/openvidu-server/src/test/java/io/openvidu/server/test/integration/WebhookIntegrationTest.java index 2bf029ca..cda9848a 100644 --- a/openvidu-server/src/test/java/io/openvidu/server/test/integration/WebhookIntegrationTest.java +++ b/openvidu-server/src/test/java/io/openvidu/server/test/integration/WebhookIntegrationTest.java @@ -167,8 +167,8 @@ public class WebhookIntegrationTest { // Client should have already received "connectionCreated" RPC response // nonetheless - verify(sessionEventsHandler, times(1)).onParticipantJoined(refEq(participant), anyString(), anyString(), anySet(), - anyInt(), refEq(null)); + verify(sessionEventsHandler, times(1)).onParticipantJoined(refEq(participant), refEq(null), anyString(), + anySet(), anyInt(), refEq(null)); // Now webhook response for event "participantJoined" should be received CustomWebhook.waitForEvent("participantJoined", 1000, TimeUnit.MILLISECONDS); diff --git a/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduEventManager.java b/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduEventManager.java index bcd1e24c..ea6a3246 100644 --- a/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduEventManager.java +++ b/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduEventManager.java @@ -255,7 +255,7 @@ public class OpenViduEventManager { return success; } - private AtomicInteger getNumEvents(String eventName) { + public AtomicInteger getNumEvents(String eventName) { return this.eventNumbers.computeIfAbsent(eventName, k -> new AtomicInteger(0)); } diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 2a86b63a..f7844d98 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -1185,7 +1185,43 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { user.getEventManager().waitUntilEventReaches("recordingStarted", 1); - Thread.sleep(5000); + Thread.sleep(3000); + + user.getDriver().findElement(By.id("close-dialog-btn")).click(); + Thread.sleep(500); + + // A new user with PUBLISHER role should trigger recordingStarted event + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.id("session-name-input-1")).clear(); + user.getDriver().findElement(By.id("session-name-input-1")).sendKeys(sessionName); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .publish-checkbox")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .subscribe-checkbox")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click(); + user.getEventManager().waitUntilEventReaches("connectionCreated", 4); + user.getEventManager().waitUntilEventReaches("streamCreated", 2); + user.getEventManager().waitUntilEventReaches("recordingStarted", 2); + + // A new user with SUBSCRIBER role should not trigger recordingStarted event + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.id("session-name-input-2")).clear(); + user.getDriver().findElement(By.id("session-name-input-2")).sendKeys(sessionName); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-2 .publish-checkbox")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-2 .subscribe-checkbox")).click(); + user.getDriver().findElement(By.id("session-settings-btn-2")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.id("radio-btn-sub")).click(); + user.getDriver().findElement(By.id("save-btn")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-2 .join-btn")).click(); + user.getEventManager().waitUntilEventReaches("connectionCreated", 9); + user.getEventManager().waitUntilEventReaches("streamCreated", 3); + + // No third recordingStarted event should be triggered for the SUBSCRIBER user + Thread.sleep(3000); + Assert.assertEquals(user.getEventManager().getNumEvents("recordingStarted").intValue(), 2); + + user.getDriver().findElement(By.id("session-api-btn-0")).click(); + Thread.sleep(500); user.getDriver().findElement(By.id("recording-id-field")).clear(); user.getDriver().findElement(By.id("recording-id-field")).sendKeys(sessionName); @@ -1256,7 +1292,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { user.getDriver().findElement(By.id("close-dialog-btn")).click(); Thread.sleep(500); - gracefullyLeaveParticipants(user, 1); + gracefullyLeaveParticipants(user, 3); } @Test