mirror of https://github.com/OpenVidu/openvidu.git
openvidu-server: record Token option
parent
7e75a6568a
commit
baa7e37c2c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) **/
|
||||
|
|
|
@ -36,4 +36,8 @@ mat-radio-button:first-child {
|
|||
#role-div {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
#record-div {
|
||||
padding-bottom: 20px;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue