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

View File

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

View File

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

View File

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

@ -70,16 +70,12 @@ public class Recording {
return this.recordingProperties.name(); return this.recordingProperties.name();
} }
public String setName() {
return this.recordingProperties.name();
}
public RecordingLayout getLayout() { public RecordingLayout getLayout() {
return this.recordingProperties.recordingLayout(); return this.recordingProperties.recordingLayout();
} }
public RecordingLayout setLayout() { public String getCustomLayout() {
return this.recordingProperties.recordingLayout(); return this.recordingProperties.customLayout();
} }
public String getSessionId() { public String getSessionId() {

View File

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

View File

@ -39,6 +39,11 @@
"type": "java.lang.String", "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'" "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", "name": "openvidu.recording.version",
"type": "java.lang.String", "type": "java.lang.String",

View File

@ -18,3 +18,4 @@ openvidu.recording: false
openvidu.recording.path: /opt/openvidu/recordings openvidu.recording.path: /opt/openvidu/recordings
openvidu.recording.public-access: false openvidu.recording.public-access: false
openvidu.recording.notification: publisher_moderator 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']; recordingModes = ['ALWAYS', 'MANUAL'];
selectedRecordingMode = 'MANUAL'; selectedRecordingMode = 'MANUAL';
defaultRecordingLayouts = ['BEST_FIT']; defaultRecordingLayouts = ['BEST_FIT', 'CUSTOM'];
selectedDefaultRecordingLayout = 'BEST_FIT'; selectedDefaultRecordingLayout = 'BEST_FIT';
mediaModes = ['ROUTED']; mediaModes = ['ROUTED'];