openvidu-test-e2e: custom layout recording test

pull/550/head
pabloFuente 2020-10-01 18:36:01 +02:00
parent 6a8670304f
commit 3056d21320
8 changed files with 266 additions and 27 deletions

View File

@ -220,7 +220,7 @@ public class ComposedQuickStartRecordingService extends ComposedRecordingService
containers.remove(containerId);
sessionsContainers.remove(session.getSessionId());
}
log.error("Error while launchig container for COMPOSED_QUICK_START: ({})", e.getMessage());
log.error("Error while launching container for COMPOSED_QUICK_START: ({})", e.getMessage());
throw e;
}
return containerId;

View File

@ -0,0 +1,41 @@
package io.openvidu.test.browsers.utils.layout;
import java.util.concurrent.CountDownLatch;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class CustomLayoutHandler extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
private static ConfigurableApplicationContext context;
public static CountDownLatch initLatch;
public static void main(String[] args, CountDownLatch initLatch) {
CustomLayoutHandler.initLatch = initLatch;
CustomLayoutHandler.context = new SpringApplicationBuilder(CustomLayoutHandler.class)
.properties("spring.config.location:classpath:aplication-pro-layout-handler.properties").build()
.run(args);
}
@Override
protected void configure(HttpSecurity security) throws Exception {
security.httpBasic().disable();
}
@EventListener(ApplicationReadyEvent.class)
public void afterStartup() {
CustomLayoutHandler.initLatch.countDown();
}
public static void shutDown() {
CustomLayoutHandler.context.close();
}
}

View File

