Move RecordingProperties#fromJson from openvidu-server to openvidu-java-client

pull/739/head
pabloFuente 2022-06-13 18:20:37 +02:00
parent 7e8b6adaad
commit 735d4a96bd
17 changed files with 403 additions and 445 deletions

View File

@ -17,6 +17,9 @@
package io.openvidu.java.client;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ -120,7 +123,9 @@ public class Recording {
}
this.status = Recording.Status.valueOf(json.get("status").getAsString());
this.recordingProperties = RecordingProperties.fromJson(json);
RecordingProperties.Builder builder = RecordingProperties
.fromJson(new Gson().fromJson(json.toString(), Map.class), null);
this.recordingProperties = builder.build();
}
/**
@ -233,9 +238,9 @@ public class Recording {
* URL of the recording. You can access the file from there. It is
* <code>null</code> until recording reaches "ready" or "failed" status. If
* <a href="https://docs.openvidu.io/en/stable/reference-docs/openvidu-config/">
* OpenVidu Server configuration
* </a> property <code>OPENVIDU_RECORDING_PUBLIC_ACCESS</code> is false,
* this path will be secured with OpenVidu credentials
* OpenVidu Server configuration </a> property
* <code>OPENVIDU_RECORDING_PUBLIC_ACCESS</code> is false, this path will be
* secured with OpenVidu credentials
*/
public String getUrl() {
return url;

View File

@ -17,9 +17,12 @@
package io.openvidu.java.client;
import java.util.Map;
import com.google.gson.JsonObject;
import io.openvidu.java.client.Recording.OutputMode;
import io.openvidu.java.client.utils.FormatChecker;
/**
* See
@ -28,6 +31,7 @@ import io.openvidu.java.client.Recording.OutputMode;
public class RecordingProperties {
public static class DefaultValues {
public static final String name = "";
public static final Boolean hasAudio = true;
public static final Boolean hasVideo = true;
public static final Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
@ -35,14 +39,15 @@ public class RecordingProperties {
public static final String resolution = "1280x720";
public static final Integer frameRate = 25;
public static final Long shmSize = 536870912L;
public static final String customLayout = "";
public static final Boolean ignoreFailedStreams = false;
}
// For all
private String name = "";
private Boolean hasAudio = true;
private Boolean hasVideo = true;
private Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
private String name = DefaultValues.name;
private Boolean hasAudio = DefaultValues.hasAudio;
private Boolean hasVideo = DefaultValues.hasVideo;
private Recording.OutputMode outputMode = DefaultValues.outputMode;
// For COMPOSED/COMPOSED_QUICK_START + hasVideo
private RecordingLayout recordingLayout;
private String resolution;
@ -60,15 +65,15 @@ public class RecordingProperties {
*/
public static class Builder {
private String name = "";
private String name = DefaultValues.name;
private Boolean hasAudio = DefaultValues.hasAudio;
private Boolean hasVideo = DefaultValues.hasVideo;
private Recording.OutputMode outputMode = DefaultValues.outputMode;
private RecordingLayout recordingLayout;
private String resolution;
private Integer frameRate;
private Long shmSize;
private String customLayout;
private RecordingLayout recordingLayout = DefaultValues.recordingLayout;
private String resolution = DefaultValues.resolution;
private Integer frameRate = DefaultValues.frameRate;
private Long shmSize = DefaultValues.shmSize;
private String customLayout = DefaultValues.customLayout;
private Boolean ignoreFailedStreams = DefaultValues.ignoreFailedStreams;
private String mediaNode;
@ -202,9 +207,9 @@ public class RecordingProperties {
* to {@link io.openvidu.java.client.RecordingLayout#CUSTOM} you can call this
* method to set the relative path to the specific custom layout you want to
* use.<br>
* See <a href="https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
* Custom recording layouts
* </a> to learn more
* See <a href=
* "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
* Custom recording layouts </a> to learn more
*/
public RecordingProperties.Builder customLayout(String path) {
this.customLayout = path;
@ -234,13 +239,13 @@ public class RecordingProperties {
}
/**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
* sans-serif">PRO</a> Call this method to force the recording to be hosted in
* the Media Node with identifier <code>mediaNodeId</code>. This property only
* applies to {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> Call
* this method to force the recording to be hosted in the Media Node with
* identifier <code>mediaNodeId</code>. This property only applies to
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true and is ignored
* for {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL}
@ -257,7 +262,7 @@ public class RecordingProperties {
protected RecordingProperties(String name, Boolean hasAudio, Boolean hasVideo, Recording.OutputMode outputMode,
RecordingLayout layout, String resolution, Integer frameRate, Long shmSize, String customLayout,
Boolean ignoreFailedStreams, String mediaNode) {
this.name = name != null ? name : "";
this.name = name != null ? name : DefaultValues.name;
this.hasAudio = hasAudio != null ? hasAudio : DefaultValues.hasAudio;
this.hasVideo = hasVideo != null ? hasVideo : DefaultValues.hasVideo;
this.outputMode = outputMode != null ? outputMode : DefaultValues.outputMode;
@ -390,9 +395,9 @@ public class RecordingProperties {
* If {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} is
* set to {@link io.openvidu.java.client.RecordingLayout#CUSTOM}, this property
* defines the relative path to the specific custom layout you want to use.<br>
* See <a href="https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
* Custom recording layouts
* </a> to learn more
* See <a href=
* "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
* Custom recording layouts </a> to learn more
*/
public String customLayout() {
return this.customLayout;
@ -423,15 +428,15 @@ public class RecordingProperties {
}
/**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
* sans-serif">PRO</a> The Media Node where to host the recording. The default
* option if this property is not defined is the same Media Node hosting the
* Session to record. This property only applies to COMPOSED or
* COMPOSED_QUICK_START recordings with {@link RecordingProperties#hasVideo()}
* to true and is ignored for INDIVIDUAL recordings and audio-only recordings
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> The
* Media Node where to host the recording. The default option if this property
* is not defined is the same Media Node hosting the Session to record. This
* property only applies to COMPOSED or COMPOSED_QUICK_START recordings with
* {@link RecordingProperties#hasVideo()} to true and is ignored for INDIVIDUAL
* recordings and audio-only recordings
*/
public String mediaNode() {
return this.mediaNode;
@ -442,7 +447,7 @@ public class RecordingProperties {
*/
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("name", name);
json.addProperty("name", name != null ? name : DefaultValues.name);
json.addProperty("hasAudio", hasAudio != null ? hasAudio : DefaultValues.hasAudio);
json.addProperty("hasVideo", hasVideo != null ? hasVideo : DefaultValues.hasVideo);
json.addProperty("outputMode", outputMode != null ? outputMode.name() : DefaultValues.outputMode.name());
@ -454,7 +459,7 @@ public class RecordingProperties {
json.addProperty("frameRate", frameRate != null ? frameRate : DefaultValues.frameRate);
json.addProperty("shmSize", shmSize != null ? shmSize : DefaultValues.shmSize);
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
json.addProperty("customLayout", customLayout != null ? customLayout : "");
json.addProperty("customLayout", customLayout != null ? customLayout : DefaultValues.customLayout);
}
}
if (OutputMode.INDIVIDUAL.equals(outputMode)) {
@ -467,69 +472,226 @@ public class RecordingProperties {
return json;
}
public static RecordingProperties.Builder fromJson(Map<String, ?> params, RecordingProperties defaultProps)
throws RuntimeException {
// Final properties being used
String nameFinal = null;
Boolean hasAudioFinal = null;
Boolean hasVideoFinal = null;
OutputMode outputModeFinal = null;
RecordingLayout recordingLayoutFinal = null;
String resolutionFinal = null;
Integer frameRateFinal = null;
Long shmSizeFinal = null;
String customLayoutFinal = null;
Boolean ignoreFailedStreamsFinal = null;
// Defaults properties
String nameDefault = null;
Boolean hasAudioDefault = null;
Boolean hasVideoDefault = null;
OutputMode outputModeDefault = null;
RecordingLayout recordingLayoutDefault = null;
String resolutionDefault = null;
Integer frameRateDefault = null;
Long shmSizeDefault = null;
String customLayoutDefault = null;
Boolean ignoreFailedStreamsDefault = null;
String mediaNodeDefault = null;
if (defaultProps != null) {
nameDefault = defaultProps.name();
hasAudioDefault = defaultProps.hasAudio();
hasVideoDefault = defaultProps.hasVideo();
outputModeDefault = defaultProps.outputMode();
recordingLayoutDefault = defaultProps.recordingLayout();
resolutionDefault = defaultProps.resolution();
frameRateDefault = defaultProps.frameRate();
shmSizeDefault = defaultProps.shmSize();
customLayoutDefault = defaultProps.customLayout();
ignoreFailedStreamsDefault = defaultProps.ignoreFailedStreams();
mediaNodeDefault = defaultProps.mediaNode();
}
nameDefault = nameDefault != null ? nameDefault : DefaultValues.name;
hasAudioDefault = hasAudioDefault != null ? hasAudioDefault : DefaultValues.hasAudio;
hasVideoDefault = hasVideoDefault != null ? hasVideoDefault : DefaultValues.hasVideo;
outputModeDefault = outputModeDefault != null ? outputModeDefault : DefaultValues.outputMode;
recordingLayoutDefault = recordingLayoutDefault != null ? recordingLayoutDefault
: DefaultValues.recordingLayout;
resolutionDefault = resolutionDefault != null ? resolutionDefault : DefaultValues.resolution;
frameRateDefault = frameRateDefault != null ? frameRateDefault : DefaultValues.frameRate;
shmSizeDefault = shmSizeDefault != null ? shmSizeDefault : DefaultValues.shmSize;
customLayoutDefault = customLayoutDefault != null ? customLayoutDefault : DefaultValues.customLayout;
ignoreFailedStreamsDefault = ignoreFailedStreamsDefault != null ? ignoreFailedStreamsDefault
: DefaultValues.ignoreFailedStreams;
// Provided properties through params
String nameParam;
Boolean hasAudioParam;
Boolean hasVideoParam;
String outputModeStringParam;
String recordingLayoutStringParam;
String resolutionParam;
Number frameRateParam;
Long shmSizeParam = null;
String customLayoutParam;
Boolean ignoreFailedStreamsParam;
try {
nameParam = (String) params.get("name");
hasAudioParam = (Boolean) params.get("hasAudio");
hasVideoParam = (Boolean) params.get("hasVideo");
outputModeStringParam = (String) params.get("outputMode");
recordingLayoutStringParam = (String) params.get("recordingLayout");
resolutionParam = (String) params.get("resolution");
frameRateParam = (Number) params.get("frameRate");
if (params.get("shmSize") != null) {
Number shmSize = (Number) params.get("shmSize");
shmSizeParam = shmSize.longValue();
}
customLayoutParam = (String) params.get("customLayout");
ignoreFailedStreamsParam = (Boolean) params.get("ignoreFailedStreams");
} catch (ClassCastException | NumberFormatException e) {
throw new IllegalArgumentException("Type error in some parameter: " + e.getMessage());
}
if (nameParam != null && !nameParam.isEmpty()) {
if (!FormatChecker.isValidRecordingName(nameParam)) {
throw new IllegalArgumentException(
"Parameter 'name' is wrong. Must be an alphanumeric string [a-zA-Z0-9_-~]+");
}
nameFinal = nameParam;
} else {
nameFinal = nameDefault;
}
if (hasAudioParam != null) {
hasAudioFinal = hasAudioParam;
} else {
hasAudioFinal = hasAudioDefault;
}
if (hasVideoParam != null) {
hasVideoFinal = hasVideoParam;
} else {
hasVideoFinal = hasVideoDefault;
}
if (!hasAudioFinal && !hasVideoFinal) {
// Cannot start a recording with both "hasAudio" and "hasVideo" to false
throw new IllegalStateException(
"Cannot start a recording with both \"hasAudio\" and \"hasVideo\" set to false");
}
if (outputModeStringParam != null) {
try {
outputModeFinal = OutputMode.valueOf(outputModeStringParam);
// If param outputMode is COMPOSED when default is COMPOSED_QUICK_START,
// change outputMode to COMPOSED_QUICK_START (and vice versa)
if (defaultProps != null && OutputMode.COMPOSED_QUICK_START.equals(defaultProps.outputMode())
&& OutputMode.COMPOSED.equals(outputModeFinal)) {
outputModeFinal = OutputMode.COMPOSED_QUICK_START;
} else if (defaultProps != null && OutputMode.COMPOSED.equals(defaultProps.outputMode())
&& OutputMode.COMPOSED_QUICK_START.equals(outputModeFinal)) {
outputModeFinal = OutputMode.COMPOSED;
}
} catch (Exception e) {
throw new IllegalArgumentException("Type error in parameter 'outputMode'");
}
} else {
outputModeFinal = outputModeDefault;
}
if (IS_COMPOSED(outputModeFinal)) {
if (recordingLayoutStringParam != null) {
try {
recordingLayoutFinal = RecordingLayout.valueOf(recordingLayoutStringParam);
} catch (Exception e) {
throw new IllegalArgumentException("Type error in parameter 'recordingLayout'");
}
} else {
recordingLayoutFinal = recordingLayoutDefault;
}
if (resolutionParam != null) {
if (!FormatChecker.isAcceptableRecordingResolution(resolutionParam)) {
throw new IllegalStateException(
"Wrong 'resolution' parameter. Acceptable values from 100 to 1999 for both width and height");
}
resolutionFinal = resolutionParam;
} else {
resolutionFinal = resolutionDefault;
}
if (frameRateParam != null) {
if (!FormatChecker.isAcceptableRecordingFrameRate(frameRateParam.intValue())) {
throw new IllegalStateException(
"Wrong 'resolution' parameter. Acceptable values from 100 to 1999 for both width and height");
}
frameRateFinal = frameRateParam.intValue();
} else {
frameRateFinal = frameRateDefault;
}
if (shmSizeParam != null) {
if (!FormatChecker.isAcceptableRecordingShmSize(shmSizeParam)) {
throw new IllegalStateException("Wrong \"shmSize\" parameter. Must be 134217728 (128 MB) minimum");
}
shmSizeFinal = shmSizeParam;
} else {
shmSizeFinal = shmSizeDefault;
}
if (RecordingLayout.CUSTOM.equals(recordingLayoutFinal)) {
if (customLayoutParam != null) {
customLayoutFinal = customLayoutParam;
} else {
customLayoutFinal = customLayoutDefault;
}
}
} else if (OutputMode.INDIVIDUAL.equals(outputModeFinal)) {
if (ignoreFailedStreamsParam != null) {
ignoreFailedStreamsFinal = ignoreFailedStreamsParam;
} else {
ignoreFailedStreamsFinal = ignoreFailedStreamsDefault;
}
}
RecordingProperties.Builder builder = new RecordingProperties.Builder();
builder.name(nameFinal).hasAudio(hasAudioFinal).hasVideo(hasVideoFinal).outputMode(outputModeFinal);
if (IS_COMPOSED(outputModeFinal) && hasVideoFinal) {
builder.recordingLayout(recordingLayoutFinal);
builder.resolution(resolutionFinal);
builder.frameRate(frameRateFinal);
builder.shmSize(shmSizeFinal);
if (RecordingLayout.CUSTOM.equals(recordingLayoutFinal)) {
builder.customLayout(customLayoutFinal);
}
}
if (OutputMode.INDIVIDUAL.equals(outputModeFinal)) {
builder.ignoreFailedStreams(ignoreFailedStreamsFinal);
}
if (mediaNodeDefault == null) {
mediaNodeDefault = SessionProperties.getMediaNodeProperty(params);
}
if (mediaNodeDefault != null && !mediaNodeDefault.isEmpty()) {
builder.mediaNode = mediaNodeDefault;
}
return builder;
}
/**
* @hidden
*/
public static RecordingProperties fromJson(JsonObject json) {
Boolean hasVideoAux = true;
Recording.OutputMode outputModeAux = null;
RecordingLayout recordingLayoutAux = null;
Builder builder = new RecordingProperties.Builder();
if (json.has("name")) {
builder.name(json.get("name").getAsString());
}
if (json.has("hasAudio")) {
builder.hasAudio(json.get("hasAudio").getAsBoolean());
}
if (json.has("hasVideo")) {
hasVideoAux = json.get("hasVideo").getAsBoolean();
builder.hasVideo(hasVideoAux);
}
if (json.has("outputMode")) {
outputModeAux = OutputMode.valueOf(json.get("outputMode").getAsString());
} else {
outputModeAux = DefaultValues.outputMode;
}
builder.outputMode(outputModeAux);
if ((OutputMode.COMPOSED.equals(outputModeAux) || OutputMode.COMPOSED_QUICK_START.equals(outputModeAux))
&& hasVideoAux) {
if (json.has("recordingLayout")) {
recordingLayoutAux = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
builder.recordingLayout(recordingLayoutAux);
}
if (json.has("resolution")) {
builder.resolution(json.get("resolution").getAsString());
}
if (json.has("frameRate")) {
builder.frameRate(json.get("frameRate").getAsInt());
}
if (json.has("shmSize")) {
builder.shmSize(json.get("shmSize").getAsLong());
}
if (RecordingLayout.CUSTOM.equals(recordingLayoutAux)) {
if (json.has("customLayout")) {
builder.customLayout(json.get("customLayout").getAsString());
}
}
}
if (json.has("ignoreFailedStreams") && OutputMode.INDIVIDUAL.equals(outputModeAux)) {
builder.ignoreFailedStreams(json.get("ignoreFailedStreams").getAsBoolean());
}
if (json.has("mediaNode")) {
String mediaNodeId = null;
if (json.get("mediaNode").isJsonObject()) {
mediaNodeId = json.get("mediaNode").getAsJsonObject().get("id").getAsString();
} else if (json.get("mediaNode").isJsonPrimitive()) {
mediaNodeId = json.get("mediaNode").getAsString();
}
if (mediaNodeId != null && !mediaNodeId.isEmpty()) {
builder.mediaNode(mediaNodeId);
}
}
return builder.build();
public final static boolean IS_COMPOSED(OutputMode outputMode) {
return (OutputMode.COMPOSED.equals(outputMode) || OutputMode.COMPOSED_QUICK_START.equals(outputMode));
}
}

View File

@ -671,8 +671,7 @@ public class Session {
.recordingMode(properties.recordingMode())
.defaultRecordingProperties(properties.defaultRecordingProperties())
.customSessionId(properties.customSessionId()).mediaNode(properties.mediaNode())
.forcedVideoCodec(forcedVideoCodec)
.allowTranscoding(allowTranscoding).build();
.forcedVideoCodec(forcedVideoCodec).allowTranscoding(allowTranscoding).build();
this.properties = responseProperties;
log.info("Session '{}' created", this.sessionId);
@ -715,8 +714,10 @@ public class Session {
.mediaMode(MediaMode.valueOf(json.get("mediaMode").getAsString()))
.recordingMode(RecordingMode.valueOf(json.get("recordingMode").getAsString()));
if (json.has("defaultRecordingProperties")) {
builder.defaultRecordingProperties(
RecordingProperties.fromJson(json.get("defaultRecordingProperties").getAsJsonObject()));
String jsonString = json.get("defaultRecordingProperties").getAsJsonObject().toString();
RecordingProperties.Builder recBuilder = RecordingProperties
.fromJson(new Gson().fromJson(jsonString, Map.class), null);
builder.defaultRecordingProperties(recBuilder.build());
}
if (json.has("customSessionId")) {
builder.customSessionId(json.get("customSessionId").getAsString());

View File

@ -17,11 +17,14 @@
package io.openvidu.java.client;
import java.lang.reflect.Type;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
/**
* See {@link io.openvidu.java.client.OpenVidu#createSession(SessionProperties)}
@ -108,12 +111,12 @@ public class SessionProperties {
}
/**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
* sans-serif">PRO</a> Call this method to force the session to be hosted in the
* Media Node with identifier <code>mediaNodeId</code>
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> Call
* this method to force the session to be hosted in the Media Node with
* identifier <code>mediaNodeId</code>
*/
public SessionProperties.Builder mediaNode(String mediaNodeId) {
this.mediaNode = mediaNodeId;
@ -121,14 +124,14 @@ public class SessionProperties {
}
/**
* Define which video codec will be forcibly used for this session.
* This forces all browsers/clients to use the same codec, which would
* avoid transcoding in the media server (Kurento only). If
* <code>forcedVideoCodec</code> is set to NONE, no codec will be forced.
* Define which video codec will be forcibly used for this session. This forces
* all browsers/clients to use the same codec, which would avoid transcoding in
* the media server (Kurento only). If <code>forcedVideoCodec</code> is set to
* NONE, no codec will be forced.
*
* If the browser/client is not compatible with the specified codec, and
* {@link #allowTranscoding(Boolean)} is <code>false</code>, an
* exception will occur.
* {@link #allowTranscoding(Boolean)} is <code>false</code>, an exception will
* occur.
*
* If defined here, this parameter has prevalence over
* OPENVIDU_STREAMS_FORCED_VIDEO_CODEC.
@ -141,11 +144,11 @@ public class SessionProperties {
}
/**
* Actual video codec that will be forcibly used for this session.
* This is the same as <code>forcedVideoCodec</code>, except when its
* value is {@link VideoCodec#MEDIA_SERVER_PREFERRED}: in that case,
* OpenVidu Server will fill this property with a resolved value,
* depending on what is the configured media server.
* Actual video codec that will be forcibly used for this session. This is the
* same as <code>forcedVideoCodec</code>, except when its value is
* {@link VideoCodec#MEDIA_SERVER_PREFERRED}: in that case, OpenVidu Server will
* fill this property with a resolved value, depending on what is the configured
* media server.
*/
public SessionProperties.Builder forcedVideoCodecResolved(VideoCodec forcedVideoCodec) {
this.forcedVideoCodecResolved = forcedVideoCodec;
@ -228,13 +231,13 @@ public class SessionProperties {
}
/**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
* sans-serif">PRO</a> The Media Node where to host the session. The default
* option if this property is not defined is the less loaded Media Node at the
* moment the first user joins the session.
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> The
* Media Node where to host the session. The default option if this property is
* not defined is the less loaded Media Node at the moment the first user joins
* the session.
*/
public String mediaNode() {
return this.mediaNode;
@ -253,12 +256,12 @@ public class SessionProperties {
* Defines which video codec is being forced to be used in the browser/client.
* This is the resolved value, for actual usage in the server.
*
* This is a server-only property, and as such, it doesn't need to be transmitted
* over the wire between server and client. Thus it doesn't get serialized in
* the `toJson()` method.
* This is a server-only property, and as such, it doesn't need to be
* transmitted over the wire between server and client. Thus it doesn't get
* serialized in the `toJson()` method.
*
* If more server-only properties start to appear here, maybe a good idea
* would be to refactor them all into a server-specific Properties class.
* If more server-only properties start to appear here, maybe a good idea would
* be to refactor them all into a server-specific Properties class.
*
* @hidden
*/
@ -377,8 +380,11 @@ public class SessionProperties {
}
if (defaultRecordingPropertiesJson != null) {
try {
RecordingProperties defaultRecordingProperties = RecordingProperties
.fromJson(defaultRecordingPropertiesJson);
String jsonString = defaultRecordingPropertiesJson.toString();
RecordingProperties.Builder recBuilder = RecordingProperties
.fromJson(new Gson().fromJson(jsonString, Map.class), null);
RecordingProperties defaultRecordingProperties = recBuilder.build();
builder = builder.defaultRecordingProperties(defaultRecordingProperties);
} catch (Exception e) {
throw new IllegalArgumentException(
@ -408,8 +414,17 @@ public class SessionProperties {
JsonObject mediaNodeJson;
try {
mediaNodeJson = JsonParser.parseString(params.get("mediaNode").toString()).getAsJsonObject();
} catch (Exception e) {
throw new IllegalArgumentException("Error in parameter 'mediaNode'. It is not a valid JSON object");
} catch (JsonSyntaxException e) {
try {
Gson gson = new Gson();
Type gsonType = new TypeToken<Map>() {
}.getType();
String gsonString = gson.toJson(params.get("mediaNode"), gsonType);
mediaNodeJson = JsonParser.parseString(gsonString).getAsJsonObject();
} catch (Exception e2) {
throw new IllegalArgumentException("Error in parameter 'mediaNode'. It is not a valid JSON object");
}
}
if (!mediaNodeJson.has("id")) {
throw new IllegalArgumentException("Error in parameter 'mediaNode'. Property 'id' not found");

View File

@ -0,0 +1,36 @@
package io.openvidu.java.client.utils;
/**
* @hidden
*/
public final class FormatChecker {
public static boolean isAcceptableRecordingResolution(String stringResolution) {
// Matches every string with format "AxB", being A and B any number not starting
// with 0 and 3 digits long or 4 digits long if they start with 1
return stringResolution.matches("^(?!(0))(([0-9]{3})|1([0-9]{3}))x(?!0)(([0-9]{3})|1([0-9]{3}))$");
}
public static boolean isAcceptableRecordingFrameRate(Integer frameRate) {
// Integer greater than 0 and below 120
return (frameRate > 0 && frameRate <= 120);
}
public static boolean isAcceptableRecordingShmSize(Long shmSize) {
// Long grater than 134217728 (128 MB)
return (shmSize >= 134217728L);
}
public static boolean isServerMetadataFormatCorrect(String metadata) {
return true;
}
public static boolean isValidCustomSessionId(String customSessionId) {
return customSessionId.matches("[a-zA-Z0-9_\\-]+");
}
public static boolean isValidRecordingName(String recodingName) {
return recodingName.matches("[a-zA-Z0-9_\\-~]+");
}
}

View File

@ -1,19 +1,24 @@
package io.openvidu.server.test.unit;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
package io.openvidu.java.client.test;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import io.openvidu.java.client.utils.FormatChecker;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import io.openvidu.server.utils.FormatChecker;
public class FormatCheckerTest extends TestCase {
public class FormatCheckerTest {
public FormatCheckerTest(String testName) {
super(testName);
}
@Test
void customSessionIdFormatTest() {
public static Test suite() {
return new TestSuite(FormatCheckerTest.class);
}
public void testCustomSessionIdFormat() {
List<String> invalidCustomSessionIds = Arrays.asList("", "session#", "session!", "session*", "'session",
"\"session", "sess(ion", "sess_ion)", "session:session", ";session;", "session@session", "$",
@ -24,40 +29,35 @@ public class FormatCheckerTest {
"0session10", "-session", "session-", "-session-", "_session", "session_", "_session_", "_-session",
"session-_", "123_session-1");
FormatChecker formatChecker = new FormatChecker();
for (String id : invalidCustomSessionIds)
assertFalse(formatChecker.isValidCustomSessionId(id));
assertFalse(FormatChecker.isValidCustomSessionId(id));
for (String id : validCustomSessionIds)
assertTrue(formatChecker.isValidCustomSessionId(id));
assertTrue(FormatChecker.isValidCustomSessionId(id));
}
@Test
void acceptableRecordingResolutionTest() {
public void testAcceptableRecordingResolution() {
List<String> invalidResolutions = Arrays.asList("", "a", "123", "true", "AXB", "AxB", "12x", "x12", "0920x1080",
"1080x0720", "720x2000", "99x720", "1920X1080");
List<String> validResolutions = Arrays.asList("1920x1080", "1280x720", "100x1999");
FormatChecker formatChecker = new FormatChecker();
for (String resolution : invalidResolutions)
assertFalse(formatChecker.isAcceptableRecordingResolution(resolution));
assertFalse(FormatChecker.isAcceptableRecordingResolution(resolution));
for (String resolution : validResolutions)
assertTrue(formatChecker.isAcceptableRecordingResolution(resolution));
assertTrue(FormatChecker.isAcceptableRecordingResolution(resolution));
}
@Test
void acceptableRecordingFrameRateTest() {
public void testAcceptableRecordingFrameRate() {
List<Integer> invalidFrameRates = Arrays.asList(-1, 0, 121, 9999);
List<Integer> validFramerates = Arrays.asList(1, 2, 30, 60, 119, 120);
FormatChecker formatChecker = new FormatChecker();
for (int framerate : invalidFrameRates)
assertFalse(formatChecker.isAcceptableRecordingFrameRate(framerate));
assertFalse(FormatChecker.isAcceptableRecordingFrameRate(framerate));
for (int framerate : validFramerates)
assertTrue(formatChecker.isAcceptableRecordingFrameRate(framerate));
assertTrue(FormatChecker.isAcceptableRecordingFrameRate(framerate));
}
}

View File

@ -21,9 +21,9 @@ import com.google.gson.JsonObject;
import io.openvidu.java.client.Recording.Status;
import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.server.core.EndReason;
import io.openvidu.server.recording.Recording;
import io.openvidu.server.utils.RecordingUtils;
public class CDREventRecordingStatusChanged extends CDREventEnd {
@ -44,7 +44,7 @@ public class CDREventRecordingStatusChanged extends CDREventEnd {
json.addProperty("id", this.recording.getId());
json.addProperty("name", this.recording.getName());
json.addProperty("outputMode", this.recording.getOutputMode().name());
if (RecordingUtils.IS_COMPOSED(this.recording.getOutputMode()) && this.recording.hasVideo()) {
if (RecordingProperties.IS_COMPOSED(this.recording.getOutputMode()) && this.recording.hasVideo()) {
json.addProperty("resolution", this.recording.getResolution());
json.addProperty("frameRate", this.recording.getFrameRate());
json.addProperty("recordingLayout", this.recording.getRecordingLayout().name());

View File

@ -53,13 +53,13 @@ import io.openvidu.java.client.KurentoOptions;
import io.openvidu.java.client.OpenViduRole;
import io.openvidu.java.client.Recording;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.java.client.utils.FormatChecker;
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.coturn.CoturnCredentialsService;
import io.openvidu.server.kurento.endpoint.EndpointType;
import io.openvidu.server.kurento.kms.Kms;
import io.openvidu.server.recording.service.RecordingManager;
import io.openvidu.server.utils.FormatChecker;
import io.openvidu.server.utils.GeoLocation;
import io.openvidu.server.utils.GeoLocationByIp;
import io.openvidu.server.utils.MediaNodeManager;
@ -93,8 +93,6 @@ public abstract class SessionManager {
@Autowired
protected GeoLocationByIp geoLocationByIp;
public FormatChecker formatChecker = new FormatChecker();
private UpdatableTimerTask sessionGarbageCollectorTimer;
final protected ConcurrentMap<String, Session> sessions = new ConcurrentHashMap<>();
@ -332,7 +330,7 @@ public abstract class SessionManager {
public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record,
KurentoOptions kurentoOptions, List<IceServerProperties> customIceServers) throws Exception {
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
if (!FormatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
log.error("Data invalid format");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
}

View File

@ -39,7 +39,6 @@ import com.google.gson.JsonObject;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.server.core.MediaServer;
import io.openvidu.server.kurento.core.KurentoSession;
import io.openvidu.server.utils.RecordingUtils;
import io.openvidu.server.utils.UpdatableTimerTask;
/**
@ -192,14 +191,14 @@ public class Kms {
public synchronized void incrementActiveRecordings(String sessionId, String recordingId,
RecordingProperties properties) {
this.activeRecordings.put(recordingId, sessionId);
if (RecordingUtils.IS_COMPOSED(properties.outputMode())) {
if (RecordingProperties.IS_COMPOSED(properties.outputMode())) {
this.activeComposedRecordings.incrementAndGet();
}
}
public synchronized void decrementActiveRecordings(String recordingId, RecordingProperties properties) {
this.activeRecordings.remove(recordingId);
if (RecordingUtils.IS_COMPOSED(properties.outputMode())) {
if (RecordingProperties.IS_COMPOSED(properties.outputMode())) {
this.activeComposedRecordings.decrementAndGet();
}
kmsManager.getMediaNodeManager().dropIdleMediaNode(this.id);

View File

@ -23,7 +23,6 @@ import com.google.gson.JsonObject;
import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.server.utils.RecordingUtils;
public class Recording {
@ -77,7 +76,7 @@ public class Recording {
.valueOf(json.get("outputMode").getAsString());
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) {
if (RecordingProperties.IS_COMPOSED(outputMode) && hasVideo) {
if (json.has("resolution")) {
builder.resolution(json.get("resolution").getAsString());
}
@ -193,7 +192,8 @@ public class Recording {
json.addProperty("object", "recording");
json.addProperty("name", this.recordingProperties.name());
json.addProperty("outputMode", this.getOutputMode().name());
if (RecordingUtils.IS_COMPOSED(this.recordingProperties.outputMode()) && this.recordingProperties.hasVideo()) {
if (RecordingProperties.IS_COMPOSED(this.recordingProperties.outputMode())
&& this.recordingProperties.hasVideo()) {
json.addProperty("resolution", this.recordingProperties.resolution());
json.addProperty("frameRate", this.recordingProperties.frameRate());
json.addProperty("recordingLayout", this.recordingProperties.recordingLayout().name());

View File

@ -80,7 +80,6 @@ import io.openvidu.server.utils.DockerManager;
import io.openvidu.server.utils.JsonUtils;
import io.openvidu.server.utils.LocalCustomFileManager;
import io.openvidu.server.utils.LocalDockerManager;
import io.openvidu.server.utils.RecordingUtils;
import io.openvidu.server.utils.RemoteOperationUtils;
public class RecordingManager {
@ -443,7 +442,7 @@ public class RecordingManager {
this.singleStreamRecordingService.startRecorderEndpointForPublisherEndpoint(recording.getId(), profile,
participant, new CountDownLatch(1));
} else if (RecordingUtils.IS_COMPOSED(recording.getOutputMode()) && !recording.hasVideo()) {
} else if (RecordingProperties.IS_COMPOSED(recording.getOutputMode()) && !recording.hasVideo()) {
// Connect this stream to existing Composite recorder
log.info("Joining PublisherEndpoint to existing Composite in session {} for new stream of participant {}",
session.getSessionId(), participant.getParticipantPublicId());
@ -479,7 +478,7 @@ public class RecordingManager {
} catch (InterruptedException e) {
log.error("Exception while waiting for state change", e);
}
} else if (RecordingUtils.IS_COMPOSED(recording.getOutputMode()) && !recording.hasVideo()) {
} else if (RecordingProperties.IS_COMPOSED(recording.getOutputMode()) && !recording.hasVideo()) {
// Disconnect this stream from existing Composite recorder
log.info("Removing PublisherEndpoint from Composite in session {} for stream of participant {}",
session.getSessionId(), streamId);

View File

@ -38,7 +38,6 @@ import io.openvidu.server.recording.RecordingDownloader;
import io.openvidu.server.recording.RecordingUploader;
import io.openvidu.server.utils.CommandExecutor;
import io.openvidu.server.utils.CustomFileManager;
import io.openvidu.server.utils.RecordingUtils;
public abstract class RecordingService {
@ -160,7 +159,7 @@ public abstract class RecordingService {
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(recordingId)
.outputMode(properties.outputMode()).hasAudio(properties.hasAudio()).hasVideo(properties.hasVideo())
.mediaNode(properties.mediaNode());
if (RecordingUtils.IS_COMPOSED(properties.outputMode()) && properties.hasVideo()) {
if (RecordingProperties.IS_COMPOSED(properties.outputMode()) && properties.hasVideo()) {
builder.resolution(properties.resolution());
builder.frameRate(properties.frameRate());
builder.recordingLayout(properties.recordingLayout());

View File

@ -56,7 +56,6 @@ import io.openvidu.java.client.ConnectionType;
import io.openvidu.java.client.IceServerProperties;
import io.openvidu.java.client.MediaMode;
import io.openvidu.java.client.Recording.OutputMode;
import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.java.client.VideoCodec;
@ -70,7 +69,6 @@ import io.openvidu.server.core.Token;
import io.openvidu.server.kurento.core.KurentoMediaOptions;
import io.openvidu.server.recording.Recording;
import io.openvidu.server.recording.service.RecordingManager;
import io.openvidu.server.utils.RecordingUtils;
import io.openvidu.server.utils.RestUtils;
/**
@ -427,9 +425,9 @@ public class SessionRestController {
RecordingProperties recordingProperties;
try {
recordingProperties = getRecordingPropertiesFromParams(params, session).build();
} catch (RuntimeException e) {
} catch (IllegalStateException e) {
return this.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.UNPROCESSABLE_ENTITY);
} catch (Exception e) {
} catch (RuntimeException e) {
return this.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.BAD_REQUEST);
}
@ -853,208 +851,10 @@ public class SessionRestController {
return builder;
}
protected RecordingProperties.Builder getRecordingPropertiesFromParams(Map<?, ?> params, Session session)
throws Exception {
// Final properties being used
String nameFinal = null;
Boolean hasAudioFinal = null;
Boolean hasVideoFinal = null;
OutputMode outputModeFinal = null;
RecordingLayout recordingLayoutFinal = null;
String resolutionFinal = null;
Integer frameRateFinal = null;
Long shmSizeFinal = null;
String customLayoutFinal = null;
Boolean ignoreFailedStreamsFinal = null;
RecordingProperties defaultProps = session.getSessionProperties().defaultRecordingProperties();
// Default properties configured in Session
String nameDefault = defaultProps.name();
Boolean hasAudioDefault = defaultProps.hasAudio();
Boolean hasVideoDefault = defaultProps.hasVideo();
OutputMode outputModeDefault = defaultProps.outputMode();
RecordingLayout recordingLayoutDefault = defaultProps.recordingLayout();
String resolutionDefault = defaultProps.resolution();
Integer frameRateDefault = defaultProps.frameRate();
Long shmSizeDefault = defaultProps.shmSize();
String customLayoutDefault = defaultProps.customLayout();
Boolean ignoreFailedStreamsDefault = defaultProps.ignoreFailedStreams();
// Provided properties through params
String sessionIdParam;
String nameParam;
Boolean hasAudioParam;
Boolean hasVideoParam;
String outputModeStringParam;
String recordingLayoutStringParam;
String resolutionParam;
Integer frameRateParam;
Long shmSizeParam = null;
String customLayoutParam;
Boolean ignoreFailedStreamsParam;
try {
sessionIdParam = (String) params.get("session");
nameParam = (String) params.get("name");
hasAudioParam = (Boolean) params.get("hasAudio");
hasVideoParam = (Boolean) params.get("hasVideo");
outputModeStringParam = (String) params.get("outputMode");
recordingLayoutStringParam = (String) params.get("recordingLayout");
resolutionParam = (String) params.get("resolution");
frameRateParam = (Integer) params.get("frameRate");
if (params.get("shmSize") != null) {
shmSizeParam = Long.parseLong(params.get("shmSize").toString());
}
customLayoutParam = (String) params.get("customLayout");
ignoreFailedStreamsParam = (Boolean) params.get("ignoreFailedStreams");
} catch (ClassCastException | NumberFormatException e) {
throw new Exception("Type error in some parameter: " + e.getMessage());
}
if (sessionIdParam == null) {
// "session" parameter not found
throw new Exception("\"session\" parameter is mandatory");
}
if (nameParam != null && !nameParam.isEmpty()) {
if (!sessionManager.formatChecker.isValidRecordingName(nameParam)) {
throw new Exception("Parameter 'name' is wrong. Must be an alphanumeric string [a-zA-Z0-9_-]+");
}
nameFinal = nameParam;
} else if (nameDefault != null) {
nameFinal = nameDefault;
} else {
nameFinal = "";
}
if (hasAudioParam != null) {
hasAudioFinal = hasAudioParam;
} else if (hasAudioDefault != null) {
hasAudioFinal = hasAudioDefault;
} else {
hasAudioFinal = RecordingProperties.DefaultValues.hasAudio;
}
if (hasVideoParam != null) {
hasVideoFinal = hasVideoParam;
} else if (hasAudioDefault != null) {
hasVideoFinal = hasVideoDefault;
} else {
hasVideoFinal = RecordingProperties.DefaultValues.hasVideo;
}
if (!hasAudioFinal && !hasVideoFinal) {
// Cannot start a recording with both "hasAudio" and "hasVideo" to false
throw new RuntimeException("Cannot start a recording with both \"hasAudio\" and \"hasVideo\" set to false");
}
if (outputModeStringParam != null) {
try {
outputModeFinal = OutputMode.valueOf(outputModeStringParam);
// If param outputMode is COMPOSED when default is COMPOSED_QUICK_START,
// change outputMode to COMPOSED_QUICK_START (and vice versa)
if (OutputMode.COMPOSED_QUICK_START.equals(outputModeDefault)
&& OutputMode.COMPOSED.equals(outputModeFinal)) {
outputModeFinal = OutputMode.COMPOSED_QUICK_START;
} else if (OutputMode.COMPOSED.equals(outputModeDefault)
&& OutputMode.COMPOSED_QUICK_START.equals(outputModeFinal)) {
outputModeFinal = OutputMode.COMPOSED;
}
} catch (Exception e) {
throw new Exception("Type error in parameter 'outputMode'");
}
} else if (outputModeDefault != null) {
outputModeFinal = outputModeDefault;
} else {
outputModeFinal = RecordingProperties.DefaultValues.outputMode;
}
if (RecordingUtils.IS_COMPOSED(outputModeFinal)) {
if (recordingLayoutStringParam != null) {
try {
recordingLayoutFinal = RecordingLayout.valueOf(recordingLayoutStringParam);
} catch (Exception e) {
throw new Exception("Type error in parameter 'recordingLayout'");
}
} else if (recordingLayoutDefault != null) {
recordingLayoutFinal = recordingLayoutDefault;
} else {
recordingLayoutFinal = RecordingProperties.DefaultValues.recordingLayout;
}
if (resolutionParam != null) {
if (!sessionManager.formatChecker.isAcceptableRecordingResolution(resolutionParam)) {
throw new RuntimeException(
"Wrong 'resolution' parameter. Acceptable values from 100 to 1999 for both width and height");
}
resolutionFinal = resolutionParam;
} else if (resolutionDefault != null) {
resolutionFinal = resolutionDefault;
} else {
resolutionFinal = RecordingProperties.DefaultValues.resolution;
}
if (frameRateParam != null) {
if (!sessionManager.formatChecker.isAcceptableRecordingFrameRate(frameRateParam)) {
throw new RuntimeException(
"Wrong 'resolution' parameter. Acceptable values from 100 to 1999 for both width and height");
}
frameRateFinal = frameRateParam;
} else if (frameRateDefault != null) {
frameRateFinal = frameRateDefault;
} else {
frameRateFinal = RecordingProperties.DefaultValues.frameRate;
}
if (shmSizeParam != null) {
if (!sessionManager.formatChecker.isAcceptableRecordingShmSize(shmSizeParam)) {
throw new RuntimeException("Wrong \"shmSize\" parameter. Must be 134217728 (128 MB) minimum");
}
shmSizeFinal = shmSizeParam;
} else if (shmSizeDefault != null) {
shmSizeFinal = shmSizeDefault;
} else {
shmSizeFinal = RecordingProperties.DefaultValues.shmSize;
}
if (RecordingLayout.CUSTOM.equals(recordingLayoutFinal)) {
if (customLayoutParam != null) {
customLayoutFinal = customLayoutParam;
} else if (customLayoutDefault != null) {
customLayoutFinal = customLayoutDefault;
} else {
customLayoutFinal = "";
}
}
} else if (OutputMode.INDIVIDUAL.equals(outputModeFinal)) {
if (ignoreFailedStreamsParam != null) {
ignoreFailedStreamsFinal = ignoreFailedStreamsParam;
} else if (ignoreFailedStreamsDefault != null) {
ignoreFailedStreamsFinal = ignoreFailedStreamsDefault;
} else {
ignoreFailedStreamsFinal = RecordingProperties.DefaultValues.ignoreFailedStreams;
}
}
RecordingProperties.Builder builder = new RecordingProperties.Builder();
builder.name(nameFinal).hasAudio(hasAudioFinal).hasVideo(hasVideoFinal).outputMode(outputModeFinal);
if (RecordingUtils.IS_COMPOSED(outputModeFinal) && hasVideoFinal) {
builder.recordingLayout(recordingLayoutFinal);
builder.resolution(resolutionFinal);
builder.frameRate(frameRateFinal);
builder.shmSize(shmSizeFinal);
if (RecordingLayout.CUSTOM.equals(recordingLayoutFinal)) {
builder.customLayout(customLayoutFinal);
}
}
if (OutputMode.INDIVIDUAL.equals(outputModeFinal)) {
builder.ignoreFailedStreams(ignoreFailedStreamsFinal);
}
protected RecordingProperties.Builder getRecordingPropertiesFromParams(Map<String, ?> params, Session session)
throws RuntimeException {
RecordingProperties.Builder builder = RecordingProperties.fromJson(params,
session.getSessionProperties().defaultRecordingProperties());
return builder;
}

View File

@ -47,6 +47,7 @@ import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.ConnectionProperties;
import io.openvidu.java.client.utils.FormatChecker;
import io.openvidu.server.config.OpenviduBuildInfo;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.EndReason;
@ -277,7 +278,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
if (tokenObj != null) {
String clientMetadata = getStringParam(request, ProtocolElements.JOINROOM_METADATA_PARAM);
if (sessionManager.formatChecker.isServerMetadataFormatCorrect(clientMetadata)) {
if (FormatChecker.isServerMetadataFormatCorrect(clientMetadata)) {
// While closing a session users can't join
if (session.closingLock.readLock().tryLock()) {

View File

@ -1,54 +0,0 @@
/*
* (C) Copyright 2017-2022 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.utils;
public class FormatChecker {
public boolean isAcceptableRecordingResolution(String stringResolution) {
// Matches every string with format "AxB", being A and B any number not starting
// with 0 and 3 digits long or 4 digits long if they start with 1
return stringResolution.matches("^(?!(0))(([0-9]{3})|1([0-9]{3}))x(?!0)(([0-9]{3})|1([0-9]{3}))$");
}
public boolean isAcceptableRecordingFrameRate(Integer frameRate) {
// Integer greater than 0 and below 120
return (frameRate > 0 && frameRate <= 120);
}
public boolean isAcceptableRecordingShmSize(Long shmSize) {
// Long grater than 134217728 (128 MB)
return (shmSize >= 134217728L);
}
public boolean isServerMetadataFormatCorrect(String metadata) {
return true;
}
public boolean isValidCustomSessionId(String customSessionId) {
return isValidAlphanumeric(customSessionId);
}
public boolean isValidRecordingName(String recodingName) {
return isValidAlphanumeric(recodingName);
}
private boolean isValidAlphanumeric(String str) {
return str.matches("[a-zA-Z0-9_-]+");
}
}

View File

@ -1,15 +1,10 @@
package io.openvidu.server.utils;
import io.openvidu.java.client.Recording.OutputMode;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.server.core.Session;
public final class RecordingUtils {
public final static boolean IS_COMPOSED(OutputMode outputMode) {
return (OutputMode.COMPOSED.equals(outputMode) || OutputMode.COMPOSED_QUICK_START.equals(outputMode));
}
public final static RecordingProperties RECORDING_PROPERTIES_WITH_MEDIA_NODE(Session session) {
RecordingProperties recordingProperties = session.getSessionProperties().defaultRecordingProperties();
if (recordingProperties.mediaNode() == null) {

View File

@ -3226,6 +3226,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
body = "{'session':'CUSTOM_SESSION_ID','name':999}";
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
body = "{'session':'CUSTOM_SESSION_ID','name':'notalphanumeric@'}";
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
body = "{'session':'CUSTOM_SESSION_ID','name':'NAME','outputMode':'NOT_EXISTS'}";
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
body = "{'session':'CUSTOM_SESSION_ID','name':'NAME','outputMode':'COMPOSED','recordingLayout':'NOT_EXISTS'}";
@ -3236,7 +3238,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
// 422
body = "{'session':'CUSTOM_SESSION_ID','name':'NAME','outputMode':'COMPOSED','recordingLayout':'BEST_FIT','customLayout':'CUSTOM_LAYOUT','hasAudio':false,'hasVideo':false}";
body = "{'session':'CUSTOM_SESSION_ID','name':'NAME~','outputMode':'COMPOSED','recordingLayout':'BEST_FIT','customLayout':'CUSTOM_LAYOUT','hasAudio':false,'hasVideo':false}";
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_UNPROCESSABLE_ENTITY);
body = "{'session':'CUSTOM_SESSION_ID','name':'NAME','outputMode':'COMPOSED','recordingLayout':'BEST_FIT','customLayout':'CUSTOM_LAYOUT','hasAudio':true,'hasVideo':true,'resolution':'1920x2000'}";
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_UNPROCESSABLE_ENTITY);
@ -3848,8 +3850,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
String rareCharsName = "öæééEstoSi`+´çḈ€$";
user.getDriver().findElement(By.id("recording-name-field")).sendKeys(rareCharsName);
String recordingName = "1234abcABC_-~";
user.getDriver().findElement(By.id("recording-name-field")).sendKeys(recordingName);
user.getDriver().findElement(By.id("recording-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-ALWAYS")).click();
@ -3888,7 +3890,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Assert.assertEquals("Wrong recording startTime/timestamp in webhook event",
event.get("startTime").getAsLong(), event.get("timestamp").getAsLong());
Assert.assertNull("Wrong recording reason in webhook event (should be null)", event.get("reason"));
Assert.assertEquals("Wrong recording name in webhook event", rareCharsName,
Assert.assertEquals("Wrong recording name in webhook event", recordingName,
event.get("name").getAsString());
user.getDriver().findElement(By.id("add-user-btn")).click();