New RecordingProperty ignoreFailedStreams

pull/630/head
pabloFuente 2021-05-11 12:28:41 +02:00
parent da003448ff
commit f1da724533
4 changed files with 80 additions and 13 deletions

View File

@ -35,6 +35,7 @@ public class RecordingProperties {
public static final String resolution = "1280x720";
public static final Integer frameRate = 25;
public static final Long shmSize = 536870912L;
public static final Boolean ignoreFailedStreams = false;
}
// For all
@ -49,6 +50,8 @@ public class RecordingProperties {
private Long shmSize;
// For COMPOSED/COMPOSED_QUICK_START + hasVideo + RecordingLayout.CUSTOM
private String customLayout;
// For INDIVIDUAL
private Boolean ignoreFailedStreams;
// For OpenVidu Pro
private String mediaNode;
@ -58,14 +61,15 @@ public class RecordingProperties {
public static class Builder {
private String name = "";
private Boolean hasAudio = true;
private Boolean hasVideo = true;
private Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
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 Boolean ignoreFailedStreams = DefaultValues.ignoreFailedStreams;
private String mediaNode;
public Builder() {
@ -81,6 +85,7 @@ public class RecordingProperties {
this.frameRate = props.frameRate();
this.shmSize = props.shmSize();
this.customLayout = props.customLayout();
this.ignoreFailedStreams = props.ignoreFailedStreams();
this.mediaNode = props.mediaNode();
}
@ -90,7 +95,7 @@ public class RecordingProperties {
public RecordingProperties build() {
return new RecordingProperties(this.name, this.hasAudio, this.hasVideo, this.outputMode,
this.recordingLayout, this.resolution, this.frameRate, this.shmSize, this.customLayout,
this.mediaNode);
this.ignoreFailedStreams, this.mediaNode);
}
/**
@ -206,6 +211,27 @@ public class RecordingProperties {
return this;
}
/**
* Call this method to specify whether to ignore failed streams or not when
* starting the recording. This property only applies to
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} recordings.
* For this type of recordings, when calling
* {@link io.openvidu.java.client.OpenVidu#startRecording} by default all the
* streams available at the moment the recording process starts must be healthy
* and properly sending media. If some stream that should be sending media is
* broken, then the recording process fails after a 10s timeout. In this way
* your application is notified that some stream is not being recorded, so it
* can retry the process again. But you can disable this rollback behavior and
* simply ignore any failed stream, which will be susceptible to be recorded in
* the future if media starts flowing as expected at any point. The downside of
* this behavior is that you will have no guarantee that all streams present at
* the beginning of a recording are actually being recorded.
*/
public RecordingProperties.Builder ignoreFailedStreams(boolean ignoreFailedStreams) {
this.ignoreFailedStreams = ignoreFailedStreams;
return this;
}
/**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank"
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
@ -213,8 +239,10 @@ public class RecordingProperties {
* 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 COMPOSED or COMPOSED_QUICK_START recordings with
* {@link RecordingProperties#hasVideo()} to true and is ignored for INDIVIDUAL
* 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}
* recordings and audio-only recordings, that are always hosted in the same
* Media Node hosting its Session
*/
@ -227,7 +255,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,
String mediaNode) {
Boolean ignoreFailedStreams, String mediaNode) {
this.name = name != null ? name : "";
this.hasAudio = hasAudio != null ? hasAudio : DefaultValues.hasAudio;
this.hasVideo = hasVideo != null ? hasVideo : DefaultValues.hasVideo;
@ -242,6 +270,9 @@ public class RecordingProperties {
this.customLayout = customLayout;
}
}
if (OutputMode.INDIVIDUAL.equals(this.outputMode)) {
this.ignoreFailedStreams = ignoreFailedStreams;
}
this.mediaNode = mediaNode;
}
@ -366,6 +397,29 @@ public class RecordingProperties {
return this.customLayout;
}
/**
* Defines whether to ignore failed streams or not when starting the recording.
* This property only applies to
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} recordings.
* For this type of recordings, when calling
* {@link io.openvidu.java.client.OpenVidu#startRecording} by default all the
* streams available at the moment the recording process starts must be healthy
* and properly sending media. If some stream that should be sending media is
* broken, then the recording process fails after a 10s timeout. In this way
* your application is notified that some stream is not being recorded, so it
* can retry the process again. But you can disable this rollback behavior and
* simply ignore any failed stream, which will be susceptible to be recorded in
* the future if media starts flowing as expected at any point. The downside of
* this behavior is that you will have no guarantee that all streams present at
* the beginning of a recording are actually being recorded.<br>
* <br>
*
* Default to false
*/
public Boolean ignoreFailedStreams() {
return this.ignoreFailedStreams;
}
/**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank"
* style="display: inline-block; background-color: rgb(0, 136, 170); color:
@ -401,6 +455,10 @@ public class RecordingProperties {
json.addProperty("customLayout", customLayout != null ? customLayout : "");
}
}
if (OutputMode.INDIVIDUAL.equals(outputMode)) {
json.addProperty("ignoreFailedStreams",
ignoreFailedStreams != null ? ignoreFailedStreams : DefaultValues.ignoreFailedStreams);
}
if (this.mediaNode != null) {
json.addProperty("mediaNode", mediaNode);
}
@ -452,6 +510,9 @@ public class RecordingProperties {
}
}
}
if (OutputMode.INDIVIDUAL.equals(outputModeAux)) {
builder.ignoreFailedStreams(json.get("ignoreFailedStreams").getAsBoolean());
}
if (json.has("mediaNode")) {
String mediaNodeId = null;
if (json.get("mediaNode").isJsonObject()) {

View File

@ -430,7 +430,6 @@ public class RecordingManager {
// Start new RecorderEndpoint for this stream
log.info("Starting new RecorderEndpoint in session {} for new stream of participant {}",
session.getSessionId(), participant.getParticipantPublicId());
final CountDownLatch startedCountDown = new CountDownLatch(1);
MediaProfileSpecType profile = null;
try {
@ -443,7 +442,7 @@ public class RecordingManager {
}
this.singleStreamRecordingService.startRecorderEndpointForPublisherEndpoint(recording.getId(), profile,
participant, startedCountDown);
participant, new CountDownLatch(1));
} else if (RecordingUtils.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 {}",

View File

@ -122,9 +122,16 @@ public class SingleStreamRecordingService extends RecordingService {
}
try {
if (!recordingStartedCountdown.await(10, TimeUnit.SECONDS)) {
log.error("Error waiting for some recorder endpoint to start in session {}", session.getSessionId());
throw this.failStartRecording(session, recording, "Couldn't initialize some RecorderEndpoint");
if (!properties.ignoreFailedStreams()) {
if (!recordingStartedCountdown.await(10, TimeUnit.SECONDS)) {
log.error("Error waiting for some recorder endpoint to start in session {}",
session.getSessionId());
throw this.failStartRecording(session, recording, "Couldn't initialize some RecorderEndpoint");
}
} else {
log.info(
"Ignoring failed streams in recording {}. Some streams may not be immediately or ever recorded",
recordingId);
}
} catch (InterruptedException e) {
recording.setStatus(io.openvidu.java.client.Recording.Status.failed);

View File

@ -46,7 +46,7 @@ import io.openvidu.test.browsers.utils.RecordingUtils;
public class AbstractOpenViduTestAppE2eTest {
final protected String DEFAULT_JSON_SESSION = "{'id':'STR','object':'session','sessionId':'STR','createdAt':0,'mediaMode':'STR','recordingMode':'STR','defaultRecordingProperties':{'hasVideo':true,'frameRate':25,'hasAudio':true,'shmSize':536870912,'name':'','outputMode':'COMPOSED','resolution':'1280x720','recordingLayout':'BEST_FIT'},'customSessionId':'STR','connections':{'numberOfElements':0,'content':[]},'recording':false,'forcedVideoCodec':'STR','allowTranscoding':false}";
final protected String DEFAULT_JSON_SESSION = "{'id':'STR','object':'session','sessionId':'STR','createdAt':0,'mediaMode':'STR','recordingMode':'STR','defaultRecordingProperties':{'hasVideo':true,'frameRate':25,'hasAudio':true,'shmSize':536870912,'name':'','outputMode':'COMPOSED','resolution':'1280x720','recordingLayout':'BEST_FIT','ignoreFailedStreams':false},'customSessionId':'STR','connections':{'numberOfElements':0,'content':[]},'recording':false,'forcedVideoCodec':'STR','allowTranscoding':false}";
final protected String DEFAULT_JSON_PENDING_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'pending','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':null,'location':null,'platform':null,'token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':null,'publishers':null,'subscribers':null}";
final protected String DEFAULT_JSON_ACTIVE_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','platform':'STR','token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':'STR','publishers':[],'subscribers':[]}";
final protected String DEFAULT_JSON_IPCAM_CONNECTION = "{'id':'STR','object':'connection','type':'IPCAM','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','platform':'IPCAM','token':null,'serverData':'STR','record':true,'role':null,'kurentoOptions':null,'rtspUri':'STR','adaptativeBitrate':true,'onlyPlayWithSubscribers':true,'networkCache':2000,'clientData':null,'publishers':[],'subscribers':[]}";