mirror of https://github.com/OpenVidu/openvidu.git
openvidu-server: bug fixes for automatic stop of recordings
parent
9168de7b33
commit
2b68b3d6de
|
@ -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,27 +190,15 @@ 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
|
|
||||||
log.info("Last participant left. Stopping recording for session {}", sessionId);
|
|
||||||
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);
|
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
|
||||||
recordingManager.initAutomaticRecordingStopThread(session);
|
recordingManager.initAutomaticRecordingStopThread(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Finally close websocket session if required
|
// Finally close websocket session if required
|
||||||
if (closeWebSocket) {
|
if (closeWebSocket) {
|
||||||
|
@ -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 {
|
||||||
|
|
|
@ -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
|
if (session.getParticipants().size() == 0 || (session.getParticipants().size() == 1
|
||||||
&& session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID) != null)) {
|
&& session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID) != null)) {
|
||||||
// Close session if there are no participants connected (except for RECORDER).
|
// Close session if there are no participants connected (except for RECORDER).
|
||||||
// This code won't be executed only when some user reconnects to the session
|
// This code won't be executed only when some user reconnects to the session
|
||||||
// but never publishing (publishers automatically abort this thread)
|
// but never publishing (publishers automatically abort this thread)
|
||||||
|
log.info("Closing session {} after automatic stop of recording {}", session.getSessionId(),
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue