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 4c17cebe..092ceb43 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 @@ -244,6 +244,7 @@ public class Participant { } json.addProperty("role", this.token.getRole().name()); json.addProperty("serverData", this.serverMetadata); + json.addProperty("record", this.token.record()); json.addProperty("clientData", this.clientMetadata); return json; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Session.java b/openvidu-server/src/main/java/io/openvidu/server/core/Session.java index ddcd6759..4a3221a2 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 @@ -62,6 +62,7 @@ public class Session implements SessionInterface { protected volatile boolean closed = false; protected AtomicInteger activePublishers = new AtomicInteger(0); + protected AtomicInteger activeIndividualRecordedPublishers = new AtomicInteger(0); /** * This lock protects the following operations with read lock: [REST API](POST @@ -147,12 +148,22 @@ public class Session implements SessionInterface { return activePublishers.get(); } - public void registerPublisher() { - this.activePublishers.incrementAndGet(); + public int getActiveIndividualRecordedPublishers() { + return activeIndividualRecordedPublishers.get(); } - public void deregisterPublisher() { + public void registerPublisher(Participant participant) { + this.activePublishers.incrementAndGet(); + if (participant.getToken().record()) { + activeIndividualRecordedPublishers.incrementAndGet(); + } + } + + public void deregisterPublisher(Participant participant) { this.activePublishers.decrementAndGet(); + if (participant.getToken().record()) { + activeIndividualRecordedPublishers.decrementAndGet(); + } } public void storeToken(Token token) { 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 1b1bc4d6..6ce7c2c3 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 @@ -294,13 +294,13 @@ public abstract class SessionManager { return sessionNotActive; } - public Token newToken(Session session, OpenViduRole role, String serverMetadata, + public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record, KurentoTokenOptions kurentoTokenOptions) throws Exception { if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) { log.error("Data invalid format"); throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format"); } - Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), role, serverMetadata, + Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), role, serverMetadata, record, kurentoTokenOptions); session.storeToken(tokenObj); session.showTokens("Token created"); @@ -308,7 +308,7 @@ public abstract class SessionManager { } public Token newTokenForInsecureUser(Session session, String token, String serverMetadata) throws Exception { - Token tokenObj = new Token(token, OpenViduRole.PUBLISHER, serverMetadata != null ? serverMetadata : "", + Token tokenObj = new Token(token, OpenViduRole.PUBLISHER, serverMetadata != null ? serverMetadata : "", true, this.openviduConfig.isTurnadminAvailable() ? this.coturnCredentialsService.createUser() : null, null); session.storeToken(tokenObj); session.showTokens("Token created for insecure user"); diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Token.java b/openvidu-server/src/main/java/io/openvidu/server/core/Token.java index 55330548..bffd6a1e 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/Token.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/Token.java @@ -28,17 +28,19 @@ public class Token { private String token; private OpenViduRole role; private String serverMetadata = ""; + private boolean record; private TurnCredentials turnCredentials; private KurentoTokenOptions kurentoTokenOptions; private final String connectionId = IdentifierPrefixes.PARTICIPANT_PUBLIC_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase() + RandomStringUtils.randomAlphanumeric(9); - public Token(String token, OpenViduRole role, String serverMetadata, TurnCredentials turnCredentials, - KurentoTokenOptions kurentoTokenOptions) { + public Token(String token, OpenViduRole role, String serverMetadata, boolean record, + TurnCredentials turnCredentials, KurentoTokenOptions kurentoTokenOptions) { this.token = token; this.role = role; this.serverMetadata = serverMetadata; + this.record = record; this.turnCredentials = turnCredentials; this.kurentoTokenOptions = kurentoTokenOptions; } @@ -59,6 +61,10 @@ public class Token { return serverMetadata; } + public boolean record() { + return record; + } + public TurnCredentials getTurnCredentials() { return turnCredentials; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java b/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java index 6320f7bf..540f8e94 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/TokenGenerator.java @@ -39,7 +39,7 @@ public class TokenGenerator { @Autowired protected OpenviduBuildInfo openviduBuildConfig; - public Token generateToken(String sessionId, OpenViduRole role, String serverMetadata, + public Token generateToken(String sessionId, OpenViduRole role, String serverMetadata, boolean record, KurentoTokenOptions kurentoTokenOptions) throws Exception { String token = OpenViduServer.wsUrl; token += "?sessionId=" + sessionId; @@ -56,6 +56,6 @@ public class TokenGenerator { token += "&turnCredential=" + turnCredentials.getCredential(); } } - return new Token(token, role, serverMetadata, turnCredentials, kurentoTokenOptions); + return new Token(token, role, serverMetadata, record, turnCredentials, kurentoTokenOptions); } } 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 0e24020b..493543d2 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 @@ -181,7 +181,7 @@ public class KurentoParticipant extends Participant { log.info("PARTICIPANT {}: Is now publishing video in room {}", this.getParticipantPublicId(), this.session.getSessionId()); - if (this.openviduConfig.isRecordingModuleEnabled() + if (this.openviduConfig.isRecordingModuleEnabled() && this.token.record() && this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) { this.recordingManager.startOneIndividualStreamRecording(session, this); } @@ -447,7 +447,7 @@ public class KurentoParticipant extends Participant { // Remove streamId from publisher's map this.session.publishedStreamIds.remove(this.getPublisherStreamId()); - if (this.openviduConfig.isRecordingModuleEnabled() + if (this.openviduConfig.isRecordingModuleEnabled() && this.token.record() && this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) { this.recordingManager.stopOneIndividualStreamRecording(session, this.getPublisherStreamId(), kmsDisconnectionTime); @@ -461,7 +461,7 @@ public class KurentoParticipant extends Participant { } releaseElement(getParticipantPublicId(), publisher.getEndpoint()); this.streaming = false; - this.session.deregisterPublisher(); + this.session.deregisterPublisher(this); endpointConfig.getCdr().stopPublisher(this.getParticipantPublicId(), publisher.getStreamId(), reason); publisher = null; 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 e0ad8cb4..64bd7349 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 @@ -88,7 +88,7 @@ public class KurentoSession extends Session { } public void newPublisher(Participant participant) { - registerPublisher(); + registerPublisher(participant); log.debug("SESSION {}: Virtually subscribed other participants {} to new publisher {}", sessionId, participants.values(), participant.getParticipantPublicId()); } 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 21cfc13e..bd1e77a8 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 @@ -288,8 +288,7 @@ public class RecordingManager { this.sessionHandler.sendRecordingStartedNotification(session, recording); } if (session.getActivePublishers() == 0) { - // Init automatic recording stop if there are now publishers when starting - // recording + // Init automatic recording stop if no publishers when starting the recording log.info("No publisher in session {}. Starting {} seconds countdown for stopping recording", session.getSessionId(), this.openviduConfig.getOpenviduRecordingAutostopTimeout()); this.initAutomaticRecordingStopThread(session); diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java index 4cb7e1ab..362599fa 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java @@ -100,11 +100,11 @@ public class SingleStreamRecordingService extends RecordingService { activeRecorders.put(recording.getId(), new ConcurrentHashMap()); storedRecorders.put(recording.getId(), new ConcurrentHashMap()); - final int activePublishers = session.getActivePublishers(); - final CountDownLatch recordingStartedCountdown = new CountDownLatch(activePublishers); + int activePublishersToRecord = session.getActiveIndividualRecordedPublishers(); + final CountDownLatch recordingStartedCountdown = new CountDownLatch(activePublishersToRecord); for (Participant p : session.getParticipants()) { - if (p.isStreaming()) { + if (p.isStreaming() && p.getToken().record()) { MediaProfileSpecType profile = null; try { @@ -299,7 +299,7 @@ public class SingleStreamRecordingService extends RecordingService { if (storedRecorders.get(recordingId).containsKey(streamId)) { log.info("Stream {} recording of recording {} was already stopped", streamId, recordingId); } else { - log.error("Stream {} wasn't being recorded in recording {}", streamId, recordingId); + log.info("Stream {} wasn't being recorded in recording {}", streamId, recordingId); } } globalStopLatch.countDown(); 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 f2a1a723..7fc8c41a 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 @@ -335,10 +335,12 @@ public class SessionRestController { String sessionId; String roleString; String metadata; + Boolean record; try { sessionId = (String) params.get("session"); roleString = (String) params.get("role"); metadata = (String) params.get("data"); + record = (Boolean) params.get("record"); } catch (ClassCastException e) { return this.generateErrorResponse("Type error in some parameter", "/tokens", HttpStatus.BAD_REQUEST); } @@ -386,11 +388,12 @@ public class SessionRestController { } metadata = (metadata != null) ? metadata : ""; + record = (record != null) ? record : true; // While closing a session tokens can't be generated if (session.closingLock.readLock().tryLock()) { try { - Token token = sessionManager.newToken(session, role, metadata, kurentoTokenOptions); + Token token = sessionManager.newToken(session, role, metadata, record, kurentoTokenOptions); JsonObject responseJson = new JsonObject(); responseJson.addProperty("id", token.getToken()); @@ -398,6 +401,7 @@ public class SessionRestController { responseJson.addProperty("session", sessionId); responseJson.addProperty("role", role.toString()); responseJson.addProperty("data", metadata); + responseJson.addProperty("record", record); responseJson.addProperty("token", token.getToken()); if (kurentoOptions != null) { diff --git a/openvidu-server/src/test/java/io/openvidu/server/test/integration/SessionGarbageCollectorIntegrationTest.java b/openvidu-server/src/test/java/io/openvidu/server/test/integration/SessionGarbageCollectorIntegrationTest.java index 6b1527c8..5e36c975 100644 --- a/openvidu-server/src/test/java/io/openvidu/server/test/integration/SessionGarbageCollectorIntegrationTest.java +++ b/openvidu-server/src/test/java/io/openvidu/server/test/integration/SessionGarbageCollectorIntegrationTest.java @@ -115,7 +115,7 @@ public class SessionGarbageCollectorIntegrationTest { } private void joinParticipant(String sessionId, String token) { - Token t = new Token(token, OpenViduRole.PUBLISHER, "SERVER_METADATA", null, null); + Token t = new Token(token, OpenViduRole.PUBLISHER, "SERVER_METADATA", true, null, null); String uuid = UUID.randomUUID().toString(); String participantPrivateId = "PARTICIPANT_PRIVATE_ID_" + uuid; String finalUserId = "FINAL_USER_ID_" + uuid; 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 dc47cd32..f90eaa56 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 @@ -1583,6 +1583,46 @@ public class OpenViduTestAppE2eTest { gracefullyLeaveParticipants(2); } + @Test + @DisplayName("Individual dynamic record") + void individualDynamicRecordTest() throws Exception { + isRecordingTest = true; + + setupBrowser("chrome"); + + log.info("Individual dynamic record"); + + // Connect 3 users. Two of them not recorded + for (int i = 0; i < 3; i++) { + user.getDriver().findElement(By.id("add-user-btn")).click(); + if (i < 2) { + user.getDriver().findElement(By.id("session-settings-btn-" + i)).click(); + Thread.sleep(1000); + user.getDriver().findElement(By.id("record-checkbox")).click(); + user.getDriver().findElement(By.id("save-btn")).click(); + Thread.sleep(1000); + } + } + + String sessionName = "TestSession"; + + user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER)); + user.getEventManager().waitUntilEventReaches("streamPlaying", 6); + + CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET); + restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", + "{'session':'" + sessionName + "','outputMode':'INDIVIDUAL'}", HttpStatus.SC_OK); + user.getEventManager().waitUntilEventReaches("recordingStarted", 3); + Thread.sleep(2000); + restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/stop/" + sessionName, HttpStatus.SC_OK); + user.getEventManager().waitUntilEventReaches("recordingStopped", 3); + + String recPath = "/opt/openvidu/recordings/" + sessionName + "/"; + Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName); + this.checkIndividualRecording(recPath, recording, 1, "opus", "vp8", true); + + } + @Test @DisplayName("Record cross-browser audio-only and video-only") void audioOnlyVideoOnlyRecordTest() throws Exception { @@ -2814,7 +2854,7 @@ public class OpenViduTestAppE2eTest { // 200 body = "{'session': 'CUSTOM_SESSION_ID', 'role': 'MODERATOR', 'data': 'SERVER_DATA', 'kurentoOptions': {'allowedFilters': ['GStreamerFilter']}}"; res = restClient.rest(HttpMethod.POST, "/openvidu/api/tokens", body, HttpStatus.SC_OK, true, - "{'id':'STR','connectionId':'STR','session':'STR','role':'STR','data':'STR','token':'STR','kurentoOptions':{'allowedFilters':['STR']}}"); + "{'id':'STR','connectionId':'STR','session':'STR','role':'STR','data':'STR','record':true,'token':'STR','kurentoOptions':{'allowedFilters':['STR']}}"); final String token1 = res.get("token").getAsString(); Assert.assertEquals("JSON return value from /openvidu/api/tokens should have equal srtings in 'id' and 'token'", res.get("id").getAsString(), token1); @@ -2823,7 +2863,7 @@ public class OpenViduTestAppE2eTest { // Default values body = "{'session': 'CUSTOM_SESSION_ID'}"; res = restClient.rest(HttpMethod.POST, "/openvidu/api/tokens", body, HttpStatus.SC_OK, true, - "{'id':'STR','connectionId':'STR','session':'STR','role':'STR','data':'STR','token':'STR'}"); + "{'id':'STR','connectionId':'STR','session':'STR','role':'STR','data':'STR','record':true,'token':'STR'}"); final String token2 = res.get("id").getAsString(); /** POST /openvidu/api/signal (NOT ACTIVE SESSION) **/ diff --git a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css index 5c4c9594..f83aa643 100644 --- a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css +++ b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css @@ -36,4 +36,8 @@ mat-radio-button:first-child { #role-div { padding-top: 6px; padding-bottom: 15px; +} + +#record-div { + padding-bottom: 20px; } \ No newline at end of file diff --git a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html index 7ca258ce..b1b02bb5 100644 --- a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html +++ b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html @@ -107,6 +107,10 @@ +
+ Record +
+
@@ -120,6 +124,6 @@ + [mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf, tokenOptions: generateTokenOptions(), customToken: customToken, record: record}">SAVE
diff --git a/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts b/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts index eda242a8..5184a4db 100644 --- a/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts +++ b/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts @@ -129,6 +129,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { customToken: string; tokenOptions: TokenOptions = { role: OpenViduRole.PUBLISHER, + record: true, kurentoOptions: { videoMaxRecvBandwidth: 1000, videoMinRecvBandwidth: 300, @@ -684,7 +685,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { return this.OV_NodeClient.createSession(this.sessionProperties) .then(session_NodeClient => { this.sessionAPI = session_NodeClient; - return session_NodeClient.generateToken({ role: this.tokenOptions.role, kurentoOptions: this.tokenOptions.kurentoOptions }); + return session_NodeClient.generateToken(this.tokenOptions); }); }