Merge branch 'master' of github.com:OpenVidu/openvidu into master

pull/546/head
cruizba 2020-09-28 10:07:21 +02:00
commit f886ac72db
8 changed files with 244 additions and 127 deletions

View File

@ -22,7 +22,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.kurento.client.GenericMediaEvent; import org.kurento.client.GenericMediaEvent;
@ -62,9 +61,7 @@ public class SessionEventsHandler {
@Autowired @Autowired
protected OpenviduConfig openviduConfig; protected OpenviduConfig openviduConfig;
Map<String, Recording> recordingsStarted = new ConcurrentHashMap<>(); private Map<String, Recording> recordingsToSendClientEvents = new ConcurrentHashMap<>();
ReentrantLock lock = new ReentrantLock();
public void onSessionCreated(Session session) { public void onSessionCreated(Session session) {
CDR.recordSessionCreated(session); CDR.recordSessionCreated(session);
@ -290,16 +287,10 @@ public class SessionEventsHandler {
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, result); rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, result);
if (ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) { if (ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(participant.getParticipantPublicId())) {
lock.lock(); recordingsToSendClientEvents.computeIfPresent(session.getSessionId(), (key, value) -> {
try { sendRecordingStartedNotification(session, value);
Recording recording = this.recordingsStarted.remove(session.getSessionId()); return null;
if (recording != null) { });
// RECORDER participant is now receiving video from the first publisher
this.sendRecordingStartedNotification(session, recording);
}
} finally {
lock.unlock();
}
} }
} }
@ -311,8 +302,9 @@ public class SessionEventsHandler {
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject()); rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject());
} }
public void onNetworkQualityChanged(Participant participant, JsonObject params ) { public void onNetworkQualityChanged(Participant participant, JsonObject params) {
rpcNotificationService.sendNotification(participant.getParticipantPrivateId(), ProtocolElements.NETWORKQUALITYCHANGED_METHOD, params); rpcNotificationService.sendNotification(participant.getParticipantPrivateId(),
ProtocolElements.NETWORKQUALITYCHANGED_METHOD, params);
} }
public void onSendMessage(Participant participant, JsonObject message, Set<Participant> participants, public void onSendMessage(Participant participant, JsonObject message, Set<Participant> participants,
@ -460,7 +452,7 @@ public class SessionEventsHandler {
public void sendRecordingStoppedNotification(Session session, Recording recording, EndReason reason) { public void sendRecordingStoppedNotification(Session session, Recording recording, EndReason reason) {
// Be sure to clean this map (this should return null) // Be sure to clean this map (this should return null)
this.recordingsStarted.remove(session.getSessionId()); recordingsToSendClientEvents.remove(session.getSessionId());
// Filter participants by roles according to "OPENVIDU_RECORDING_NOTIFICATION" // Filter participants by roles according to "OPENVIDU_RECORDING_NOTIFICATION"
Set<Participant> existingParticipants; Set<Participant> existingParticipants;
@ -570,8 +562,8 @@ public class SessionEventsHandler {
this.rpcNotificationService.closeRpcSession(participantPrivateId); this.rpcNotificationService.closeRpcSession(participantPrivateId);
} }
public void setRecordingStarted(String sessionId, Recording recording) { public void storeRecordingToSendClientEvent(Recording recording) {
this.recordingsStarted.put(sessionId, recording); recordingsToSendClientEvents.put(recording.getSessionId(), recording);
} }
private Set<Participant> filterParticipantsByRole(OpenViduRole[] roles, Set<Participant> participants) { private Set<Participant> filterParticipantsByRole(OpenViduRole[] roles, Set<Participant> participants) {

View File

@ -50,7 +50,7 @@ public class RecordingInfoUtils {
throw new OpenViduException(Code.RECORDING_FILE_EMPTY_ERROR, "The recording file is corrupted"); throw new OpenViduException(Code.RECORDING_FILE_EMPTY_ERROR, "The recording file is corrupted");
} }
if (this.json.size() == 0) { if (this.json.size() == 0) {
// Recording metadata from ffprobe is an emtpy JSON // Recording metadata from ffprobe is an empty JSON
throw new OpenViduException(Code.RECORDING_FILE_EMPTY_ERROR, "The recording file is empty"); throw new OpenViduException(Code.RECORDING_FILE_EMPTY_ERROR, "The recording file is empty");
} }

View File

@ -78,7 +78,7 @@ public class ComposedQuickStartRecordingService extends ComposedRecordingService
recordExecCommand += "export " + envs.get(i) + " "; recordExecCommand += "export " + envs.get(i) + " ";
} }
recordExecCommand += "&& ./composed_quick_start.sh --start-recording > /var/log/ffmpeg.log 2>&1 &"; recordExecCommand += "&& ./composed_quick_start.sh --start-recording > /var/log/ffmpeg.log 2>&1 &";
dockerManager.runCommandInContainer(containerId, recordExecCommand, 0); dockerManager.runCommandInContainer(containerId, recordExecCommand);
} catch (Exception e) { } catch (Exception e) {
this.cleanRecordingMaps(recording); this.cleanRecordingMaps(recording);
throw this.failStartRecording(session, recording, throw this.failStartRecording(session, recording,
@ -116,7 +116,7 @@ public class ComposedQuickStartRecordingService extends ComposedRecordingService
} }
try { try {
dockerManager.runCommandInContainer(containerId, "./composed_quick_start.sh --stop-recording", 10); dockerManager.runCommandInContainerSync(containerId, "./composed_quick_start.sh --stop-recording", 10);
} catch (InterruptedException e1) { } catch (InterruptedException e1) {
cleanRecordingMaps(recording); cleanRecordingMaps(recording);
log.error("Error stopping recording for session id: {}", session.getSessionId()); log.error("Error stopping recording for session id: {}", session.getSessionId());

View File

@ -393,10 +393,10 @@ public class ComposedRecordingService extends RecordingService {
return finalRecordingArray[0]; return finalRecordingArray[0];
} }
protected void stopAndRemoveRecordingContainer(Recording recording, String containerId, int secondsOfWait) { private void stopAndRemoveRecordingContainer(Recording recording, String containerId, int secondsOfWait) {
// Gracefully stop ffmpeg process // Gracefully stop ffmpeg process
try { try {
dockerManager.runCommandInContainer(containerId, "echo 'q' > stop", 0); dockerManager.runCommandInContainer(containerId, "echo 'q' > stop");
} catch (InterruptedException e1) { } catch (InterruptedException e1) {
e1.printStackTrace(); e1.printStackTrace();
} }
@ -440,21 +440,26 @@ public class ComposedRecordingService extends RecordingService {
} }
protected void waitForVideoFileNotEmpty(Recording recording) throws OpenViduException { protected void waitForVideoFileNotEmpty(Recording recording) throws OpenViduException {
boolean isPresent = false;
int i = 1; final String VIDEO_FILE = this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + "/"
int timeout = 150; // Wait for 150*150 = 22500 = 22.5 seconds + recording.getName() + ".mp4";
while (!isPresent && timeout <= 150) {
int SECONDS_MAX_WAIT = 15;
int MILLISECONDS_INTERVAL_WAIT = 100;
int LIMIT = SECONDS_MAX_WAIT * 1000 / MILLISECONDS_INTERVAL_WAIT;
int i = 0;
boolean arePresent = fileExistsAndHasBytes(VIDEO_FILE);
while (!arePresent && i < LIMIT) {
try { try {
Thread.sleep(150); Thread.sleep(MILLISECONDS_INTERVAL_WAIT);
timeout++; arePresent = fileExistsAndHasBytes(VIDEO_FILE);
File f = new File(this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + "/" i++;
+ recording.getName() + ".mp4");
isPresent = ((f.isFile()) && (f.length() > 0));
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
if (i == timeout) { if (!arePresent) {
log.error("Recorder container failed generating video file (is empty) for session {}", log.error("Recorder container failed generating video file (is empty) for session {}",
recording.getSessionId()); recording.getSessionId());
throw new OpenViduException(Code.RECORDING_START_ERROR_CODE, throw new OpenViduException(Code.RECORDING_START_ERROR_CODE,
@ -462,7 +467,12 @@ public class ComposedRecordingService extends RecordingService {
} }
} }
protected void failRecordingCompletion(Recording recording, String containerId, OpenViduException e) private boolean fileExistsAndHasBytes(String fileName) {
File f = new File(fileName);
return (f.exists() && f.isFile() && f.length() > 0);
}
private void failRecordingCompletion(Recording recording, String containerId, OpenViduException e)
throws OpenViduException { throws OpenViduException {
recording.setStatus(io.openvidu.java.client.Recording.Status.failed); recording.setStatus(io.openvidu.java.client.Recording.Status.failed);
dockerManager.removeDockerContainer(containerId, true); dockerManager.removeDockerContainer(containerId, true);

View File

@ -283,6 +283,7 @@ public class RecordingManager {
if (!(OutputMode.COMPOSED.equals(properties.outputMode()) && properties.hasVideo())) { if (!(OutputMode.COMPOSED.equals(properties.outputMode()) && properties.hasVideo())) {
// Directly send recording started notification for all cases except for // Directly send recording started notification for all cases except for
// COMPOSED recordings with video (will be sent on first RECORDER subscriber) // COMPOSED recordings with video (will be sent on first RECORDER subscriber)
// Both INDIVIDUAL and COMPOSED_QUICK_START should notify immediately
this.sessionHandler.sendRecordingStartedNotification(session, recording); this.sessionHandler.sendRecordingStartedNotification(session, recording);
} }
if (session.getActivePublishers() == 0) { if (session.getActivePublishers() == 0) {
@ -317,7 +318,19 @@ public class RecordingManager {
recording = this.sessionsRecordings.get(session.getSessionId()); recording = this.sessionsRecordings.get(session.getSessionId());
} }
recording = ((RecordingService) singleStreamRecordingService).sealRecordingMetadataFileAsStopped(recording); if (recording == null) {
recording = this.sessionsRecordingsStarting.get(session.getSessionId());
if (recording == null) {
log.error("Cannot stop recording. Session {} is not being recorded", recordingId,
session.getSessionId());
return null;
} else {
// Recording is still starting
log.warn("Recording {} is still in \"starting\" status", recording.getId());
}
}
((RecordingService) singleStreamRecordingService).sealRecordingMetadataFileAsStopped(recording);
final long timestamp = System.currentTimeMillis(); final long timestamp = System.currentTimeMillis();
this.cdr.recordRecordingStatusChanged(recording, reason, timestamp, Status.stopped); this.cdr.recordRecordingStatusChanged(recording, reason, timestamp, Status.stopped);
@ -409,8 +422,15 @@ public class RecordingManager {
public void stopOneIndividualStreamRecording(KurentoSession session, String streamId, long kmsDisconnectionTime) { public void stopOneIndividualStreamRecording(KurentoSession session, String streamId, long kmsDisconnectionTime) {
Recording recording = this.sessionsRecordings.get(session.getSessionId()); Recording recording = this.sessionsRecordings.get(session.getSessionId());
if (recording == null) { if (recording == null) {
log.error("Cannot stop recording of existing stream {}. Session {} is not being recorded", streamId, recording = this.sessionsRecordingsStarting.get(session.getSessionId());
session.getSessionId()); if (recording == null) {
log.error("Cannot stop recording of existing stream {}. Session {} is not being recorded", streamId,
session.getSessionId());
return;
} else {
// Recording is still starting
log.warn("Recording {} is still in \"starting\" status", recording.getId());
}
} }
if (OutputMode.INDIVIDUAL.equals(recording.getOutputMode())) { if (OutputMode.INDIVIDUAL.equals(recording.getOutputMode())) {
// Stop specific RecorderEndpoint for this stream // Stop specific RecorderEndpoint for this stream
@ -835,6 +855,8 @@ public class RecordingManager {
|| (sessionsRecordingsStarting.putIfAbsent(recording.getSessionId(), recording) != null)) { || (sessionsRecordingsStarting.putIfAbsent(recording.getSessionId(), recording) != null)) {
log.error("Concurrent session recording initialization. Aborting this thread"); log.error("Concurrent session recording initialization. Aborting this thread");
throw new RuntimeException("Concurrent initialization of recording " + recording.getId()); throw new RuntimeException("Concurrent initialization of recording " + recording.getId());
} else {
this.sessionHandler.storeRecordingToSendClientEvent(recording);
} }
} }
@ -843,7 +865,6 @@ public class RecordingManager {
* collection * collection
*/ */
private void recordingFromStartingToStarted(Recording recording) { private void recordingFromStartingToStarted(Recording recording) {
this.sessionHandler.setRecordingStarted(recording.getSessionId(), recording);
this.sessionsRecordings.put(recording.getSessionId(), recording); this.sessionsRecordings.put(recording.getSessionId(), recording);
this.startingRecordings.remove(recording.getId()); this.startingRecordings.remove(recording.getId());
this.sessionsRecordingsStarting.remove(recording.getSessionId()); this.sessionsRecordingsStarting.remove(recording.getSessionId());

View File

@ -26,23 +26,29 @@ import java.util.concurrent.TimeUnit;
import javax.ws.rs.ProcessingException; import javax.ws.rs.ProcessingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback; import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.*; import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.command.InspectImageResponse;
import com.github.dockerjava.api.exception.ConflictException; import com.github.dockerjava.api.exception.ConflictException;
import com.github.dockerjava.api.exception.DockerClientException; import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.api.exception.InternalServerErrorException; import com.github.dockerjava.api.exception.InternalServerErrorException;
import com.github.dockerjava.api.exception.NotFoundException; import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.*; import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Volume;
import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.command.ExecStartResultCallback; import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.core.command.PullImageResultCallback; import com.github.dockerjava.core.command.PullImageResultCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code; import io.openvidu.client.OpenViduException.Code;
import io.openvidu.server.recording.service.WaitForContainerStoppedCallback; import io.openvidu.server.recording.service.WaitForContainerStoppedCallback;
@ -63,7 +69,7 @@ public class DockerManager {
// Pull image // Pull image
this.dockerClient.pullImageCmd(image).exec(new PullImageResultCallback()).awaitCompletion(secondsOfWait, this.dockerClient.pullImageCmd(image).exec(new PullImageResultCallback()).awaitCompletion(secondsOfWait,
TimeUnit.SECONDS); TimeUnit.SECONDS);
} catch (NotFoundException | InternalServerErrorException e) { } catch (NotFoundException | InternalServerErrorException e) {
if (dockerImageExistsLocally(image)) { if (dockerImageExistsLocally(image)) {
log.info("Docker image '{}' exists locally", image); log.info("Docker image '{}' exists locally", image);
@ -102,9 +108,8 @@ public class DockerManager {
} }
public String runContainer(String container, String containerName, String user, List<Volume> volumes, public String runContainer(String container, String containerName, String user, List<Volume> volumes,
List<Bind> binds, String networkMode, List<String> envs, List<String> command, Long shmSize, boolean privileged, List<Bind> binds, String networkMode, List<String> envs, List<String> command, Long shmSize,
Map<String, String> labels) boolean privileged, Map<String, String> labels) throws Exception {
throws Exception {
CreateContainerCmd cmd = dockerClient.createContainerCmd(container).withEnv(envs); CreateContainerCmd cmd = dockerClient.createContainerCmd(container).withEnv(envs);
if (containerName != null) { if (containerName != null) {
@ -167,21 +172,30 @@ public class DockerManager {
} }
} }
public String runCommandInContainer(String containerId, String command, int secondsOfWait) public void runCommandInContainer(String containerId, String command) throws InterruptedException {
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true)
.withAttachStderr(true).withCmd("bash", "-c", command).exec();
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ExecStartResultCallback() {
});
}
public void runCommandInContainerSync(String containerId, String command, int secondsOfWait)
throws InterruptedException { throws InterruptedException {
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true) ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true)
.withAttachStderr(true).withCmd("bash", "-c", command).exec(); .withAttachStderr(true).withCmd("bash", "-c", command).exec();
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
final String[] stringResponse = new String[1];
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ExecStartResultCallback() { dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ExecStartResultCallback() {
@Override @Override
public void onNext(Frame item) { public void onComplete() {
stringResponse[0] = new String(item.getPayload());
latch.countDown(); latch.countDown();
} }
}); });
latch.await(secondsOfWait, TimeUnit.SECONDS); try {
return stringResponse[0]; latch.await(secondsOfWait, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new InterruptedException("Container " + containerId + " did not return from executing command \""
+ command + "\" in " + secondsOfWait + " seconds");
}
} }
public void waitForContainerStopped(String containerId, int secondsOfWait) throws Exception { public void waitForContainerStopped(String containerId, int secondsOfWait) throws Exception {

View File

@ -59,6 +59,10 @@ public class CustomWebhook {
CustomWebhook.context.close(); CustomWebhook.context.close();
} }
public static void clean() {
CustomWebhook.events.clear();
}
public synchronized static JsonObject waitForEvent(String eventName, int maxSecondsWait) throws Exception { public synchronized static JsonObject waitForEvent(String eventName, int maxSecondsWait) throws Exception {
if (events.get(eventName) == null) { if (events.get(eventName) == null) {
events.put(eventName, new LinkedBlockingDeque<>()); events.put(eventName, new LinkedBlockingDeque<>());

View File

@ -1246,95 +1246,170 @@ public class OpenViduTestAppE2eTest {
log.info("Remote composed quick start record"); log.info("Remote composed quick start record");
final String sessionName = "COMPOSED_QUICK_START_RECORDED_SESSION"; CountDownLatch initLatch = new CountDownLatch(1);
io.openvidu.test.browsers.utils.CustomWebhook.main(new String[0], initLatch);
// 1. MANUAL mode and recording explicitly stopped try {
user.getDriver().findElement(By.id("add-user-btn")).click(); if (!initLatch.await(30, TimeUnit.SECONDS)) {
user.getDriver().findElement(By.id("session-name-input-0")).clear(); Assert.fail("Timeout waiting for webhook springboot app to start");
user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName); CustomWebhook.shutDown();
return;
}
user.getDriver().findElement(By.id("session-settings-btn-0")).click(); final String sessionName = "COMPOSED_QUICK_START_RECORDED_SESSION";
Thread.sleep(1000); JsonObject event;
user.getDriver().findElement(By.id("output-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-COMPOSED_QUICK_START")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
// Join the subscriber user to the session // 1. MANUAL mode and recording explicitly stopped
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .publish-checkbox")).click();
user.getDriver().findElement(By.className("join-btn")).click();
user.getEventManager().waitUntilEventReaches("connectionCreated", 1);
// Check the recording container is up and running but no ongoing recordings user.getDriver().findElement(By.id("add-user-btn")).click();
checkDockerContainerRunning(RECORDING_IMAGE, 1); user.getDriver().findElement(By.id("session-name-input-0")).clear();
Assert.assertEquals("Wrong number of recordings found", 0, OV.listRecordings().size()); user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName);
// Join the publisher user to the session user.getDriver().findElement(By.id("session-settings-btn-0")).click();
user.getDriver().findElement(By.id("add-user-btn")).click(); Thread.sleep(1000);
user.getDriver().findElement(By.id("session-name-input-1")).clear(); user.getDriver().findElement(By.id("output-mode-select")).click();
user.getDriver().findElement(By.id("session-name-input-1")).sendKeys(sessionName); Thread.sleep(500);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click(); user.getDriver().findElement(By.id("option-COMPOSED_QUICK_START")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getEventManager().waitUntilEventReaches("connectionCreated", 4); // Join the subscriber user to the session
user.getEventManager().waitUntilEventReaches("accessAllowed", 1); user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .publish-checkbox")).click();
user.getEventManager().waitUntilEventReaches("streamCreated", 2); user.getDriver().findElement(By.className("join-btn")).click();
user.getEventManager().waitUntilEventReaches("streamPlaying", 2); user.getEventManager().waitUntilEventReaches("connectionCreated", 1);
// Start recording // Check the recording container is up and running but no ongoing recordings
OV.fetch(); checkDockerContainerRunning(RECORDING_IMAGE, 1);
String recId = OV.startRecording(sessionName).getId(); Assert.assertEquals("Wrong number of recordings found", 0, OV.listRecordings().size());
user.getEventManager().waitUntilEventReaches("recordingStarted", 2);
checkDockerContainerRunning("openvidu/openvidu-recording", 1);
Thread.sleep(1000); // Join the publisher user to the session
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.id("session-name-input-1")).clear();
user.getDriver().findElement(By.id("session-name-input-1")).sendKeys(sessionName);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click();
Assert.assertEquals("Wrong number of recordings found", 1, OV.listRecordings().size()); user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
OV.stopRecording(recId); user.getEventManager().waitUntilEventReaches("accessAllowed", 1);
user.getEventManager().waitUntilEventReaches("recordingStopped", 2); user.getEventManager().waitUntilEventReaches("streamCreated", 2);
checkDockerContainerRunning("openvidu/openvidu-recording", 1); user.getEventManager().waitUntilEventReaches("streamPlaying", 2);
Assert.assertEquals("Wrong number of sessions", 1, OV.getActiveSessions().size()); // Start recording
Session session = OV.getActiveSessions().get(0); OV.fetch();
session.close(); String recId = OV.startRecording(sessionName).getId();
user.getEventManager().waitUntilEventReaches("recordingStarted", 2);
CustomWebhook.waitForEvent("recordingStatusChanged", 5);
checkDockerContainerRunning("openvidu/openvidu-recording", 1);
checkDockerContainerRunning("openvidu/openvidu-recording", 0); Thread.sleep(2000);
// 2. ALWAYS mode and recording stopped by session close up Assert.assertEquals("Wrong number of recordings found", 1, OV.listRecordings().size());
user.getDriver().findElement(By.id("remove-all-users-btn")).click(); OV.stopRecording(recId);
user.getDriver().findElement(By.id("add-user-btn")).click(); user.getEventManager().waitUntilEventReaches("recordingStopped", 2);
user.getDriver().findElement(By.id("session-name-input-0")).clear(); checkDockerContainerRunning("openvidu/openvidu-recording", 1);
user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName);
user.getDriver().findElement(By.id("session-settings-btn-0")).click(); Assert.assertEquals("Wrong number of sessions", 1, OV.getActiveSessions().size());
Thread.sleep(1000); Session session = OV.getActiveSessions().get(0);
user.getDriver().findElement(By.id("recording-mode-select")).click(); session.close();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-ALWAYS")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("output-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-COMPOSED_QUICK_START")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.className("join-btn")).click(); checkDockerContainerRunning("openvidu/openvidu-recording", 0);
user.getEventManager().waitUntilEventReaches("connectionCreated", 5);
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
user.getEventManager().waitUntilEventReaches("streamCreated", 3);
user.getEventManager().waitUntilEventReaches("streamPlaying", 3);
user.getEventManager().waitUntilEventReaches("recordingStarted", 3);
checkDockerContainerRunning("openvidu/openvidu-recording", 1); Assert.assertEquals("Wrong recording status", Recording.Status.ready,
OV.getRecording(sessionName).getStatus());
OV.fetch(); // 2. ALWAYS mode and recording stopped by session close up
session = OV.getActiveSessions().get(0); CustomWebhook.clean();
session.close(); user.getDriver().findElement(By.id("remove-all-users-btn")).click();
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.id("session-name-input-0")).clear();
user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName);
checkDockerContainerRunning("openvidu/openvidu-recording", 0); user.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("recording-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-ALWAYS")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("output-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-COMPOSED_QUICK_START")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.className("join-btn")).click();
user.getEventManager().waitUntilEventReaches("connectionCreated", 5);
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
user.getEventManager().waitUntilEventReaches("streamCreated", 3);
user.getEventManager().waitUntilEventReaches("streamPlaying", 3);
user.getEventManager().waitUntilEventReaches("recordingStarted", 3);
event = CustomWebhook.waitForEvent("recordingStatusChanged", 5); // started
Assert.assertEquals("Wrong status in recordingStatusChanged event", "started",
event.get("status").getAsString());
checkDockerContainerRunning("openvidu/openvidu-recording", 1);
OV.fetch();
session = OV.getActiveSessions().get(0);
session.close();
checkDockerContainerRunning("openvidu/openvidu-recording", 0);
Assert.assertEquals("Wrong recording status", Recording.Status.ready,
OV.getRecording(sessionName + "-1").getStatus());
// 3. Session closed before recording started should trigger
CustomWebhook.clean();
user.getDriver().findElement(By.id("remove-all-users-btn")).click();
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.id("session-name-input-0")).clear();
user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName);
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("recording-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-ALWAYS")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("output-mode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-COMPOSED_QUICK_START")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.className("join-btn")).click();
user.getEventManager().waitUntilEventReaches("connectionCreated", 6);
user.getEventManager().waitUntilEventReaches("accessAllowed", 3);
user.getEventManager().waitUntilEventReaches("streamCreated", 4);
user.getEventManager().waitUntilEventReaches("streamPlaying", 4);
checkDockerContainerRunning("openvidu/openvidu-recording", 1);
OV.fetch();
session = OV.getActiveSessions().get(0);
session.close();
// Recording hasn't had time to start. Should trigger stopped, started, failed
event = CustomWebhook.waitForEvent("recordingStatusChanged", 1); // stopped
Assert.assertEquals("Wrong status in recordingStatusChanged event", "stopped",
event.get("status").getAsString());
event = CustomWebhook.waitForEvent("recordingStatusChanged", 5); // started
Assert.assertEquals("Wrong status in recordingStatusChanged event", "started",
event.get("status").getAsString());
event = CustomWebhook.waitForEvent("recordingStatusChanged", 1); // failed
Assert.assertEquals("Wrong status in recordingStatusChanged event", "failed",
event.get("status").getAsString());
checkDockerContainerRunning("openvidu/openvidu-recording", 0);
Assert.assertEquals("Wrong recording status", Recording.Status.failed,
OV.getRecording(sessionName + "-2").getStatus());
} finally {
CustomWebhook.shutDown();
}
} }
@Test @Test
@ -2366,9 +2441,10 @@ public class OpenViduTestAppE2eTest {
.recordingLayout(RecordingLayout.BEST_FIT).resolution("1280x720").hasVideo(true).hasAudio(false) .recordingLayout(RecordingLayout.BEST_FIT).resolution("1280x720").hasVideo(true).hasAudio(false)
.name(customRecordingName).build(); .name(customRecordingName).build();
// Start recording method should block until video exists and size > 0
Recording recording2 = OV.startRecording(session.getSessionId(), recordingProperties); Recording recording2 = OV.startRecording(session.getSessionId(), recordingProperties);
recording2 = OV.stopRecording(recording2.getId()); recording2 = OV.stopRecording(recording2.getId());
Assert.assertEquals("Wrong recording status", Recording.Status.failed, recording2.getStatus()); Assert.assertEquals("Wrong recording status", Recording.Status.ready, recording2.getStatus());
OV.deleteRecording(recording2.getId()); OV.deleteRecording(recording2.getId());
recording2 = OV.startRecording(session.getSessionId(), recordingProperties); recording2 = OV.startRecording(session.getSessionId(), recordingProperties);