openvidu-server: custom recording layout

pull/73/head
pabloFuente 2018-04-20 15:53:20 +02:00
parent 13b4bf0f3a
commit 43894c215b
10 changed files with 133 additions and 59 deletions

View File

@ -32,6 +32,9 @@ public class OpenviduConfig {
@Value("${openvidu.recording.notification}")
String openviduRecordingNotification;
@Value("${openvidu.recording.custom-layout}")
String openviduRecordingCustomLayout;
@Value("${openvidu.recording.version}")
String openviduRecordingVersion;
@ -68,12 +71,20 @@ public class OpenviduConfig {
return this.openviduRecordingPath;
}
public void setOpenViduRecordingPath(String recordingPath) {
this.openviduRecordingPath = recordingPath;
}
public boolean getOpenViduRecordingPublicAccess() {
return this.openviduRecordingPublicAccess;
}
public void setOpenViduRecordingPath(String recordingPath) {
this.openviduRecordingPath = recordingPath;
public String getOpenviduRecordingCustomLayout() {
return this.openviduRecordingCustomLayout;
}
public void setOpenViduRecordingCustomLayout(String recordingCustomLayout) {
this.openviduRecordingCustomLayout = recordingCustomLayout;
}
public String getFinalUrl() {

View File

@ -17,32 +17,34 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// Security for API REST
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry conf = http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/sessions").authenticated()
.antMatchers(HttpMethod.POST, "/api/tokens").authenticated()
.antMatchers(HttpMethod.POST, "/api/recordings/start").authenticated()
.antMatchers(HttpMethod.POST, "/api/recordings/stop").authenticated()
.antMatchers(HttpMethod.GET, "/api/recordings").authenticated()
.antMatchers(HttpMethod.GET, "/api/recordings/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/recordings/**").authenticated()
.antMatchers(HttpMethod.GET, "/config/**").authenticated()
.antMatchers("/").authenticated();
.authorizeRequests().antMatchers(HttpMethod.POST, "/api/sessions").authenticated()
.antMatchers(HttpMethod.POST, "/api/tokens").authenticated()
.antMatchers(HttpMethod.POST, "/api/recordings/start").authenticated()
.antMatchers(HttpMethod.POST, "/api/recordings/stop").authenticated()
.antMatchers(HttpMethod.GET, "/api/recordings").authenticated()
.antMatchers(HttpMethod.GET, "/api/recordings/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/recordings/**").authenticated()
.antMatchers(HttpMethod.GET, "/config/**").authenticated().antMatchers("/").authenticated();
if (openviduConf.getOpenViduRecordingPublicAccess()) {
conf = conf.antMatchers("/recordings/*").permitAll();
} else {
conf = conf.antMatchers("/recordings/*").authenticated();
}
// Security for layouts
conf.antMatchers("/layouts/*").authenticated();
conf.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic();
// Security for recorded videos
if (openviduConf.getOpenViduRecordingPublicAccess()) {
conf = conf.antMatchers("/recordings/*").permitAll();
} else {
conf = conf.antMatchers("/recordings/*").authenticated();
}
conf.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("OPENVIDUAPP").password(openviduConf.getOpenViduSecret()).roles("ADMIN");
}
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("OPENVIDUAPP").password(openviduConf.getOpenViduSecret()).roles("ADMIN");
}
}

View File

@ -237,8 +237,10 @@ public class KurentoSessionManager extends SessionManager {
&& session.getActivePublishers() == 0) {
// Insecure session recording
new Thread(() -> {
recordingService.startRecording(session, new RecordingProperties.Builder().name("")
.recordingLayout(session.getSessionProperties().defaultRecordingLayout()).build());
recordingService.startRecording(session,
new RecordingProperties.Builder().name("")
.recordingLayout(session.getSessionProperties().defaultRecordingLayout())
.customLayout(session.getSessionProperties().defaultCustomLayout()).build());
}).start();
}

View File

@ -47,6 +47,7 @@ import com.github.dockerjava.core.command.PullImageResultCallback;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.server.CommandExecutor;
import io.openvidu.server.OpenViduServer;
@ -87,12 +88,11 @@ public class ComposedRecordingService {
String shortSessionId = session.getSessionId().substring(session.getSessionId().lastIndexOf('/') + 1,
session.getSessionId().length());
String recordingId = this.getFreeRecordingId(session.getSessionId(), shortSessionId);
String secret = openviduConfig.getOpenViduSecret();
if (properties.name() == null || properties.name().isEmpty()) {
// No name provided for the recording file
properties = new RecordingProperties.Builder().name(recordingId)
.recordingLayout(properties.recordingLayout()).build();
.recordingLayout(properties.recordingLayout()).customLayout(properties.customLayout()).build();
}
Recording recording = new Recording(session.getSessionId(), recordingId, properties);
@ -111,11 +111,9 @@ public class ComposedRecordingService {
e.printStackTrace();
}
String location = OpenViduServer.publicUrl.replaceFirst("wss://", "");
String layoutUrl = properties.recordingLayout().name().toLowerCase().replaceAll("_", "-");
String layoutUrl = this.getLayoutUrl(recording, shortSessionId);
envs.add("URL=https://OPENVIDUAPP:" + secret + "@" + location + "/#/layout-" + layoutUrl + "/" + shortSessionId
+ "/" + secret);
envs.add("URL=" + layoutUrl);
envs.add("RESOLUTION=1920x1080");
envs.add("FRAMERATE=30");
envs.add("VIDEO_ID=" + recordingId);
@ -125,8 +123,7 @@ public class ComposedRecordingService {
envs.add("RECORDING_JSON=" + recording.toJson().toJSONString());
log.info(recording.toJson().toJSONString());
log.debug("Recorder connecting to url {}",
"https://OPENVIDUAPP:" + secret + "@localhost:8443/#/layout-best-fit/" + shortSessionId + "/" + secret);
log.debug("Recorder connecting to url {}", layoutUrl);
String containerId = this.runRecordingContainer(envs, "recording_" + recordingId);
@ -456,6 +453,27 @@ public class ComposedRecordingService {
throw e;
}
private String getLayoutUrl(Recording recording, String shortSessionId) {
String secret = openviduConfig.getOpenViduSecret();
String location = OpenViduServer.publicUrl.replaceFirst("wss://", "");
String layout, finalUrl;
if (RecordingLayout.CUSTOM.equals(recording.getLayout())) {
layout = recording.getCustomLayout();
layout = layout.startsWith("/") ? layout.substring(1) : layout;
layout = layout.endsWith("/") ? layout.substring(0, layout.length() - 1) : layout;
layout += "/index.html";
finalUrl = "https://OPENVIDUAPP:" + secret + "@" + location + "/layouts/custom/" + layout + "/?sessionId="
+ shortSessionId + "&secret=" + secret;
} else {
layout = recording.getLayout().name().toLowerCase().replaceAll("_", "-");
finalUrl = "https://OPENVIDUAPP:" + secret + "@" + location + "/#/layout-" + layout + "/" + shortSessionId
+ "/" + secret;
}
return finalUrl;
}
public void setRecordingVersion(String version) {
this.IMAGE_TAG = version;
}

View File

@ -0,0 +1,27 @@
package io.openvidu.server.recording;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import io.openvidu.server.config.OpenviduConfig;
@Configuration
public class LayoutsHttpHandler extends WebMvcConfigurerAdapter {
@Autowired
OpenviduConfig openviduConfig;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String customLayoutsPath = openviduConfig.getOpenviduRecordingCustomLayout();
customLayoutsPath = customLayoutsPath.endsWith("/") ? customLayoutsPath : customLayoutsPath + "/";
openviduConfig.setOpenViduRecordingCustomLayout(customLayoutsPath);
registry.addResourceHandler("/layouts/custom/**").addResourceLocations("file:" + customLayoutsPath);
}
}

View File

@ -66,21 +66,17 @@ public class Recording {
this.id = id;
}
public String getName() {
return this.recordingProperties.name();
}
public String getName() {
return this.recordingProperties.name();
}
public String setName() {
return this.recordingProperties.name();
}
public RecordingLayout getLayout() {
return this.recordingProperties.recordingLayout();
}
public RecordingLayout getLayout() {
return this.recordingProperties.recordingLayout();
}
public RecordingLayout setLayout() {
return this.recordingProperties.recordingLayout();
}
public String getCustomLayout() {
return this.recordingProperties.customLayout();
}
public String getSessionId() {
return sessionId;

View File

@ -72,23 +72,34 @@ public class SessionRestController {
SessionProperties.Builder builder = new SessionProperties.Builder();
if (params != null) {
String mediaModeString = (String) params.get("mediaMode");
String recordingModeString = (String) params.get("recordingMode");
String defaultRecordingLayoutString = (String) params.get("defaultRecordingLayout");
String mediaModeString = (String) params.get("mediaMode");
String defaultCustomLayout = (String) params.get("defaultCustomLayout");
try {
// Safe parameter retrieval. Default values if not defined
if (recordingModeString != null) {
RecordingMode recordingMode = RecordingMode.valueOf(recordingModeString);
builder = builder.recordingMode(recordingMode);
} else {
builder = builder.recordingMode(RecordingMode.MANUAL);
}
if (defaultRecordingLayoutString != null) {
RecordingLayout defaultRecordingLayout = RecordingLayout.valueOf(defaultRecordingLayoutString);
builder = builder.defaultRecordingLayout(defaultRecordingLayout);
} else {
builder.defaultRecordingLayout(RecordingLayout.BEST_FIT);
}
if (mediaModeString != null) {
MediaMode mediaMode = MediaMode.valueOf(mediaModeString);
builder = builder.mediaMode(mediaMode);
} else {
builder = builder.mediaMode(MediaMode.ROUTED);
}
builder = builder.defaultCustomLayout((defaultCustomLayout != null) ? defaultCustomLayout : "");
} catch (IllegalArgumentException e) {
return this.generateErrorResponse("RecordingMode " + params.get("recordingMode") + " | "
+ "Default RecordingLayout " + params.get("defaultRecordingLayout") + " | " + "MediaMode "
@ -120,9 +131,7 @@ public class SessionRestController {
role = ParticipantRole.PUBLISHER;
}
if (metadata == null) {
metadata = "";
}
metadata = (metadata != null) ? metadata : "";
String token = sessionManager.newToken(sessionId, role, metadata);
JSONObject responseJson = new JSONObject();
@ -149,6 +158,7 @@ public class SessionRestController {
String sessionId = (String) params.get("session");
String name = (String) params.get("name");
String recordingLayoutString = (String) params.get("recordingLayout");
String customLayout = (String) params.get("customLayout");
if (sessionId == null) {
// "session" parameter not found
@ -181,8 +191,10 @@ public class SessionRestController {
recordingLayout = RecordingLayout.valueOf(recordingLayoutString);
}
customLayout = (customLayout == null) ? session.getSessionProperties().defaultCustomLayout() : customLayout;
Recording startedRecording = this.recordingService.startRecording(session,
new RecordingProperties.Builder().name(name).recordingLayout(recordingLayout).build());
new RecordingProperties.Builder().name(name).recordingLayout(recordingLayout).customLayout(customLayout).build());
return new ResponseEntity<>(startedRecording.toJson(), HttpStatus.OK);
}

View File

@ -39,6 +39,11 @@
"type": "java.lang.String",
"description": "Which users will receive a notfication (client events 'recordingStarted' and 'recordingStopped') when recording starts and stops: 'none', 'publisher_moderator', 'all'"
},
{
"name": "openvidu.recording.custom-layout",
"type": "java.lang.String",
"description": "Where should OpenVidu Server look for custom recording layouts"
},
{
"name": "openvidu.recording.version",
"type": "java.lang.String",

View File

@ -18,3 +18,4 @@ openvidu.recording: false
openvidu.recording.path: /opt/openvidu/recordings
openvidu.recording.public-access: false
openvidu.recording.notification: publisher_moderator
openvidu.recording.custom-layout: /opt/openvidu/custom-layout

View File

@ -29,7 +29,7 @@ export class TestApirestComponent implements OnInit, OnDestroy {
recordingModes = ['ALWAYS', 'MANUAL'];
selectedRecordingMode = 'MANUAL';
defaultRecordingLayouts = ['BEST_FIT'];
defaultRecordingLayouts = ['BEST_FIT', 'CUSTOM'];
selectedDefaultRecordingLayout = 'BEST_FIT';
mediaModes = ['ROUTED'];