openvidu-server: bug fixes for automatic stop of recordings

pull/203/head
pabloFuente 2019-01-29 16:41:36 +01:00
parent 9168de7b33
commit 2b68b3d6de
5 changed files with 66 additions and 68 deletions

View File

@ -176,11 +176,9 @@ public class KurentoSessionManager extends SessionManager {
if (remainingParticipants.isEmpty()) { if (remainingParticipants.isEmpty()) {
if (openviduConfig.isRecordingModuleEnabled() if (openviduConfig.isRecordingModuleEnabled()
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) && MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
&& (this.recordingManager.sessionIsBeingRecordedIndividual(sessionId) && (this.recordingManager.sessionIsBeingRecorded(sessionId))) {
|| (this.recordingManager.sessionIsBeingRecordedComposed(sessionId) // Start countdown to stop recording. Will be aborted if a Publisher starts
&& this.recordingManager.sessionIsBeingRecordedOnlyAudio(sessionId)))) { // before timeout
// Start countdown to stop recording if INDIVIDUAL mode or COMPOSED audio-only
// (will be aborted if a Publisher starts before timeout)
log.info( log.info(
"Last participant left. Starting {} seconds countdown for stopping recording of session {}", "Last participant left. Starting {} seconds countdown for stopping recording of session {}",
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId); this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
@ -192,25 +190,13 @@ public class KurentoSessionManager extends SessionManager {
} }
} else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled() } else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled()
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) && MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
&& this.recordingManager.sessionIsBeingRecordedComposed(sessionId) && this.recordingManager.sessionIsBeingRecorded(sessionId)
&& !this.recordingManager.sessionIsBeingRecordedOnlyAudio(sessionId)
&& ProtocolElements.RECORDER_PARTICIPANT_PUBLICID && ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
.equals(remainingParticipants.iterator().next().getParticipantPublicId())) { .equals(remainingParticipants.iterator().next().getParticipantPublicId())) {
if (RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())) { // Start countdown
// Immediately stop recording when last real participant left if log.info("Last participant left. Starting {} seconds countdown for stopping recording of session {}",
// RecordingMode.ALWAYS this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
log.info("Last participant left. Stopping recording for session {}", sessionId); recordingManager.initAutomaticRecordingStopThread(session);
recordingManager.stopRecording(session, null, reason);
evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID),
null, null, "EVICT_RECORDER");
} else if (RecordingMode.MANUAL.equals(session.getSessionProperties().recordingMode())) {
// Start countdown to stop recording if RecordingMode.MANUAL (will be aborted if
// a Publisher starts before timeout)
log.info(
"Last participant left. Starting {} seconds countdown for stopping recording of session {}",
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
recordingManager.initAutomaticRecordingStopThread(session);
}
} }
} }
@ -300,11 +286,11 @@ public class KurentoSessionManager extends SessionManager {
&& session.getActivePublishers() == 0) { && session.getActivePublishers() == 0) {
if (RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode()) if (RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())
&& !recordingManager.sessionIsBeingRecorded(session.getSessionId())) { && !recordingManager.sessionIsBeingRecorded(session.getSessionId())) {
// Insecure session recording // Start automatic recording for sessions configured with RecordingMode.ALWAYS
new Thread(() -> { new Thread(() -> {
recordingManager.startRecording(session, recordingManager.startRecording(session,
new RecordingProperties.Builder().name("") new RecordingProperties.Builder().name("")
.outputMode(io.openvidu.java.client.Recording.OutputMode.COMPOSED) .outputMode(session.getSessionProperties().defaultOutputMode())
.recordingLayout(session.getSessionProperties().defaultRecordingLayout()) .recordingLayout(session.getSessionProperties().defaultRecordingLayout())
.customLayout(session.getSessionProperties().defaultCustomLayout()).build()); .customLayout(session.getSessionProperties().defaultCustomLayout()).build());
}).start(); }).start();
@ -313,7 +299,7 @@ public class KurentoSessionManager extends SessionManager {
// Abort automatic recording stop (user published before timeout) // Abort automatic recording stop (user published before timeout)
log.info("Participant {} published before timeout finished. Aborting automatic recording stop", log.info("Participant {} published before timeout finished. Aborting automatic recording stop",
participant.getParticipantPublicId()); participant.getParticipantPublicId());
boolean stopAborted = recordingManager.abortAutomaticRecordingStopThread(session.getSessionId()); boolean stopAborted = recordingManager.abortAutomaticRecordingStopThread(session);
if (stopAborted) { if (stopAborted) {
log.info("Automatic recording stopped succesfully aborted"); log.info("Automatic recording stopped succesfully aborted");
} else { } else {

View File

@ -200,7 +200,7 @@ public class RecordingManager {
recording = this.singleStreamRecordingService.stopRecording(session, recording, reason); recording = this.singleStreamRecordingService.stopRecording(session, recording, reason);
break; break;
} }
this.abortAutomaticRecordingStopThread(session.getSessionId()); this.abortAutomaticRecordingStopThread(session);
return recording; return recording;
} }
@ -260,21 +260,6 @@ public class RecordingManager {
return (this.sessionsRecordings.get(sessionId) != null); return (this.sessionsRecordings.get(sessionId) != null);
} }
public boolean sessionIsBeingRecordedIndividual(String sessionId) {
Recording rec = this.sessionsRecordings.get(sessionId);
return (rec != null && io.openvidu.java.client.Recording.OutputMode.INDIVIDUAL.equals(rec.getOutputMode()));
}
public boolean sessionIsBeingRecordedComposed(String sessionId) {
Recording rec = this.sessionsRecordings.get(sessionId);
return (rec != null && io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(rec.getOutputMode()));
}
public boolean sessionIsBeingRecordedOnlyAudio(String sessionId) {
Recording rec = this.sessionsRecordings.get(sessionId);
return (rec != null && !rec.hasVideo());
}
public Recording getStartedRecording(String recordingId) { public Recording getStartedRecording(String recordingId) {
return this.startedRecordings.get(recordingId); return this.startedRecordings.get(recordingId);
} }
@ -363,26 +348,46 @@ public class RecordingManager {
log.info("Stopping recording {} after {} seconds wait (no publisher published before timeout)", recordingId, log.info("Stopping recording {} after {} seconds wait (no publisher published before timeout)", recordingId,
this.openviduConfig.getOpenviduRecordingAutostopTimeout()); this.openviduConfig.getOpenviduRecordingAutostopTimeout());
this.stopRecording(null, recordingId, "automaticStop"); if (this.automaticRecordingStopThreads.remove(session.getSessionId()) != null) {
this.automaticRecordingStopThreads.remove(session.getSessionId()); if (session.getParticipants().size() == 0 || (session.getParticipants().size() == 1
&& session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID) != null)) {
if (session.getParticipants().size() == 0 || (session.getParticipants().size() == 1 // Close session if there are no participants connected (except for RECORDER).
&& session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID) != null)) { // This code won't be executed only when some user reconnects to the session
// Close session if there are no participants connected (except for RECORDER). // but never publishing (publishers automatically abort this thread)
// This code won't be executed only when some user reconnects to the session log.info("Closing session {} after automatic stop of recording {}", session.getSessionId(),
// but never publishing (publishers automatically abort this thread) recordingId);
sessionManager.closeSessionAndEmptyCollections(session, "automaticStop"); sessionManager.closeSessionAndEmptyCollections(session, "automaticStop");
sessionManager.showTokens(); sessionManager.showTokens();
} else {
this.stopRecording(null, recordingId, "automaticStop");
}
} else {
// This code is reachable if there already was an automatic stop of a recording
// caused by not user publishing within timeout after recording started, and a
// new automatic stop thread was started by last user leaving the session
log.warn("Recording {} was already automatically stopped by a previous thread", recordingId);
} }
}, this.openviduConfig.getOpenviduRecordingAutostopTimeout(), TimeUnit.SECONDS); }, this.openviduConfig.getOpenviduRecordingAutostopTimeout(), TimeUnit.SECONDS);
this.automaticRecordingStopThreads.putIfAbsent(session.getSessionId(), future); this.automaticRecordingStopThreads.putIfAbsent(session.getSessionId(), future);
} }
public boolean abortAutomaticRecordingStopThread(String sessionId) { public boolean abortAutomaticRecordingStopThread(Session session) {
ScheduledFuture<?> future = this.automaticRecordingStopThreads.remove(sessionId); ScheduledFuture<?> future = this.automaticRecordingStopThreads.remove(session.getSessionId());
if (future != null) { if (future != null) {
return future.cancel(false); boolean cancelled = future.cancel(false);
if (session.getParticipants().size() == 0 || (session.getParticipants().size() == 1
&& session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID) != null)) {
// Close session if there are no participants connected (except for RECORDER).
// This code will only be executed if recording is manually stopped during the
// automatic stop timeout, so the session must be also closed
log.info(
"Ongoing recording of session {} was explicetly stopped within timeout for automatic recording stop. Closing session",
session.getSessionId());
sessionManager.closeSessionAndEmptyCollections(session, "automaticStop");
sessionManager.showTokens();
}
return cancelled;
} else { } else {
return true; return true;
} }

View File

@ -54,6 +54,17 @@ public abstract class RecordingService {
* store Recording entity) * store Recording entity)
*/ */
protected void generateRecordingMetadataFile(Recording recording) { protected void generateRecordingMetadataFile(Recording recording) {
String folder = this.openviduConfig.getOpenViduRecordingPath() + recording.getId();
boolean newFolderCreated = this.fileWriter.createFolderIfNotExists(folder);
if (newFolderCreated) {
log.info(
"New folder {} created. This means the recording started for a session with no publishers or no media type compatible publishers",
folder);
} else {
log.info("Folder {} already existed. Some publisher is already being recorded", folder);
}
String filePath = this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + "/" String filePath = this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + "/"
+ RecordingManager.RECORDING_ENTITY_FILE + recording.getId(); + RecordingManager.RECORDING_ENTITY_FILE + recording.getId();
String text = recording.toJson().toString(); String text = recording.toJson().toString();

View File

@ -58,7 +58,6 @@ import io.openvidu.server.kurento.core.KurentoParticipant;
import io.openvidu.server.kurento.endpoint.PublisherEndpoint; import io.openvidu.server.kurento.endpoint.PublisherEndpoint;
import io.openvidu.server.recording.RecorderEndpointWrapper; import io.openvidu.server.recording.RecorderEndpointWrapper;
import io.openvidu.server.recording.Recording; import io.openvidu.server.recording.Recording;
import io.openvidu.server.utils.CommandExecutor;
public class SingleStreamRecordingService extends RecordingService { public class SingleStreamRecordingService extends RecordingService {
@ -90,7 +89,6 @@ public class SingleStreamRecordingService extends RecordingService {
final int activePublishers = session.getActivePublishers(); final int activePublishers = session.getActivePublishers();
final CountDownLatch recordingStartedCountdown = new CountDownLatch(activePublishers); final CountDownLatch recordingStartedCountdown = new CountDownLatch(activePublishers);
int incompatibleMediaTypePublishers = 0;
for (Participant p : session.getParticipants()) { for (Participant p : session.getParticipants()) {
if (p.isStreaming()) { if (p.isStreaming()) {
@ -102,7 +100,6 @@ public class SingleStreamRecordingService extends RecordingService {
log.error( log.error(
"Cannot start single stream recorder for stream {} in session {}: {}. Skipping to next stream being published", "Cannot start single stream recorder for stream {} in session {}: {}. Skipping to next stream being published",
p.getPublisherStreamId(), session.getSessionId(), e.getMessage()); p.getPublisherStreamId(), session.getSessionId(), e.getMessage());
incompatibleMediaTypePublishers++;
recordingStartedCountdown.countDown(); recordingStartedCountdown.countDown();
continue; continue;
} }
@ -121,13 +118,6 @@ public class SingleStreamRecordingService extends RecordingService {
log.error("Exception while waiting for state change", e); log.error("Exception while waiting for state change", e);
} }
if (activePublishers == 0 || incompatibleMediaTypePublishers == activePublishers) {
// Recording started for a session with some user connected but no publishers
// or with no publisher having a compatible media type stream with the recording
// configuration. Must create recording root folder for storing metadata archive
this.fileWriter.createFolder(this.openviduConfig.getOpenViduRecordingPath() + recording.getId());
}
this.generateRecordingMetadataFile(recording); this.generateRecordingMetadataFile(recording);
this.updateRecordingManagerCollections(session, recording); this.updateRecordingManagerCollections(session, recording);
this.sendRecordingStartedNotification(session, recording); this.sendRecordingStartedNotification(session, recording);

View File

@ -33,7 +33,7 @@ public class CustomFileWriter {
try { try {
this.writeAndCloseOnOutputStreamWriter(new FileOutputStream(filePath), text); this.writeAndCloseOnOutputStreamWriter(new FileOutputStream(filePath), text);
} catch (IOException e) { } catch (IOException e) {
log.error("Couldn't create file {}. Error: ", filePath, e.getMessage()); log.error("Couldn't create file {}. Error: {}", filePath, e.getMessage());
} }
} }
@ -41,12 +41,18 @@ public class CustomFileWriter {
try { try {
this.writeAndCloseOnOutputStreamWriter(new FileOutputStream(filePath, false), text); this.writeAndCloseOnOutputStreamWriter(new FileOutputStream(filePath, false), text);
} catch (IOException e) { } catch (IOException e) {
log.error("Couldn't overwrite file {}. Error: ", filePath, e.getMessage()); log.error("Couldn't overwrite file {}. Error: {}", filePath, e.getMessage());
} }
} }
public boolean createFolder(String path) { public boolean createFolderIfNotExists(String path) {
return new File(path).mkdir(); File folder = new File(path);
if (!folder.exists()) {
return folder.mkdir();
} else {
return false;
}
} }
private void writeAndCloseOnOutputStreamWriter(FileOutputStream fos, String text) throws IOException { private void writeAndCloseOnOutputStreamWriter(FileOutputStream fos, String text) throws IOException {