openvidu-server: INDIVIDUAL recording stream selection on token

pull/533/head
pabloFuente 2020-09-07 13:43:57 +02:00
parent 8f1512f965
commit 7a9c6271dd
13 changed files with 85 additions and 87 deletions

View File

@ -50,7 +50,6 @@ import io.openvidu.server.config.OpenviduConfig.Error;
import io.openvidu.server.core.SessionEventsHandler; import io.openvidu.server.core.SessionEventsHandler;
import io.openvidu.server.core.SessionManager; import io.openvidu.server.core.SessionManager;
import io.openvidu.server.core.TokenGenerator; import io.openvidu.server.core.TokenGenerator;
import io.openvidu.server.core.TokenGeneratorDefault;
import io.openvidu.server.coturn.CoturnCredentialsService; import io.openvidu.server.coturn.CoturnCredentialsService;
import io.openvidu.server.coturn.CoturnCredentialsServiceFactory; import io.openvidu.server.coturn.CoturnCredentialsServiceFactory;
import io.openvidu.server.kurento.core.KurentoParticipantEndpointConfig; import io.openvidu.server.kurento.core.KurentoParticipantEndpointConfig;
@ -156,7 +155,7 @@ public class OpenViduServer implements JsonRpcConfigurer {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@DependsOn("openviduConfig") @DependsOn("openviduConfig")
public TokenGenerator tokenGenerator() { public TokenGenerator tokenGenerator() {
return new TokenGeneratorDefault(); return new TokenGenerator();
} }
@Bean @Bean

View File

@ -60,6 +60,7 @@ public class Session implements SessionInterface {
protected volatile boolean closed = false; protected volatile boolean closed = false;
protected AtomicInteger activePublishers = new AtomicInteger(0); protected AtomicInteger activePublishers = new AtomicInteger(0);
protected AtomicInteger activeIndividualRecordedPublishers = new AtomicInteger(0);
/** /**
* This lock protects the following operations with read lock: [REST API](POST * This lock protects the following operations with read lock: [REST API](POST
@ -145,12 +146,22 @@ public class Session implements SessionInterface {
return activePublishers.get(); return activePublishers.get();
} }
public void registerPublisher() { public int getActiveIndividualRecordedPublishers() {
this.activePublishers.incrementAndGet(); 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(); this.activePublishers.decrementAndGet();
if (participant.getToken().record()) {
activeIndividualRecordedPublishers.decrementAndGet();
}
} }
public void storeToken(Token token) { public void storeToken(Token token) {

View File

@ -295,13 +295,13 @@ public abstract class SessionManager {
return sessionNotActive; return sessionNotActive;
} }
public String newToken(Session session, OpenViduRole role, String serverMetadata, public String newToken(Session session, OpenViduRole role, String serverMetadata, boolean record,
KurentoTokenOptions kurentoTokenOptions) throws Exception { KurentoTokenOptions kurentoTokenOptions) throws Exception {
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) { if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
log.error("Data invalid format"); log.error("Data invalid format");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format"); throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
} }
Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), role, serverMetadata, Token tokenObj = tokenGenerator.generateToken(session.getSessionId(), role, serverMetadata, record,
kurentoTokenOptions); kurentoTokenOptions);
session.storeToken(tokenObj); session.storeToken(tokenObj);
session.showTokens("Token created"); session.showTokens("Token created");
@ -310,7 +310,8 @@ public abstract class SessionManager {
public Token newTokenForInsecureUser(Session session, String token, String serverMetadata) throws Exception { 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 : "",
this.openviduConfig.isTurnadminAvailable() ? this.coturnCredentialsService.createUser() : null, null); this.openviduConfig.isTurnadminAvailable() ? this.coturnCredentialsService.createUser() : null, true,
null);
session.storeToken(tokenObj); session.storeToken(tokenObj);
session.showTokens("Token created for insecure user"); session.showTokens("Token created for insecure user");
return tokenObj; return tokenObj;

View File

@ -27,6 +27,7 @@ public class Token {
private OpenViduRole role; private OpenViduRole role;
private String serverMetadata = ""; private String serverMetadata = "";
private TurnCredentials turnCredentials; private TurnCredentials turnCredentials;
private boolean record = true;
private KurentoTokenOptions kurentoTokenOptions; private KurentoTokenOptions kurentoTokenOptions;
@ -35,11 +36,12 @@ public class Token {
} }
public Token(String token, OpenViduRole role, String serverMetadata, TurnCredentials turnCredentials, public Token(String token, OpenViduRole role, String serverMetadata, TurnCredentials turnCredentials,
KurentoTokenOptions kurentoTokenOptions) { boolean record, KurentoTokenOptions kurentoTokenOptions) {
this.token = token; this.token = token;
this.role = role; this.role = role;
this.serverMetadata = serverMetadata; this.serverMetadata = serverMetadata;
this.turnCredentials = turnCredentials; this.turnCredentials = turnCredentials;
this.record = record;
this.kurentoTokenOptions = kurentoTokenOptions; this.kurentoTokenOptions = kurentoTokenOptions;
} }
@ -50,7 +52,7 @@ public class Token {
public void setToken(String token) { public void setToken(String token) {
this.token = token; this.token = token;
} }
public OpenViduRole getRole() { public OpenViduRole getRole() {
return role; return role;
} }
@ -63,6 +65,10 @@ public class Token {
return turnCredentials; return turnCredentials;
} }
public boolean record() {
return record;
}
public KurentoTokenOptions getKurentoTokenOptions() { public KurentoTokenOptions getKurentoTokenOptions() {
return kurentoTokenOptions; return kurentoTokenOptions;
} }

View File

@ -17,12 +17,45 @@
package io.openvidu.server.core; package io.openvidu.server.core;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import io.openvidu.java.client.OpenViduRole; import io.openvidu.java.client.OpenViduRole;
import io.openvidu.server.OpenViduServer;
import io.openvidu.server.config.OpenviduBuildInfo;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.coturn.CoturnCredentialsService;
import io.openvidu.server.coturn.TurnCredentials;
import io.openvidu.server.kurento.core.KurentoTokenOptions; import io.openvidu.server.kurento.core.KurentoTokenOptions;
public interface TokenGenerator { public class TokenGenerator {
public Token generateToken(String sessionId, OpenViduRole role, String serverMetadata, @Autowired
KurentoTokenOptions kurentoTokenOptions) throws Exception; private CoturnCredentialsService coturnCredentialsService;
@Autowired
protected OpenviduConfig openviduConfig;
@Autowired
protected OpenviduBuildInfo openviduBuildConfig;
public Token generateToken(String sessionId, OpenViduRole role, String serverMetadata, boolean record,
KurentoTokenOptions kurentoTokenOptions) throws Exception {
String token = OpenViduServer.wsUrl;
token += "?sessionId=" + sessionId;
token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
+ RandomStringUtils.randomAlphanumeric(15);
token += "&role=" + role.name();
token += "&version=" + openviduBuildConfig.getOpenViduServerVersion();
TurnCredentials turnCredentials = null;
if (this.openviduConfig.isTurnadminAvailable()) {
turnCredentials = coturnCredentialsService.createUser();
if (turnCredentials != null) {
token += "&coturnIp=" + openviduConfig.getCoturnIp();
token += "&turnUsername=" + turnCredentials.getUsername();
token += "&turnCredential=" + turnCredentials.getCredential();
}
}
return new Token(token, role, serverMetadata, turnCredentials, record, kurentoTokenOptions);
}
} }

View File

@ -1,62 +0,0 @@
/*
* (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.openvidu.server.core;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import io.openvidu.java.client.OpenViduRole;
import io.openvidu.server.OpenViduServer;
import io.openvidu.server.config.OpenviduBuildInfo;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.coturn.CoturnCredentialsService;
import io.openvidu.server.coturn.TurnCredentials;
import io.openvidu.server.kurento.core.KurentoTokenOptions;
public class TokenGeneratorDefault implements TokenGenerator {
@Autowired
private CoturnCredentialsService coturnCredentialsService;
@Autowired
protected OpenviduConfig openviduConfig;
@Autowired
protected OpenviduBuildInfo openviduBuildConfig;
@Override
public Token generateToken(String sessionId, OpenViduRole role, String serverMetadata,
KurentoTokenOptions kurentoTokenOptions) throws Exception {
String token = OpenViduServer.wsUrl;
token += "?sessionId=" + sessionId;
token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
+ RandomStringUtils.randomAlphanumeric(15);
token += "&role=" + role.name();
token += "&version=" + openviduBuildConfig.getOpenViduServerVersion();
TurnCredentials turnCredentials = null;
if (this.openviduConfig.isTurnadminAvailable()) {
turnCredentials = coturnCredentialsService.createUser();
if (turnCredentials != null) {
token += "&coturnIp=" + openviduConfig.getCoturnIp();
token += "&turnUsername=" + turnCredentials.getUsername();
token += "&turnCredential=" + turnCredentials.getCredential();
}
}
return new Token(token, role, serverMetadata, turnCredentials, kurentoTokenOptions);
}
}

View File

@ -182,7 +182,7 @@ public class KurentoParticipant extends Participant {
this.session.getSessionId()); this.session.getSessionId());
if (this.openviduConfig.isRecordingModuleEnabled() if (this.openviduConfig.isRecordingModuleEnabled()
&& this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) { && this.recordingManager.sessionIsBeingRecorded(session.getSessionId()) && this.token.record()) {
this.recordingManager.startOneIndividualStreamRecording(session, null, null, this); this.recordingManager.startOneIndividualStreamRecording(session, null, null, this);
} }
@ -461,7 +461,7 @@ public class KurentoParticipant extends Participant {
} }
releaseElement(getParticipantPublicId(), publisher.getEndpoint()); releaseElement(getParticipantPublicId(), publisher.getEndpoint());
this.streaming = false; this.streaming = false;
this.session.deregisterPublisher(); this.session.deregisterPublisher(this);
endpointConfig.getCdr().stopPublisher(this.getParticipantPublicId(), publisher.getStreamId(), reason); endpointConfig.getCdr().stopPublisher(this.getParticipantPublicId(), publisher.getStreamId(), reason);
publisher = null; publisher = null;

View File

@ -88,7 +88,7 @@ public class KurentoSession extends Session {
} }
public void newPublisher(Participant participant) { public void newPublisher(Participant participant) {
registerPublisher(); registerPublisher(participant);
log.debug("SESSION {}: Virtually subscribed other participants {} to new publisher {}", sessionId, log.debug("SESSION {}: Virtually subscribed other participants {} to new publisher {}", sessionId,
participants.values(), participant.getParticipantPublicId()); participants.values(), participant.getParticipantPublicId());
} }

View File

@ -279,8 +279,7 @@ public class RecordingManager {
this.sessionHandler.sendRecordingStartedNotification(session, recording); this.sessionHandler.sendRecordingStartedNotification(session, recording);
} }
if (session.getActivePublishers() == 0) { if (session.getActivePublishers() == 0) {
// Init automatic recording stop if there are now publishers when starting // Init automatic recording stop if no publishers when starting the recording
// recording
log.info("No publisher in session {}. Starting {} seconds countdown for stopping recording", log.info("No publisher in session {}. Starting {} seconds countdown for stopping recording",
session.getSessionId(), this.openviduConfig.getOpenviduRecordingAutostopTimeout()); session.getSessionId(), this.openviduConfig.getOpenviduRecordingAutostopTimeout());
this.initAutomaticRecordingStopThread(session); this.initAutomaticRecordingStopThread(session);

View File

@ -98,11 +98,11 @@ public class SingleStreamRecordingService extends RecordingService {
activeRecorders.put(session.getSessionId(), new ConcurrentHashMap<String, RecorderEndpointWrapper>()); activeRecorders.put(session.getSessionId(), new ConcurrentHashMap<String, RecorderEndpointWrapper>());
storedRecorders.put(session.getSessionId(), new ConcurrentHashMap<String, RecorderEndpointWrapper>()); storedRecorders.put(session.getSessionId(), new ConcurrentHashMap<String, RecorderEndpointWrapper>());
final int activePublishers = session.getActivePublishers(); int activePublishersToRecord = session.getActiveIndividualRecordedPublishers();
final CountDownLatch recordingStartedCountdown = new CountDownLatch(activePublishers); final CountDownLatch recordingStartedCountdown = new CountDownLatch(activePublishersToRecord);
for (Participant p : session.getParticipants()) { for (Participant p : session.getParticipants()) {
if (p.isStreaming()) { if (p.isStreaming() && p.getToken().record()) {
MediaProfileSpecType profile = null; MediaProfileSpecType profile = null;
try { try {

View File

@ -330,10 +330,12 @@ public class SessionRestController {
String sessionId; String sessionId;
String roleString; String roleString;
String metadata; String metadata;
Boolean record;
try { try {
sessionId = (String) params.get("session"); sessionId = (String) params.get("session");
roleString = (String) params.get("role"); roleString = (String) params.get("role");
metadata = (String) params.get("data"); metadata = (String) params.get("data");
record = (Boolean) params.get("record");
} catch (ClassCastException e) { } catch (ClassCastException e) {
return this.generateErrorResponse("Type error in some parameter", "/api/tokens", HttpStatus.BAD_REQUEST); return this.generateErrorResponse("Type error in some parameter", "/api/tokens", HttpStatus.BAD_REQUEST);
} }
@ -383,17 +385,19 @@ public class SessionRestController {
} }
metadata = (metadata != null) ? metadata : ""; metadata = (metadata != null) ? metadata : "";
record = (record != null) ? record : true;
// While closing a session tokens can't be generated // While closing a session tokens can't be generated
if (session.closingLock.readLock().tryLock()) { if (session.closingLock.readLock().tryLock()) {
try { try {
String token = sessionManager.newToken(session, role, metadata, kurentoTokenOptions); String token = sessionManager.newToken(session, role, metadata, record, kurentoTokenOptions);
JsonObject responseJson = new JsonObject(); JsonObject responseJson = new JsonObject();
responseJson.addProperty("id", token); responseJson.addProperty("id", token);
responseJson.addProperty("session", sessionId); responseJson.addProperty("session", sessionId);
responseJson.addProperty("role", role.toString()); responseJson.addProperty("role", role.toString());
responseJson.addProperty("data", metadata); responseJson.addProperty("data", metadata);
responseJson.addProperty("record", record);
responseJson.addProperty("token", token); responseJson.addProperty("token", token);
if (kurentoOptions != null) { if (kurentoOptions != null) {

View File

@ -115,7 +115,7 @@ public class SessionGarbageCollectorIntegrationTest {
} }
private void joinParticipant(String sessionId, String token) { 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", null, true, null);
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
String participantPrivateId = "PARTICIPANT_PRIVATE_ID_" + uuid; String participantPrivateId = "PARTICIPANT_PRIVATE_ID_" + uuid;
String finalUserId = "FINAL_USER_ID_" + uuid; String finalUserId = "FINAL_USER_ID_" + uuid;

View File

@ -1349,8 +1349,15 @@ public class OpenViduTestAppE2eTest {
final String sessionName = "TestSession"; final String sessionName = "TestSession";
final String recordingName = "CUSTOM_NAME"; final String recordingName = "CUSTOM_NAME";
user.getDriver().findElement(By.id("auto-join-checkbox")).click(); // Connect 2 users. One of them not recorded
user.getDriver().findElement(By.id("one2one-btn")).click(); user.getDriver().findElement(By.id("one2one-btn")).click();
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("record-checkbox")).click();
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
user.getEventManager().waitUntilEventReaches("connectionCreated", 4); user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
user.getEventManager().waitUntilEventReaches("accessAllowed", 2); user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
@ -1418,7 +1425,7 @@ public class OpenViduTestAppE2eTest {
String recPath = recordingsPath + sessionName + "/"; String recPath = recordingsPath + sessionName + "/";
Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName); Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName);
this.checkIndividualRecording(recPath, recording, 2, "opus", "vp8", true); this.checkIndividualRecording(recPath, recording, 1, "opus", "vp8", true);
// Try to get the stopped recording // Try to get the stopped recording
user.getDriver().findElement(By.id("get-recording-btn")).click(); user.getDriver().findElement(By.id("get-recording-btn")).click();