openvidu-server: record Token option

pull/550/head
pabloFuente 2020-10-05 11:16:36 +02:00
parent 7e75a6568a
commit baa7e37c2c
15 changed files with 96 additions and 26 deletions

View File

@ -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;
}

View File

@ -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) {

View File

@ -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");

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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());
}

View File

@ -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);

View File

@ -100,11 +100,11 @@ public class SingleStreamRecordingService extends RecordingService {
activeRecorders.put(recording.getId(), new ConcurrentHashMap<String, RecorderEndpointWrapper>());
storedRecorders.put(recording.getId(), new ConcurrentHashMap<String, RecorderEndpointWrapper>());
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();

View File

@ -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) {

View File

@ -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;

View File

@ -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) **/

View File

@ -36,4 +36,8 @@ mat-radio-button:first-child {
#role-div {
padding-top: 6px;
padding-bottom: 15px;
}
#record-div {
padding-bottom: 20px;
}

View File

@ -107,6 +107,10 @@
</button>
</div>
<div id="record-div">
<mat-checkbox class="checkbox-form" [(ngModel)]="tokenOptions.record" id="record-checkbox">Record</mat-checkbox>
</div>
<label class="label">Token</label>
<div id="custom-token-div">
<mat-form-field>
@ -120,6 +124,6 @@
<mat-dialog-actions>
<button id="cancel-btn" mat-button [mat-dialog-close]="undefined">CANCEL</button>
<button id="save-btn" mat-button
[mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf, tokenOptions: generateTokenOptions(), customToken: customToken}">SAVE</button>
[mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf, tokenOptions: generateTokenOptions(), customToken: customToken, record: record}">SAVE</button>
</mat-dialog-actions>
</div>

View File

@ -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);
});
}