@ -15,7 +15,7 @@
*
*/
package io.openvidu.test.browsers.utils;
package io.openvidu.test.browsers.utils.webhook;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
@ -24,7 +24,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ConfigurableApplicationContext;
@ -39,7 +38,6 @@ import org.springframework.web.bind.annotation.RestController;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@SpringBootApplication
public class CustomWebhook {
private static ConfigurableApplicationContext context;

View File

@ -0,0 +1,3 @@
server.port=5555
server.ssl.enabled=false
security.basic.enabled=false

View File

@ -0,0 +1,55 @@
<html>
<head>
<script>
const xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', 'https://api.github.com/repos/OpenVidu/openvidu/releases/latest', false);
xmlHttp.send(null);
const response = JSON.parse(xmlHttp.responseText);
const version = response.tag_name.replace(/^v/, '');
const newScript = document.createElement('script');
newScript.src = 'https://github.com/OpenVidu/openvidu/releases/download/v' + version +
'/openvidu-browser-' + version + '.min.js';
document.getElementsByTagName('head')[0].appendChild(newScript);
newScript.onload = () => {
startOpenVidu();
};
</script>
</script>
<style>
video {
height: 5px !important;
width: 10px !important;
}
</style>
</head>
<body style='background-color: red'>
<div id='videos'></div>
</body>
<script>
function startOpenVidu() {
var url = new URL(window.location.href);
var SESSION_ID = url.searchParams.get('sessionId');
var SECRET = url.searchParams.get('secret');
var TOKEN = 'wss://' + location.hostname + ':4443?sessionId=' + SESSION_ID + '&secret=' + SECRET +
'&recorder=true';
var OV = new OpenVidu();
var session = OV.initSession();
session.on('streamCreated', (event) => {
session.subscribe(event.stream, 'videos');
});
session.connect(TOKEN)
.then(() => {
console.log('Recorder participant connected')
})
.catch(error => {
console.error(error)
});
}
</script>
</html>

View File

@ -6,6 +6,7 @@ node('container') {
sh 'rm -rf /opt/openvidu/* || true'
sh 'wget https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/barcode.y4m -P /opt/openvidu'
sh 'wget https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/fakeaudio.wav -P /opt/openvidu'
sh 'wget --directory-prefix=/opt/openvidu/test-layouts/layout1 https://github.com/OpenVidu/openvidu/blob/master/openvidu-test-e2e/docker/my-custom-layout/index.html'
docker.image('selenium/standalone-firefox:latest').withRun('-p 6667:4444 --name firefox --shm-size=1g') { d ->
def mycontainer = docker.image('openvidu/openvidu-test-e2e:$DISTRO')
mycontainer.pull()
@ -93,10 +94,10 @@ node('container') {
sh(script: '''#!/bin/bash
if [ "$DOCKER_RECORDING_VERSION" != "default" ]; then
echo "Using custom openvidu-recording tag: $DOCKER_RECORDING_VERSION"
java -jar -DDOMAIN_OR_PUBLIC_IP=172.17.0.1 -DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true -DOPENVIDU_RECORDING_VERSION=$DOCKER_RECORDING_VERSION -DOPENVIDU_WEBHOOK=true -DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook /opt/openvidu/openvidu-server-*.jar &> openvidu-server.log &
java -jar -DDOMAIN_OR_PUBLIC_IP=172.17.0.1 -DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true -DOPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/test-layouts -DOPENVIDU_RECORDING_VERSION=$DOCKER_RECORDING_VERSION -DOPENVIDU_WEBHOOK=true -DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook /opt/openvidu/openvidu-server-*.jar &> openvidu-server.log &
else
echo "Using default openvidu-recording tag"
java -jar -DDOMAIN_OR_PUBLIC_IP=172.17.0.1 -DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true -DOPENVIDU_WEBHOOK=true -DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook /opt/openvidu/openvidu-server-*.jar &> openvidu-server.log &
java -jar -DDOMAIN_OR_PUBLIC_IP=172.17.0.1 -DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true -DOPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/test-layouts -DOPENVIDU_WEBHOOK=true -DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook /opt/openvidu/openvidu-server-*.jar &> openvidu-server.log &
fi
'''.stripIndent())
sh 'until $(curl --insecure --output /dev/null --silent --head --fail https://OPENVIDUAPP:MY_SECRET@localhost:4443/); do echo "Waiting for openvidu-server..."; sleep 2; done'

View File

@ -42,6 +42,7 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.imageio.ImageIO;
@ -103,9 +104,10 @@ import io.openvidu.test.browsers.FirefoxUser;
import io.openvidu.test.browsers.OperaUser;
import io.openvidu.test.browsers.utils.CommandLineExecutor;
import io.openvidu.test.browsers.utils.CustomHttpClient;
import io.openvidu.test.browsers.utils.CustomWebhook;
import io.openvidu.test.browsers.utils.MultimediaFileMetadata;
import io.openvidu.test.browsers.utils.Unzipper;
import io.openvidu.test.browsers.utils.layout.CustomLayoutHandler;
import io.openvidu.test.browsers.utils.webhook.CustomWebhook;
/**
* E2E tests for openvidu-testapp.
@ -1208,9 +1210,10 @@ public class OpenViduTestAppE2eTest {
Assert.assertTrue("File " + file3.getAbsolutePath() + " does not exist or is empty",
file3.exists() && file3.length() > 0);
Assert.assertTrue("Recorded file " + file1.getAbsolutePath() + " is not fine",
this.recordedFileFine(file1, new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName)));
Assert.assertTrue("Thumbnail " + file3.getAbsolutePath() + " is not fine", this.thumbnailIsFine(file3));
Assert.assertTrue("Recorded file " + file1.getAbsolutePath() + " is not fine", this.recordedGreenFileFine(file1,
new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName)));
Assert.assertTrue("Thumbnail " + file3.getAbsolutePath() + " is not fine",
this.thumbnailIsFine(file3, OpenViduTestAppE2eTest::checkVideoAverageRgbGreen));
// Try to get the stopped recording
user.getDriver().findElement(By.id("get-recording-btn")).click();
@ -1247,7 +1250,7 @@ public class OpenViduTestAppE2eTest {
log.info("Composed quick start record");
CountDownLatch initLatch = new CountDownLatch(1);
io.openvidu.test.browsers.utils.CustomWebhook.main(new String[0], initLatch);
io.openvidu.test.browsers.utils.webhook.CustomWebhook.main(new String[0], initLatch);
try {
@ -1520,7 +1523,7 @@ public class OpenViduTestAppE2eTest {
@Test
@DisplayName("Record cross-browser audio-only and video-only")
void recordAudioOnlyVideoOnlyTest() throws Exception {
void audioOnlyVideoOnlyRecordTest() throws Exception {
isRecordingTest = true;
setupBrowser("chromeAlternateScreenShare");
@ -1750,6 +1753,128 @@ public class OpenViduTestAppE2eTest {
}
}
@Test
@DisplayName("Custom layout recording")
void customLayoutRecordTest() throws Exception {
isRecordingTest = true;
setupBrowser("chrome");
log.info("Custom layout recording");
final String SESSION_NAME = "CUSTOM_LAYOUT_SESSION";
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(SESSION_NAME);
// Custom layout from local storage
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("recording-layout-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-CUSTOM")).click();
Thread.sleep(500);
WebElement tokeInput = user.getDriver().findElement(By.id("default-custom-layout-input"));
tokeInput.clear();
tokeInput.sendKeys("layout1");
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.className("join-btn")).sendKeys(Keys.ENTER);
user.getEventManager().waitUntilEventReaches("connectionCreated", 1);
user.getEventManager().waitUntilEventReaches("accessAllowed", 1);
user.getEventManager().waitUntilEventReaches("streamCreated", 1);
user.getEventManager().waitUntilEventReaches("streamPlaying", 1);
user.getEventManager().waitUntilEventReaches("recordingStarted", 1);
Thread.sleep(4000);
user.getDriver().findElement(By.id("session-api-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("recording-id-field")).clear();
user.getDriver().findElement(By.id("recording-id-field")).sendKeys(SESSION_NAME);
user.getDriver().findElement(By.id("stop-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording stopped [" + SESSION_NAME + "]"));
user.getEventManager().waitUntilEventReaches("recordingStopped", 1);
user.getDriver().findElement(By.id("close-session-btn")).click();
user.getEventManager().waitUntilEventReaches("streamDestroyed", 1);
user.getEventManager().waitUntilEventReaches("sessionDisconnected", 1);
user.getDriver().findElement(By.id("close-dialog-btn")).click();
String recordingsPath = "/opt/openvidu/recordings/" + SESSION_NAME + "/";
File file1 = new File(recordingsPath + SESSION_NAME + ".mp4");
File file2 = new File(recordingsPath + SESSION_NAME + ".jpg");
Assert.assertTrue("Recorded file " + file1.getAbsolutePath() + " is not fine", this.recordedRedFileFine(file1,
new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME)));
Assert.assertTrue("Thumbnail " + file2.getAbsolutePath() + " is not fine",
this.thumbnailIsFine(file2, OpenViduTestAppE2eTest::checkVideoAverageRgbRed));
// Custom layout from external URL
CountDownLatch initLatch = new CountDownLatch(1);
CustomLayoutHandler.main(new String[0], initLatch);
try {
if (!initLatch.await(30, TimeUnit.SECONDS)) {
Assert.fail("Timeout waiting for webhook springboot app to start");
CustomLayoutHandler.shutDown();
return;
}
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
tokeInput = user.getDriver().findElement(By.id("default-custom-layout-input"));
tokeInput.clear();
tokeInput.sendKeys("http://localhost:5555?sessionId=CUSTOM_LAYOUT_SESSION&secret=MY_SECRET");
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.className("join-btn")).sendKeys(Keys.ENTER);
user.getEventManager().waitUntilEventReaches("connectionCreated", 2);
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
user.getEventManager().waitUntilEventReaches("streamCreated", 2);
user.getEventManager().waitUntilEventReaches("streamPlaying", 2);
user.getEventManager().waitUntilEventReaches("recordingStarted", 2);
Thread.sleep(4000);
user.getDriver().findElement(By.id("session-api-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("recording-id-field")).clear();
user.getDriver().findElement(By.id("recording-id-field")).sendKeys(SESSION_NAME + "-1");
user.getDriver().findElement(By.id("stop-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording stopped [" + SESSION_NAME + "-1]"));
user.getEventManager().waitUntilEventReaches("recordingStopped", 2);
user.getDriver().findElement(By.id("close-session-btn")).click();
user.getEventManager().waitUntilEventReaches("streamDestroyed", 2);
user.getEventManager().waitUntilEventReaches("sessionDisconnected", 2);
user.getDriver().findElement(By.id("close-dialog-btn")).click();
recordingsPath = "/opt/openvidu/recordings/" + SESSION_NAME + "-1/";
file1 = new File(recordingsPath + SESSION_NAME + "-1.mp4");
file2 = new File(recordingsPath + SESSION_NAME + "-1.jpg");
Assert.assertTrue("Recorded file " + file1.getAbsolutePath() + " is not fine", this.recordedRedFileFine(
file1, new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME + "-1")));
Assert.assertTrue("Thumbnail " + file2.getAbsolutePath() + " is not fine",
this.thumbnailIsFine(file2, OpenViduTestAppE2eTest::checkVideoAverageRgbRed));
} finally {
CustomLayoutHandler.shutDown();
}
}
@Test
@DisplayName("REST API: Fetch all, fetch one, force disconnect, force unpublish, close session")
void restApiFetchForce() throws Exception {
@ -2490,8 +2615,9 @@ public class OpenViduTestAppE2eTest {
file3.exists() && file3.length() > 0);
Assert.assertTrue("Recorded file " + file1.getAbsolutePath() + " is not fine",
this.recordedFileFine(file1, recording2));
Assert.assertTrue("Thumbnail " + file3.getAbsolutePath() + " is not fine", this.thumbnailIsFine(file3));
this.recordedGreenFileFine(file1, recording2));
Assert.assertTrue("Thumbnail " + file3.getAbsolutePath() + " is not fine",
this.thumbnailIsFine(file3, OpenViduTestAppE2eTest::checkVideoAverageRgbGreen));
try {
OV.deleteRecording("NOT_EXISTS");
@ -2506,12 +2632,12 @@ public class OpenViduTestAppE2eTest {
try {
session.forceUnpublish("NOT_EXISTS");
} catch (OpenViduHttpException e) {
Assert.assertEquals("Wrong HTTP status on Session.fetch()", 404, e.getStatus());
Assert.assertEquals("Wrong HTTP status on Session.forceUnpublish()", 404, e.getStatus());
}
try {
session.forceDisconnect("NOT_EXISTS");
} catch (OpenViduHttpException e) {
Assert.assertEquals("Wrong HTTP status on Session.fetch()", 404, e.getStatus());
Assert.assertEquals("Wrong HTTP status on Session.forceDisconnect()", 404, e.getStatus());
}
if (OpenViduRole.MODERATOR.equals(session.getActiveConnections().get(0).getRole())) {
@ -3051,7 +3177,7 @@ public class OpenViduTestAppE2eTest {
log.info("Webhook test");
CountDownLatch initLatch = new CountDownLatch(1);
io.openvidu.test.browsers.utils.CustomWebhook.main(new String[0], initLatch);
io.openvidu.test.browsers.utils.webhook.CustomWebhook.main(new String[0], initLatch);
try {
@ -3204,7 +3330,7 @@ public class OpenViduTestAppE2eTest {
log.info("IP camera test");
CountDownLatch initLatch = new CountDownLatch(1);
io.openvidu.test.browsers.utils.CustomWebhook.main(new String[0], initLatch);
io.openvidu.test.browsers.utils.webhook.CustomWebhook.main(new String[0], initLatch);
try {
@ -3464,12 +3590,12 @@ public class OpenViduTestAppE2eTest {
};
}
private boolean checkVideoAverageRgbGreen(Map<String, Long> rgb) {
private static boolean checkVideoAverageRgbGreen(Map<String, Long> rgb) {
// GREEN color: {r < 15, g > 130, b <15}
return (rgb.get("r") < 15) && (rgb.get("g") > 130) && (rgb.get("b") < 15);
}
private boolean checkVideoAverageRgbGray(Map<String, Long> rgb) {
private static boolean checkVideoAverageRgbGray(Map<String, Long> rgb) {
// GRAY color: {r < 50, g < 50, b < 50} and the absolute difference between them
// not greater than 2
return (rgb.get("r") < 50) && (rgb.get("g") < 50) && (rgb.get("b") < 50)
@ -3477,6 +3603,11 @@ public class OpenViduTestAppE2eTest {
&& (Math.abs(rgb.get("b") - rgb.get("g")) <= 2);
}
private static boolean checkVideoAverageRgbRed(Map<String, Long> rgb) {
// RED color: {r > 240, g < 15, b <15}
return (rgb.get("r") > 240) && (rgb.get("g") < 15) && (rgb.get("b") < 15);
}
private void gracefullyLeaveParticipants(int numberOfParticipants) throws Exception {
int accumulatedConnectionDestroyed = 0;
for (int j = 1; j <= numberOfParticipants; j++) {
@ -3494,7 +3625,8 @@ public class OpenViduTestAppE2eTest {
return "data:image/png;base64," + screenshotBase64;
}
private boolean recordedFileFine(File file, Recording recording) throws IOException {
private boolean recordedFileFine(File file, Recording recording,
Function<Map<String, Long>, Boolean> colorCheckFunction) throws IOException {
this.checkMultimediaFile(file, recording.hasAudio(), recording.hasVideo(), recording.getDuration(),
recording.getResolution(), "aac", "h264", true);
@ -3515,7 +3647,7 @@ public class OpenViduTestAppE2eTest {
log.info("Recording map color: {}", colorMap.toString());
log.info("Recording frame below");
System.out.println(bufferedImageToBase64PngString(image));
isFine = this.checkVideoAverageRgbGreen(colorMap);
isFine = colorCheckFunction.apply(colorMap);
} catch (IOException | JCodecException e) {
log.warn("Error getting frame from video recording: {}", e.getMessage());
isFine = false;
@ -3523,6 +3655,14 @@ public class OpenViduTestAppE2eTest {
return isFine;
}
private boolean recordedGreenFileFine(File file, Recording recording) throws IOException {
return this.recordedFileFine(file, recording, OpenViduTestAppE2eTest::checkVideoAverageRgbGreen);
}
private boolean recordedRedFileFine(File file, Recording recording) throws IOException {
return this.recordedFileFine(file, recording, OpenViduTestAppE2eTest::checkVideoAverageRgbRed);
}
private String bufferedImageToBase64PngString(BufferedImage image) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
String imageString = null;
@ -3654,7 +3794,7 @@ public class OpenViduTestAppE2eTest {
Math.abs((metadata.getDuration() - duration)) < difference);
}
private boolean thumbnailIsFine(File file) {
private boolean thumbnailIsFine(File file, Function<Map<String, Long>, Boolean> colorCheckFunction) {
boolean isFine = false;
BufferedImage image = null;
try {
@ -3666,7 +3806,7 @@ public class OpenViduTestAppE2eTest {
log.info("Recording thumbnail dimensions: {}x{}", image.getWidth(), image.getHeight());
Map<String, Long> colorMap = this.averageColor(image);
log.info("Thumbnail map color: {}", colorMap.toString());
isFine = this.checkVideoAverageRgbGreen(colorMap);
isFine = colorCheckFunction.apply(colorMap);
return isFine;
}

View File

@ -27,16 +27,17 @@
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="this.sessionProperties.defaultOutputMode === 'COMPOSED'">
<mat-select placeholder="DefaultRecordingLayout" [(ngModel)]="sessionProperties.defaultRecordingLayout">
<mat-select placeholder="DefaultRecordingLayout" [(ngModel)]="sessionProperties.defaultRecordingLayout"
id="recording-layout-select">
<mat-option *ngFor="let enumerator of enumToArray(defaultRecordingLayout)" [value]="enumerator">
{{ enumerator }}
<span [attr.id]="'option-' + enumerator">{{ enumerator }}</span>
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field
*ngIf="this.sessionProperties.defaultOutputMode === 'COMPOSED' && this.sessionProperties.defaultRecordingLayout === 'CUSTOM'">
<input matInput placeholder="DefaultCustomLayout" type="text"
[(ngModel)]="sessionProperties.defaultCustomLayout">
[(ngModel)]="sessionProperties.defaultCustomLayout" id="default-custom-layout-input">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="CustomSessionId" type="text" [(ngModel)]="sessionProperties.customSessionId">