mirror of https://github.com/OpenVidu/openvidu.git
Move RecordingProperties#fromJson from openvidu-server to openvidu-java-client
parent
7e8b6adaad
commit
735d4a96bd
|
@ -17,6 +17,9 @@
|
||||||
|
|
||||||
package io.openvidu.java.client;
|
package io.openvidu.java.client;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
@ -120,7 +123,9 @@ public class Recording {
|
||||||
}
|
}
|
||||||
this.status = Recording.Status.valueOf(json.get("status").getAsString());
|
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
|
* URL of the recording. You can access the file from there. It is
|
||||||
* <code>null</code> until recording reaches "ready" or "failed" status. If
|
* <code>null</code> until recording reaches "ready" or "failed" status. If
|
||||||
* <a href="https://docs.openvidu.io/en/stable/reference-docs/openvidu-config/">
|
* <a href="https://docs.openvidu.io/en/stable/reference-docs/openvidu-config/">
|
||||||
* OpenVidu Server configuration
|
* OpenVidu Server configuration </a> property
|
||||||
* </a> property <code>OPENVIDU_RECORDING_PUBLIC_ACCESS</code> is false,
|
* <code>OPENVIDU_RECORDING_PUBLIC_ACCESS</code> is false, this path will be
|
||||||
* this path will be secured with OpenVidu credentials
|
* secured with OpenVidu credentials
|
||||||
*/
|
*/
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return url;
|
return url;
|
||||||
|
|
|
@ -17,9 +17,12 @@
|
||||||
|
|
||||||
package io.openvidu.java.client;
|
package io.openvidu.java.client;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
import io.openvidu.java.client.Recording.OutputMode;
|
import io.openvidu.java.client.Recording.OutputMode;
|
||||||
|
import io.openvidu.java.client.utils.FormatChecker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See
|
* See
|
||||||
|
@ -28,6 +31,7 @@ import io.openvidu.java.client.Recording.OutputMode;
|
||||||
public class RecordingProperties {
|
public class RecordingProperties {
|
||||||
|
|
||||||
public static class DefaultValues {
|
public static class DefaultValues {
|
||||||
|
public static final String name = "";
|
||||||
public static final Boolean hasAudio = true;
|
public static final Boolean hasAudio = true;
|
||||||
public static final Boolean hasVideo = true;
|
public static final Boolean hasVideo = true;
|
||||||
public static final Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
|
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 String resolution = "1280x720";
|
||||||
public static final Integer frameRate = 25;
|
public static final Integer frameRate = 25;
|
||||||
public static final Long shmSize = 536870912L;
|
public static final Long shmSize = 536870912L;
|
||||||
|
public static final String customLayout = "";
|
||||||
public static final Boolean ignoreFailedStreams = false;
|
public static final Boolean ignoreFailedStreams = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For all
|
// For all
|
||||||
private String name = "";
|
private String name = DefaultValues.name;
|
||||||
private Boolean hasAudio = true;
|
private Boolean hasAudio = DefaultValues.hasAudio;
|
||||||
private Boolean hasVideo = true;
|
private Boolean hasVideo = DefaultValues.hasVideo;
|
||||||
private Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
|
private Recording.OutputMode outputMode = DefaultValues.outputMode;
|
||||||
// For COMPOSED/COMPOSED_QUICK_START + hasVideo
|
// For COMPOSED/COMPOSED_QUICK_START + hasVideo
|
||||||
private RecordingLayout recordingLayout;
|
private RecordingLayout recordingLayout;
|
||||||
private String resolution;
|
private String resolution;
|
||||||
|
@ -60,15 +65,15 @@ public class RecordingProperties {
|
||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private String name = "";
|
private String name = DefaultValues.name;
|
||||||
private Boolean hasAudio = DefaultValues.hasAudio;
|
private Boolean hasAudio = DefaultValues.hasAudio;
|
||||||
private Boolean hasVideo = DefaultValues.hasVideo;
|
private Boolean hasVideo = DefaultValues.hasVideo;
|
||||||
private Recording.OutputMode outputMode = DefaultValues.outputMode;
|
private Recording.OutputMode outputMode = DefaultValues.outputMode;
|
||||||
private RecordingLayout recordingLayout;
|
private RecordingLayout recordingLayout = DefaultValues.recordingLayout;
|
||||||
private String resolution;
|
private String resolution = DefaultValues.resolution;
|
||||||
private Integer frameRate;
|
private Integer frameRate = DefaultValues.frameRate;
|
||||||
private Long shmSize;
|
private Long shmSize = DefaultValues.shmSize;
|
||||||
private String customLayout;
|
private String customLayout = DefaultValues.customLayout;
|
||||||
private Boolean ignoreFailedStreams = DefaultValues.ignoreFailedStreams;
|
private Boolean ignoreFailedStreams = DefaultValues.ignoreFailedStreams;
|
||||||
private String mediaNode;
|
private String mediaNode;
|
||||||
|
|
||||||
|
@ -202,9 +207,9 @@ public class RecordingProperties {
|
||||||
* to {@link io.openvidu.java.client.RecordingLayout#CUSTOM} you can call this
|
* 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
|
* method to set the relative path to the specific custom layout you want to
|
||||||
* use.<br>
|
* use.<br>
|
||||||
* See <a href="https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
|
* See <a href=
|
||||||
* Custom recording layouts
|
* "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
|
||||||
* </a> to learn more
|
* Custom recording layouts </a> to learn more
|
||||||
*/
|
*/
|
||||||
public RecordingProperties.Builder customLayout(String path) {
|
public RecordingProperties.Builder customLayout(String path) {
|
||||||
this.customLayout = path;
|
this.customLayout = path;
|
||||||
|
@ -234,13 +239,13 @@ public class RecordingProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
|
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
|
||||||
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
|
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
|
||||||
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
|
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
|
||||||
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
|
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> Call
|
||||||
* sans-serif">PRO</a> Call this method to force the recording to be hosted in
|
* this method to force the recording to be hosted in the Media Node with
|
||||||
* the Media Node with identifier <code>mediaNodeId</code>. This property only
|
* identifier <code>mediaNodeId</code>. This property only applies to
|
||||||
* applies to {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
|
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
|
||||||
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
|
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
|
||||||
* recordings with {@link RecordingProperties#hasVideo()} to true and is ignored
|
* recordings with {@link RecordingProperties#hasVideo()} to true and is ignored
|
||||||
* for {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL}
|
* 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,
|
protected RecordingProperties(String name, Boolean hasAudio, Boolean hasVideo, Recording.OutputMode outputMode,
|
||||||
RecordingLayout layout, String resolution, Integer frameRate, Long shmSize, String customLayout,
|
RecordingLayout layout, String resolution, Integer frameRate, Long shmSize, String customLayout,
|
||||||
Boolean ignoreFailedStreams, String mediaNode) {
|
Boolean ignoreFailedStreams, String mediaNode) {
|
||||||
this.name = name != null ? name : "";
|
this.name = name != null ? name : DefaultValues.name;
|
||||||
this.hasAudio = hasAudio != null ? hasAudio : DefaultValues.hasAudio;
|
this.hasAudio = hasAudio != null ? hasAudio : DefaultValues.hasAudio;
|
||||||
this.hasVideo = hasVideo != null ? hasVideo : DefaultValues.hasVideo;
|
this.hasVideo = hasVideo != null ? hasVideo : DefaultValues.hasVideo;
|
||||||
this.outputMode = outputMode != null ? outputMode : DefaultValues.outputMode;
|
this.outputMode = outputMode != null ? outputMode : DefaultValues.outputMode;
|
||||||
|
@ -390,9 +395,9 @@ public class RecordingProperties {
|
||||||
* If {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} is
|
* If {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} is
|
||||||
* set to {@link io.openvidu.java.client.RecordingLayout#CUSTOM}, this property
|
* 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>
|
* 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">
|
* See <a href=
|
||||||
* Custom recording layouts
|
* "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts">
|
||||||
* </a> to learn more
|
* Custom recording layouts </a> to learn more
|
||||||
*/
|
*/
|
||||||
public String customLayout() {
|
public String customLayout() {
|
||||||
return this.customLayout;
|
return this.customLayout;
|
||||||
|
@ -423,15 +428,15 @@ public class RecordingProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
|
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
|
||||||
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
|
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
|
||||||
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
|
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
|
||||||
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
|
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> The
|
||||||
* sans-serif">PRO</a> The Media Node where to host the recording. The default
|
* Media Node where to host the recording. The default option if this property
|
||||||
* option if this property is not defined is the same Media Node hosting the
|
* is not defined is the same Media Node hosting the Session to record. This
|
||||||
* Session to record. This property only applies to COMPOSED or
|
* property only applies to COMPOSED or COMPOSED_QUICK_START recordings with
|
||||||
* COMPOSED_QUICK_START recordings with {@link RecordingProperties#hasVideo()}
|
* {@link RecordingProperties#hasVideo()} to true and is ignored for INDIVIDUAL
|
||||||
* to true and is ignored for INDIVIDUAL recordings and audio-only recordings
|
* recordings and audio-only recordings
|
||||||
*/
|
*/
|
||||||
public String mediaNode() {
|
public String mediaNode() {
|
||||||
return this.mediaNode;
|
return this.mediaNode;
|
||||||
|
@ -442,7 +447,7 @@ public class RecordingProperties {
|
||||||
*/
|
*/
|
||||||
public JsonObject toJson() {
|
public JsonObject toJson() {
|
||||||
JsonObject json = new JsonObject();
|
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("hasAudio", hasAudio != null ? hasAudio : DefaultValues.hasAudio);
|
||||||
json.addProperty("hasVideo", hasVideo != null ? hasVideo : DefaultValues.hasVideo);
|
json.addProperty("hasVideo", hasVideo != null ? hasVideo : DefaultValues.hasVideo);
|
||||||
json.addProperty("outputMode", outputMode != null ? outputMode.name() : DefaultValues.outputMode.name());
|
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("frameRate", frameRate != null ? frameRate : DefaultValues.frameRate);
|
||||||
json.addProperty("shmSize", shmSize != null ? shmSize : DefaultValues.shmSize);
|
json.addProperty("shmSize", shmSize != null ? shmSize : DefaultValues.shmSize);
|
||||||
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
|
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
|
||||||
json.addProperty("customLayout", customLayout != null ? customLayout : "");
|
json.addProperty("customLayout", customLayout != null ? customLayout : DefaultValues.customLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (OutputMode.INDIVIDUAL.equals(outputMode)) {
|
if (OutputMode.INDIVIDUAL.equals(outputMode)) {
|
||||||
|
@ -467,69 +472,226 @@ public class RecordingProperties {
|
||||||
return json;
|
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
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public static RecordingProperties fromJson(JsonObject json) {
|
public final static boolean IS_COMPOSED(OutputMode outputMode) {
|
||||||
|
return (OutputMode.COMPOSED.equals(outputMode) || OutputMode.COMPOSED_QUICK_START.equals(outputMode));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -671,8 +671,7 @@ public class Session {
|
||||||
.recordingMode(properties.recordingMode())
|
.recordingMode(properties.recordingMode())
|
||||||
.defaultRecordingProperties(properties.defaultRecordingProperties())
|
.defaultRecordingProperties(properties.defaultRecordingProperties())
|
||||||
.customSessionId(properties.customSessionId()).mediaNode(properties.mediaNode())
|
.customSessionId(properties.customSessionId()).mediaNode(properties.mediaNode())
|
||||||
.forcedVideoCodec(forcedVideoCodec)
|
.forcedVideoCodec(forcedVideoCodec).allowTranscoding(allowTranscoding).build();
|
||||||
.allowTranscoding(allowTranscoding).build();
|
|
||||||
|
|
||||||
this.properties = responseProperties;
|
this.properties = responseProperties;
|
||||||
log.info("Session '{}' created", this.sessionId);
|
log.info("Session '{}' created", this.sessionId);
|
||||||
|
@ -715,8 +714,10 @@ public class Session {
|
||||||
.mediaMode(MediaMode.valueOf(json.get("mediaMode").getAsString()))
|
.mediaMode(MediaMode.valueOf(json.get("mediaMode").getAsString()))
|
||||||
.recordingMode(RecordingMode.valueOf(json.get("recordingMode").getAsString()));
|
.recordingMode(RecordingMode.valueOf(json.get("recordingMode").getAsString()));
|
||||||
if (json.has("defaultRecordingProperties")) {
|
if (json.has("defaultRecordingProperties")) {
|
||||||
builder.defaultRecordingProperties(
|
String jsonString = json.get("defaultRecordingProperties").getAsJsonObject().toString();
|
||||||
RecordingProperties.fromJson(json.get("defaultRecordingProperties").getAsJsonObject()));
|
RecordingProperties.Builder recBuilder = RecordingProperties
|
||||||
|
.fromJson(new Gson().fromJson(jsonString, Map.class), null);
|
||||||
|
builder.defaultRecordingProperties(recBuilder.build());
|
||||||
}
|
}
|
||||||
if (json.has("customSessionId")) {
|
if (json.has("customSessionId")) {
|
||||||
builder.customSessionId(json.get("customSessionId").getAsString());
|
builder.customSessionId(json.get("customSessionId").getAsString());
|
||||||
|
|
|
@ -17,11 +17,14 @@
|
||||||
|
|
||||||
package io.openvidu.java.client;
|
package io.openvidu.java.client;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
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)}
|
* 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/"
|
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
|
||||||
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
|
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
|
||||||
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
|
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
|
||||||
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
|
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> Call
|
||||||
* sans-serif">PRO</a> Call this method to force the session to be hosted in the
|
* this method to force the session to be hosted in the Media Node with
|
||||||
* Media Node with identifier <code>mediaNodeId</code>
|
* identifier <code>mediaNodeId</code>
|
||||||
*/
|
*/
|
||||||
public SessionProperties.Builder mediaNode(String mediaNodeId) {
|
public SessionProperties.Builder mediaNode(String mediaNodeId) {
|
||||||
this.mediaNode = mediaNodeId;
|
this.mediaNode = mediaNodeId;
|
||||||
|
@ -121,14 +124,14 @@ public class SessionProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define which video codec will be forcibly used for this session.
|
* Define which video codec will be forcibly used for this session. This forces
|
||||||
* This forces all browsers/clients to use the same codec, which would
|
* all browsers/clients to use the same codec, which would avoid transcoding in
|
||||||
* avoid transcoding in the media server (Kurento only). If
|
* the media server (Kurento only). If <code>forcedVideoCodec</code> is set to
|
||||||
* <code>forcedVideoCodec</code> is set to NONE, no codec will be forced.
|
* NONE, no codec will be forced.
|
||||||
*
|
*
|
||||||
* If the browser/client is not compatible with the specified codec, and
|
* If the browser/client is not compatible with the specified codec, and
|
||||||
* {@link #allowTranscoding(Boolean)} is <code>false</code>, an
|
* {@link #allowTranscoding(Boolean)} is <code>false</code>, an exception will
|
||||||
* exception will occur.
|
* occur.
|
||||||
*
|
*
|
||||||
* If defined here, this parameter has prevalence over
|
* If defined here, this parameter has prevalence over
|
||||||
* OPENVIDU_STREAMS_FORCED_VIDEO_CODEC.
|
* OPENVIDU_STREAMS_FORCED_VIDEO_CODEC.
|
||||||
|
@ -141,11 +144,11 @@ public class SessionProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actual video codec that will be forcibly used for this session.
|
* Actual video codec that will be forcibly used for this session. This is the
|
||||||
* This is the same as <code>forcedVideoCodec</code>, except when its
|
* same as <code>forcedVideoCodec</code>, except when its value is
|
||||||
* value is {@link VideoCodec#MEDIA_SERVER_PREFERRED}: in that case,
|
* {@link VideoCodec#MEDIA_SERVER_PREFERRED}: in that case, OpenVidu Server will
|
||||||
* OpenVidu Server will fill this property with a resolved value,
|
* fill this property with a resolved value, depending on what is the configured
|
||||||
* depending on what is the configured media server.
|
* media server.
|
||||||
*/
|
*/
|
||||||
public SessionProperties.Builder forcedVideoCodecResolved(VideoCodec forcedVideoCodec) {
|
public SessionProperties.Builder forcedVideoCodecResolved(VideoCodec forcedVideoCodec) {
|
||||||
this.forcedVideoCodecResolved = forcedVideoCodec;
|
this.forcedVideoCodecResolved = forcedVideoCodec;
|
||||||
|
@ -228,13 +231,13 @@ public class SessionProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/"
|
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display:
|
||||||
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
|
* inline-block; background-color: rgb(0, 136, 170); color: white; font-weight:
|
||||||
* white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius:
|
* bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size:
|
||||||
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
|
* 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> The
|
||||||
* sans-serif">PRO</a> The Media Node where to host the session. The default
|
* Media Node where to host the session. The default option if this property is
|
||||||
* option if this property is not defined is the less loaded Media Node at the
|
* not defined is the less loaded Media Node at the moment the first user joins
|
||||||
* moment the first user joins the session.
|
* the session.
|
||||||
*/
|
*/
|
||||||
public String mediaNode() {
|
public String mediaNode() {
|
||||||
return this.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.
|
* 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 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
|
* This is a server-only property, and as such, it doesn't need to be
|
||||||
* over the wire between server and client. Thus it doesn't get serialized in
|
* transmitted over the wire between server and client. Thus it doesn't get
|
||||||
* the `toJson()` method.
|
* serialized in the `toJson()` method.
|
||||||
*
|
*
|
||||||
* If more server-only properties start to appear here, maybe a good idea
|
* If more server-only properties start to appear here, maybe a good idea would
|
||||||
* would be to refactor them all into a server-specific Properties class.
|
* be to refactor them all into a server-specific Properties class.
|
||||||
*
|
*
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -377,8 +380,11 @@ public class SessionProperties {
|
||||||
}
|
}
|
||||||
if (defaultRecordingPropertiesJson != null) {
|
if (defaultRecordingPropertiesJson != null) {
|
||||||
try {
|
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);
|
builder = builder.defaultRecordingProperties(defaultRecordingProperties);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
|
@ -408,8 +414,17 @@ public class SessionProperties {
|
||||||
JsonObject mediaNodeJson;
|
JsonObject mediaNodeJson;
|
||||||
try {
|
try {
|
||||||
mediaNodeJson = JsonParser.parseString(params.get("mediaNode").toString()).getAsJsonObject();
|
mediaNodeJson = JsonParser.parseString(params.get("mediaNode").toString()).getAsJsonObject();
|
||||||
} catch (Exception e) {
|
} catch (JsonSyntaxException e) {
|
||||||
throw new IllegalArgumentException("Error in parameter 'mediaNode'. It is not a valid JSON object");
|
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")) {
|
if (!mediaNodeJson.has("id")) {
|
||||||
throw new IllegalArgumentException("Error in parameter 'mediaNode'. Property 'id' not found");
|
throw new IllegalArgumentException("Error in parameter 'mediaNode'. Property 'id' not found");
|
||||||
|
|
|
@ -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_\\-~]+");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,19 +1,24 @@
|
||||||
package io.openvidu.server.test.unit;
|
package io.openvidu.java.client.test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
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
|
public static Test suite() {
|
||||||
void customSessionIdFormatTest() {
|
return new TestSuite(FormatCheckerTest.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCustomSessionIdFormat() {
|
||||||
|
|
||||||
List<String> invalidCustomSessionIds = Arrays.asList("", "session#", "session!", "session*", "'session",
|
List<String> invalidCustomSessionIds = Arrays.asList("", "session#", "session!", "session*", "'session",
|
||||||
"\"session", "sess(ion", "sess_ion)", "session: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",
|
"0session10", "-session", "session-", "-session-", "_session", "session_", "_session_", "_-session",
|
||||||
"session-_", "123_session-1");
|
"session-_", "123_session-1");
|
||||||
|
|
||||||
FormatChecker formatChecker = new FormatChecker();
|
|
||||||
for (String id : invalidCustomSessionIds)
|
for (String id : invalidCustomSessionIds)
|
||||||
assertFalse(formatChecker.isValidCustomSessionId(id));
|
assertFalse(FormatChecker.isValidCustomSessionId(id));
|
||||||
for (String id : validCustomSessionIds)
|
for (String id : validCustomSessionIds)
|
||||||
assertTrue(formatChecker.isValidCustomSessionId(id));
|
assertTrue(FormatChecker.isValidCustomSessionId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
public void testAcceptableRecordingResolution() {
|
||||||
void acceptableRecordingResolutionTest() {
|
|
||||||
|
|
||||||
List<String> invalidResolutions = Arrays.asList("", "a", "123", "true", "AXB", "AxB", "12x", "x12", "0920x1080",
|
List<String> invalidResolutions = Arrays.asList("", "a", "123", "true", "AXB", "AxB", "12x", "x12", "0920x1080",
|
||||||
"1080x0720", "720x2000", "99x720", "1920X1080");
|
"1080x0720", "720x2000", "99x720", "1920X1080");
|
||||||
|
|
||||||
List<String> validResolutions = Arrays.asList("1920x1080", "1280x720", "100x1999");
|
List<String> validResolutions = Arrays.asList("1920x1080", "1280x720", "100x1999");
|
||||||
|
|
||||||
FormatChecker formatChecker = new FormatChecker();
|
|
||||||
for (String resolution : invalidResolutions)
|
for (String resolution : invalidResolutions)
|
||||||
assertFalse(formatChecker.isAcceptableRecordingResolution(resolution));
|
assertFalse(FormatChecker.isAcceptableRecordingResolution(resolution));
|
||||||
for (String resolution : validResolutions)
|
for (String resolution : validResolutions)
|
||||||
assertTrue(formatChecker.isAcceptableRecordingResolution(resolution));
|
assertTrue(FormatChecker.isAcceptableRecordingResolution(resolution));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
public void testAcceptableRecordingFrameRate() {
|
||||||
void acceptableRecordingFrameRateTest() {
|
|
||||||
|
|
||||||
List<Integer> invalidFrameRates = Arrays.asList(-1, 0, 121, 9999);
|
List<Integer> invalidFrameRates = Arrays.asList(-1, 0, 121, 9999);
|
||||||
|
|
||||||
List<Integer> validFramerates = Arrays.asList(1, 2, 30, 60, 119, 120);
|
List<Integer> validFramerates = Arrays.asList(1, 2, 30, 60, 119, 120);
|
||||||
|
|
||||||
FormatChecker formatChecker = new FormatChecker();
|
|
||||||
for (int framerate : invalidFrameRates)
|
for (int framerate : invalidFrameRates)
|
||||||
assertFalse(formatChecker.isAcceptableRecordingFrameRate(framerate));
|
assertFalse(FormatChecker.isAcceptableRecordingFrameRate(framerate));
|
||||||
for (int framerate : validFramerates)
|
for (int framerate : validFramerates)
|
||||||
assertTrue(formatChecker.isAcceptableRecordingFrameRate(framerate));
|
assertTrue(FormatChecker.isAcceptableRecordingFrameRate(framerate));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,9 +21,9 @@ import com.google.gson.JsonObject;
|
||||||
|
|
||||||
import io.openvidu.java.client.Recording.Status;
|
import io.openvidu.java.client.Recording.Status;
|
||||||
import io.openvidu.java.client.RecordingLayout;
|
import io.openvidu.java.client.RecordingLayout;
|
||||||
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.recording.Recording;
|
import io.openvidu.server.recording.Recording;
|
||||||
import io.openvidu.server.utils.RecordingUtils;
|
|
||||||
|
|
||||||
public class CDREventRecordingStatusChanged extends CDREventEnd {
|
public class CDREventRecordingStatusChanged extends CDREventEnd {
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ public class CDREventRecordingStatusChanged extends CDREventEnd {
|
||||||
json.addProperty("id", this.recording.getId());
|
json.addProperty("id", this.recording.getId());
|
||||||
json.addProperty("name", this.recording.getName());
|
json.addProperty("name", this.recording.getName());
|
||||||
json.addProperty("outputMode", this.recording.getOutputMode().name());
|
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("resolution", this.recording.getResolution());
|
||||||
json.addProperty("frameRate", this.recording.getFrameRate());
|
json.addProperty("frameRate", this.recording.getFrameRate());
|
||||||
json.addProperty("recordingLayout", this.recording.getRecordingLayout().name());
|
json.addProperty("recordingLayout", this.recording.getRecordingLayout().name());
|
||||||
|
|
|
@ -53,13 +53,13 @@ import io.openvidu.java.client.KurentoOptions;
|
||||||
import io.openvidu.java.client.OpenViduRole;
|
import io.openvidu.java.client.OpenViduRole;
|
||||||
import io.openvidu.java.client.Recording;
|
import io.openvidu.java.client.Recording;
|
||||||
import io.openvidu.java.client.SessionProperties;
|
import io.openvidu.java.client.SessionProperties;
|
||||||
|
import io.openvidu.java.client.utils.FormatChecker;
|
||||||
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
|
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.coturn.CoturnCredentialsService;
|
import io.openvidu.server.coturn.CoturnCredentialsService;
|
||||||
import io.openvidu.server.kurento.endpoint.EndpointType;
|
import io.openvidu.server.kurento.endpoint.EndpointType;
|
||||||
import io.openvidu.server.kurento.kms.Kms;
|
import io.openvidu.server.kurento.kms.Kms;
|
||||||
import io.openvidu.server.recording.service.RecordingManager;
|
import io.openvidu.server.recording.service.RecordingManager;
|
||||||
import io.openvidu.server.utils.FormatChecker;
|
|
||||||
import io.openvidu.server.utils.GeoLocation;
|
import io.openvidu.server.utils.GeoLocation;
|
||||||
import io.openvidu.server.utils.GeoLocationByIp;
|
import io.openvidu.server.utils.GeoLocationByIp;
|
||||||
import io.openvidu.server.utils.MediaNodeManager;
|
import io.openvidu.server.utils.MediaNodeManager;
|
||||||
|
@ -93,8 +93,6 @@ public abstract class SessionManager {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected GeoLocationByIp geoLocationByIp;
|
protected GeoLocationByIp geoLocationByIp;
|
||||||
|
|
||||||
public FormatChecker formatChecker = new FormatChecker();
|
|
||||||
|
|
||||||
private UpdatableTimerTask sessionGarbageCollectorTimer;
|
private UpdatableTimerTask sessionGarbageCollectorTimer;
|
||||||
|
|
||||||
final protected ConcurrentMap<String, Session> sessions = new ConcurrentHashMap<>();
|
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,
|
public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record,
|
||||||
KurentoOptions kurentoOptions, List<IceServerProperties> customIceServers) throws Exception {
|
KurentoOptions kurentoOptions, List<IceServerProperties> customIceServers) throws Exception {
|
||||||
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
|
if (!FormatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
|
||||||
log.error("Data invalid format");
|
log.error("Data invalid format");
|
||||||
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
|
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import com.google.gson.JsonObject;
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.server.core.MediaServer;
|
import io.openvidu.server.core.MediaServer;
|
||||||
import io.openvidu.server.kurento.core.KurentoSession;
|
import io.openvidu.server.kurento.core.KurentoSession;
|
||||||
import io.openvidu.server.utils.RecordingUtils;
|
|
||||||
import io.openvidu.server.utils.UpdatableTimerTask;
|
import io.openvidu.server.utils.UpdatableTimerTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,14 +191,14 @@ public class Kms {
|
||||||
public synchronized void incrementActiveRecordings(String sessionId, String recordingId,
|
public synchronized void incrementActiveRecordings(String sessionId, String recordingId,
|
||||||
RecordingProperties properties) {
|
RecordingProperties properties) {
|
||||||
this.activeRecordings.put(recordingId, sessionId);
|
this.activeRecordings.put(recordingId, sessionId);
|
||||||
if (RecordingUtils.IS_COMPOSED(properties.outputMode())) {
|
if (RecordingProperties.IS_COMPOSED(properties.outputMode())) {
|
||||||
this.activeComposedRecordings.incrementAndGet();
|
this.activeComposedRecordings.incrementAndGet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void decrementActiveRecordings(String recordingId, RecordingProperties properties) {
|
public synchronized void decrementActiveRecordings(String recordingId, RecordingProperties properties) {
|
||||||
this.activeRecordings.remove(recordingId);
|
this.activeRecordings.remove(recordingId);
|
||||||
if (RecordingUtils.IS_COMPOSED(properties.outputMode())) {
|
if (RecordingProperties.IS_COMPOSED(properties.outputMode())) {
|
||||||
this.activeComposedRecordings.decrementAndGet();
|
this.activeComposedRecordings.decrementAndGet();
|
||||||
}
|
}
|
||||||
kmsManager.getMediaNodeManager().dropIdleMediaNode(this.id);
|
kmsManager.getMediaNodeManager().dropIdleMediaNode(this.id);
|
||||||
|
|
|
@ -23,7 +23,6 @@ import com.google.gson.JsonObject;
|
||||||
|
|
||||||
import io.openvidu.java.client.RecordingLayout;
|
import io.openvidu.java.client.RecordingLayout;
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.server.utils.RecordingUtils;
|
|
||||||
|
|
||||||
public class Recording {
|
public class Recording {
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@ public class Recording {
|
||||||
.valueOf(json.get("outputMode").getAsString());
|
.valueOf(json.get("outputMode").getAsString());
|
||||||
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
|
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
|
||||||
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
|
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
|
||||||
if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) {
|
if (RecordingProperties.IS_COMPOSED(outputMode) && hasVideo) {
|
||||||
if (json.has("resolution")) {
|
if (json.has("resolution")) {
|
||||||
builder.resolution(json.get("resolution").getAsString());
|
builder.resolution(json.get("resolution").getAsString());
|
||||||
}
|
}
|
||||||
|
@ -193,7 +192,8 @@ public class Recording {
|
||||||
json.addProperty("object", "recording");
|
json.addProperty("object", "recording");
|
||||||
json.addProperty("name", this.recordingProperties.name());
|
json.addProperty("name", this.recordingProperties.name());
|
||||||
json.addProperty("outputMode", this.getOutputMode().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("resolution", this.recordingProperties.resolution());
|
||||||
json.addProperty("frameRate", this.recordingProperties.frameRate());
|
json.addProperty("frameRate", this.recordingProperties.frameRate());
|
||||||
json.addProperty("recordingLayout", this.recordingProperties.recordingLayout().name());
|
json.addProperty("recordingLayout", this.recordingProperties.recordingLayout().name());
|
||||||
|
|
|
@ -80,7 +80,6 @@ import io.openvidu.server.utils.DockerManager;
|
||||||
import io.openvidu.server.utils.JsonUtils;
|
import io.openvidu.server.utils.JsonUtils;
|
||||||
import io.openvidu.server.utils.LocalCustomFileManager;
|
import io.openvidu.server.utils.LocalCustomFileManager;
|
||||||
import io.openvidu.server.utils.LocalDockerManager;
|
import io.openvidu.server.utils.LocalDockerManager;
|
||||||
import io.openvidu.server.utils.RecordingUtils;
|
|
||||||
import io.openvidu.server.utils.RemoteOperationUtils;
|
import io.openvidu.server.utils.RemoteOperationUtils;
|
||||||
|
|
||||||
public class RecordingManager {
|
public class RecordingManager {
|
||||||
|
@ -443,7 +442,7 @@ public class RecordingManager {
|
||||||
|
|
||||||
this.singleStreamRecordingService.startRecorderEndpointForPublisherEndpoint(recording.getId(), profile,
|
this.singleStreamRecordingService.startRecorderEndpointForPublisherEndpoint(recording.getId(), profile,
|
||||||
participant, new CountDownLatch(1));
|
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
|
// Connect this stream to existing Composite recorder
|
||||||
log.info("Joining PublisherEndpoint to existing Composite in session {} for new stream of participant {}",
|
log.info("Joining PublisherEndpoint to existing Composite in session {} for new stream of participant {}",
|
||||||
session.getSessionId(), participant.getParticipantPublicId());
|
session.getSessionId(), participant.getParticipantPublicId());
|
||||||
|
@ -479,7 +478,7 @@ public class RecordingManager {
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
log.error("Exception while waiting for state change", 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
|
// Disconnect this stream from existing Composite recorder
|
||||||
log.info("Removing PublisherEndpoint from Composite in session {} for stream of participant {}",
|
log.info("Removing PublisherEndpoint from Composite in session {} for stream of participant {}",
|
||||||
session.getSessionId(), streamId);
|
session.getSessionId(), streamId);
|
||||||
|
|
|
@ -38,7 +38,6 @@ import io.openvidu.server.recording.RecordingDownloader;
|
||||||
import io.openvidu.server.recording.RecordingUploader;
|
import io.openvidu.server.recording.RecordingUploader;
|
||||||
import io.openvidu.server.utils.CommandExecutor;
|
import io.openvidu.server.utils.CommandExecutor;
|
||||||
import io.openvidu.server.utils.CustomFileManager;
|
import io.openvidu.server.utils.CustomFileManager;
|
||||||
import io.openvidu.server.utils.RecordingUtils;
|
|
||||||
|
|
||||||
public abstract class RecordingService {
|
public abstract class RecordingService {
|
||||||
|
|
||||||
|
@ -160,7 +159,7 @@ public abstract class RecordingService {
|
||||||
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(recordingId)
|
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(recordingId)
|
||||||
.outputMode(properties.outputMode()).hasAudio(properties.hasAudio()).hasVideo(properties.hasVideo())
|
.outputMode(properties.outputMode()).hasAudio(properties.hasAudio()).hasVideo(properties.hasVideo())
|
||||||
.mediaNode(properties.mediaNode());
|
.mediaNode(properties.mediaNode());
|
||||||
if (RecordingUtils.IS_COMPOSED(properties.outputMode()) && properties.hasVideo()) {
|
if (RecordingProperties.IS_COMPOSED(properties.outputMode()) && properties.hasVideo()) {
|
||||||
builder.resolution(properties.resolution());
|
builder.resolution(properties.resolution());
|
||||||
builder.frameRate(properties.frameRate());
|
builder.frameRate(properties.frameRate());
|
||||||
builder.recordingLayout(properties.recordingLayout());
|
builder.recordingLayout(properties.recordingLayout());
|
||||||
|
|
|
@ -56,7 +56,6 @@ import io.openvidu.java.client.ConnectionType;
|
||||||
import io.openvidu.java.client.IceServerProperties;
|
import io.openvidu.java.client.IceServerProperties;
|
||||||
import io.openvidu.java.client.MediaMode;
|
import io.openvidu.java.client.MediaMode;
|
||||||
import io.openvidu.java.client.Recording.OutputMode;
|
import io.openvidu.java.client.Recording.OutputMode;
|
||||||
import io.openvidu.java.client.RecordingLayout;
|
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.java.client.SessionProperties;
|
import io.openvidu.java.client.SessionProperties;
|
||||||
import io.openvidu.java.client.VideoCodec;
|
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.kurento.core.KurentoMediaOptions;
|
||||||
import io.openvidu.server.recording.Recording;
|
import io.openvidu.server.recording.Recording;
|
||||||
import io.openvidu.server.recording.service.RecordingManager;
|
import io.openvidu.server.recording.service.RecordingManager;
|
||||||
import io.openvidu.server.utils.RecordingUtils;
|
|
||||||
import io.openvidu.server.utils.RestUtils;
|
import io.openvidu.server.utils.RestUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -427,9 +425,9 @@ public class SessionRestController {
|
||||||
RecordingProperties recordingProperties;
|
RecordingProperties recordingProperties;
|
||||||
try {
|
try {
|
||||||
recordingProperties = getRecordingPropertiesFromParams(params, session).build();
|
recordingProperties = getRecordingPropertiesFromParams(params, session).build();
|
||||||
} catch (RuntimeException e) {
|
} catch (IllegalStateException e) {
|
||||||
return this.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.UNPROCESSABLE_ENTITY);
|
return this.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
} catch (Exception e) {
|
} catch (RuntimeException e) {
|
||||||
return this.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.BAD_REQUEST);
|
return this.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -853,208 +851,10 @@ public class SessionRestController {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected RecordingProperties.Builder getRecordingPropertiesFromParams(Map<?, ?> params, Session session)
|
protected RecordingProperties.Builder getRecordingPropertiesFromParams(Map<String, ?> params, Session session)
|
||||||
throws Exception {
|
throws RuntimeException {
|
||||||
|
RecordingProperties.Builder builder = RecordingProperties.fromJson(params,
|
||||||
// Final properties being used
|
session.getSessionProperties().defaultRecordingProperties());
|
||||||
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);
|
|
||||||
}
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import io.openvidu.client.OpenViduException;
|
||||||
import io.openvidu.client.OpenViduException.Code;
|
import io.openvidu.client.OpenViduException.Code;
|
||||||
import io.openvidu.client.internal.ProtocolElements;
|
import io.openvidu.client.internal.ProtocolElements;
|
||||||
import io.openvidu.java.client.ConnectionProperties;
|
import io.openvidu.java.client.ConnectionProperties;
|
||||||
|
import io.openvidu.java.client.utils.FormatChecker;
|
||||||
import io.openvidu.server.config.OpenviduBuildInfo;
|
import io.openvidu.server.config.OpenviduBuildInfo;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
|
@ -277,7 +278,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
if (tokenObj != null) {
|
if (tokenObj != null) {
|
||||||
|
|
||||||
String clientMetadata = getStringParam(request, ProtocolElements.JOINROOM_METADATA_PARAM);
|
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
|
// While closing a session users can't join
|
||||||
if (session.closingLock.readLock().tryLock()) {
|
if (session.closingLock.readLock().tryLock()) {
|
||||||
|
|
|
@ -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_-]+");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,15 +1,10 @@
|
||||||
package io.openvidu.server.utils;
|
package io.openvidu.server.utils;
|
||||||
|
|
||||||
import io.openvidu.java.client.Recording.OutputMode;
|
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.server.core.Session;
|
import io.openvidu.server.core.Session;
|
||||||
|
|
||||||
public final class RecordingUtils {
|
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) {
|
public final static RecordingProperties RECORDING_PROPERTIES_WITH_MEDIA_NODE(Session session) {
|
||||||
RecordingProperties recordingProperties = session.getSessionProperties().defaultRecordingProperties();
|
RecordingProperties recordingProperties = session.getSessionProperties().defaultRecordingProperties();
|
||||||
if (recordingProperties.mediaNode() == null) {
|
if (recordingProperties.mediaNode() == null) {
|
||||||
|
|
|
@ -3226,6 +3226,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
|
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
|
||||||
body = "{'session':'CUSTOM_SESSION_ID','name':999}";
|
body = "{'session':'CUSTOM_SESSION_ID','name':999}";
|
||||||
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
|
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'}";
|
body = "{'session':'CUSTOM_SESSION_ID','name':'NAME','outputMode':'NOT_EXISTS'}";
|
||||||
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
|
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'}";
|
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);
|
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", body, HttpStatus.SC_BAD_REQUEST);
|
||||||
|
|
||||||
// 422
|
// 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);
|
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'}";
|
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);
|
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();
|
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
String rareCharsName = "öæééEstoSi`+´çḈ€$";
|
String recordingName = "1234abcABC_-~";
|
||||||
user.getDriver().findElement(By.id("recording-name-field")).sendKeys(rareCharsName);
|
user.getDriver().findElement(By.id("recording-name-field")).sendKeys(recordingName);
|
||||||
user.getDriver().findElement(By.id("recording-mode-select")).click();
|
user.getDriver().findElement(By.id("recording-mode-select")).click();
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
user.getDriver().findElement(By.id("option-ALWAYS")).click();
|
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",
|
Assert.assertEquals("Wrong recording startTime/timestamp in webhook event",
|
||||||
event.get("startTime").getAsLong(), event.get("timestamp").getAsLong());
|
event.get("startTime").getAsLong(), event.get("timestamp").getAsLong());
|
||||||
Assert.assertNull("Wrong recording reason in webhook event (should be null)", event.get("reason"));
|
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());
|
event.get("name").getAsString());
|
||||||
|
|
||||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||||
|
|
Loading…
Reference in New Issue