From bd23cfcd710dd5bfcbebabfb361f09833c4fe8ee Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Thu, 16 Feb 2023 14:43:00 +0100 Subject: [PATCH] Broadcast SDKs. Broadcast tests in OpenViduProTestaAppE2eTest --- .../io/openvidu/java/client/OpenVidu.java | 137 +++++++ openvidu-node-client/src/OpenVidu.ts | 131 ++++++- .../server/rest/SessionRestController.java | 53 +-- .../test/browsers/utils/CustomHttpClient.java | 2 +- .../test/e2e/OpenViduProTestAppE2eTest.java | 349 +++++++++++++++++- .../src/test/resources/broadcast-nginx.conf | 19 + 6 files changed, 649 insertions(+), 42 deletions(-) create mode 100644 openvidu-test-e2e/src/test/resources/broadcast-nginx.conf diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java index c43c5d56..e11bf5a4 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java @@ -86,6 +86,9 @@ public class OpenVidu { protected final static String API_RECORDINGS = API_PATH + "/recordings"; protected final static String API_RECORDINGS_START = API_RECORDINGS + "/start"; protected final static String API_RECORDINGS_STOP = API_RECORDINGS + "/stop"; + protected final static String API_BROADCAST = API_PATH + "/broadcast"; + protected final static String API_BROADCAST_START = API_BROADCAST + "/start"; + protected final static String API_BROADCAST_STOP = API_BROADCAST + "/stop"; /** * @param hostname URL where your OpenVidu deployment is up an running. It must @@ -515,6 +518,140 @@ public class OpenVidu { } } + /** + * Starts the broadcast of a {@link io.openvidu.java.client.Session} + * + * @param sessionId The sessionId of the session you want to start + * broadcasting + * @param broadcastUrl The URL where to broadcast + * + * @throws OpenViduJavaClientException + * @throws OpenViduHttpException The status code carries a specific + * meaning + * {@link io.openvidu.java.client.OpenViduHttpException#getStatus()} + * (see REST + * API) + */ + public void startBroadcast(String sessionId, String broadcastUrl) + throws OpenViduJavaClientException, OpenViduHttpException { + this.startBroadcast(sessionId, broadcastUrl, new RecordingProperties.Builder().build()); + } + + /** + * Starts the broadcast of a {@link io.openvidu.java.client.Session} + * + * @param sessionId The sessionId of the session you want to start + * broadcasting + * @param broadcastUrl The URL where to broadcast + * @param properties The configuration for this broadcast. It uses a subset of + * the {@link io.openvidu.java.client.RecordingProperties}: + * + * + * + * @throws OpenViduJavaClientException + * @throws OpenViduHttpException The status code carries a specific + * meaning + * {@link io.openvidu.java.client.OpenViduHttpException#getStatus()} + * (see REST + * API) + */ + public void startBroadcast(String sessionId, String broadcastUrl, RecordingProperties properties) + throws OpenViduJavaClientException, OpenViduHttpException { + final HttpClientResponseHandler responseHandler = new HttpClientResponseHandler() { + @Override + public Void handleResponse(final ClassicHttpResponse response) throws IOException, HttpException { + final int status = response.getCode(); + if (status == HttpStatus.SC_OK) { + Session activeSession = activeSessions.get(sessionId); + if (activeSession != null) { + activeSession.setIsBeingBroadcasted(true); + } else { + log.warn( + "No active session found for sessionId '{}'. This instance of OpenVidu Java Client didn't create this session", + sessionId); + } + } else { + throw openViduHttpException(status); + } + return null; + } + }; + + JsonObject json = properties.toJson(); + json.addProperty("session", sessionId); + json.addProperty("broadcastUrl", broadcastUrl); + StringEntity params = new StringEntity(json.toString(), StandardCharsets.UTF_8); + + HttpPost request = new HttpPost(this.hostname + API_BROADCAST_START); + request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + request.setEntity(params); + + try { + this.httpClient.execute(request, responseHandler); + } catch (IOException e) { + throw ioExceptionToOpenViduHttpException(e); + } + } + + /** + * Stops the broadcast of a {@link io.openvidu.java.client.Session} + * + * @param sessionId The sessionId of the session you want to stop broadcasting + * + * @throws OpenViduJavaClientException + * @throws OpenViduHttpException The status code carries a specific + * meaning + * {@link io.openvidu.java.client.OpenViduHttpException#getStatus()} + * (see REST + * API) + */ + public void stopBroadcast(String sessionId) throws OpenViduJavaClientException, OpenViduHttpException { + final HttpClientResponseHandler responseHandler = new HttpClientResponseHandler() { + @Override + public Void handleResponse(final ClassicHttpResponse response) throws IOException, HttpException { + final int status = response.getCode(); + if (status == HttpStatus.SC_OK) { + Session activeSession = activeSessions.get(sessionId); + if (activeSession != null) { + activeSession.setIsBeingBroadcasted(false); + } else { + log.warn( + "No active session found for sessionId '{}'. This instance of OpenVidu Java Client didn't create this session", + sessionId); + } + return null; + } else { + throw openViduHttpException(status); + } + } + }; + + JsonObject json = new JsonObject(); + json.addProperty("session", sessionId); + StringEntity params = new StringEntity(json.toString(), StandardCharsets.UTF_8); + + HttpPost request = new HttpPost(this.hostname + API_BROADCAST_STOP); + request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + request.setEntity(params); + + try { + this.httpClient.execute(request, responseHandler); + } catch (IOException e) { + throw ioExceptionToOpenViduHttpException(e); + } + } + /** * Returns the list of active sessions. This value will remain unchanged * since the last time method {@link io.openvidu.java.client.OpenVidu#fetch()} diff --git a/openvidu-node-client/src/OpenVidu.ts b/openvidu-node-client/src/OpenVidu.ts index 7a6aedaa..558afcd9 100644 --- a/openvidu-node-client/src/OpenVidu.ts +++ b/openvidu-node-client/src/OpenVidu.ts @@ -65,7 +65,18 @@ export class OpenVidu { * @hidden */ static readonly API_RECORDINGS_STOP: string = OpenVidu.API_RECORDINGS + '/stop'; - + /** + * @hidden + */ + static readonly API_BROADCAST: string = OpenVidu.API_PATH + '/broadcast'; + /** + * @hidden + */ + static readonly API_BROADCAST_START: string = OpenVidu.API_BROADCAST + '/start'; + /** + * @hidden + */ + static readonly API_BROADCAST_STOP: string = OpenVidu.API_BROADCAST + '/stop'; @@ -359,6 +370,124 @@ export class OpenVidu { }); } + public startBroadcast(sessionId: string, broadcastUrl: string): Promise; + public startBroadcast(sessionId: string, broadcastUrl: string, properties: RecordingProperties): Promise; + + /** + * Starts the broadcast of a {@link Session} + * + * @param sessionId The `sessionId` of the {@link Session} you want to start broadcasting + * @param broadcastUrl The URL where to broadcast + * @param properties The configuration for this broadcast. It uses a subset of the {@link RecordingProperties}: + * - {@link RecordingProperties.hasAudio} + * - {@link RecordingProperties.resolution} + * - {@link RecordingProperties.frameRate} + * - {@link RecordingProperties.recordingLayout} + * - {@link RecordingProperties.customLayout} + * - {@link RecordingProperties.shmSize} + * - {@link RecordingProperties.mediaNode} + * + * @returns A Promise that is resolved if the broadcast successfully started and rejected with an + * [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object if not. + * This Error object has as `message` property with a status code carrying a specific meaning + * (see [REST API](/en/stable/reference-docs/REST-API/#start-broadcast)). + */ + public startBroadcast(sessionId: string, broadcastUrl: string, properties?: RecordingProperties): Promise { + return new Promise((resolve, reject) => { + + let data; + + if (properties != undefined) { + data = { + session: sessionId, + broadcastUrl, + recordingLayout: properties.recordingLayout, + customLayout: properties.customLayout, + resolution: properties.resolution, + frameRate: properties.frameRate, + hasAudio: properties.hasAudio, + shmSize: properties.shmSize, + mediaNode: properties.mediaNode + }; + data = JSON.stringify(data); + } else { + data = { + session: sessionId, + broadcastUrl + } + } + + axios.post( + this.host + OpenVidu.API_BROADCAST_START, + data, + { + headers: { + 'Authorization': this.basicAuth, + 'Content-Type': 'application/json' + } + } + ) + .then(res => { + if (res.status === 200) { + const activeSession = this.activeSessions.find(s => s.sessionId === sessionId); + if (!!activeSession) { + activeSession.broadcasting = true; + } else { + console.warn("No active session found for sessionId '" + sessionId + "'. This instance of OpenVidu Node Client didn't create this session"); + } + resolve(); + } else { + // ERROR response from openvidu-server. Resolve HTTP status + reject(new Error(res.status.toString())); + } + }).catch(error => { + this.handleError(error, reject); + }); + }); + } + + /** + * Stops the broadcast of a {@link Session} + * + * @param sessionId The `sessionId` of the {@link Session} you want to stop broadcasting + * + * @returns A Promise that is resolved if the broadcast successfully stopped and rejected with an + * [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object if not. + * This Error object has as `message` property with a status code carrying a specific meaning + * (see [REST API](/en/stable/reference-docs/REST-API/#stop-broadcast)). + */ + public stopBroadcst(sessionId: string): Promise { + return new Promise((resolve, reject) => { + axios.post( + this.host + OpenVidu.API_BROADCAST_STOP, + { session: sessionId }, + { + headers: { + 'Authorization': this.basicAuth, + 'Content-Type': 'application/json' + } + } + ) + .then(res => { + if (res.status === 200) { + // SUCCESS response from openvidu-server + const activeSession = this.activeSessions.find(s => s.sessionId === sessionId); + if (!!activeSession) { + activeSession.broadcasting = false; + } else { + console.warn("No active session found for sessionId '" + sessionId + "'. This instance of OpenVidu Node Client didn't create this session"); + } + resolve(); + } else { + // ERROR response from openvidu-server. Resolve HTTP status + reject(new Error(res.status.toString())); + } + }).catch(error => { + this.handleError(error, reject); + }); + }); + } + /** * Updates every property of every active Session with the current status they have in OpenVidu Server. * After calling this method you can access the updated array of active sessions in {@link activeSessions} diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java index 8ba03603..b1f67900 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java @@ -292,8 +292,8 @@ public class SessionRestController { case IPCAM: return this.newIpcamConnection(session, connectionProperties); default: - return SessionRestController.generateErrorResponse("Wrong type parameter", "/sessions/" + sessionId + "/connection", - HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("Wrong type parameter", + "/sessions/" + sessionId + "/connection", HttpStatus.BAD_REQUEST); } } @@ -371,8 +371,8 @@ public class SessionRestController { public ResponseEntity startRecording(@RequestBody Map params) { if (params == null) { - return SessionRestController.generateErrorResponse("Error in body parameters. Cannot be empty", "/recordings/start", - HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("Error in body parameters. Cannot be empty", + "/recordings/start", HttpStatus.BAD_REQUEST); } log.info("REST API: POST {}/recordings/start {}", RequestMappings.API, params.toString()); @@ -386,14 +386,14 @@ public class SessionRestController { try { sessionId = (String) params.get("session"); } catch (Exception e) { - return SessionRestController.generateErrorResponse("Type error in parameter \"session\"", "/recordings/start", - HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("Type error in parameter \"session\"", + "/recordings/start", HttpStatus.BAD_REQUEST); } if (sessionId == null) { // "session" parameter not found - return SessionRestController.generateErrorResponse("\"session\" parameter is mandatory", "/recordings/start", - HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("\"session\" parameter is mandatory", + "/recordings/start", HttpStatus.BAD_REQUEST); } Session session = sessionManager.getSession(sessionId); @@ -426,7 +426,8 @@ public class SessionRestController { try { recordingProperties = getRecordingPropertiesFromParams(params, session).build(); } catch (IllegalStateException e) { - return SessionRestController.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.UNPROCESSABLE_ENTITY); + return SessionRestController.generateErrorResponse(e.getMessage(), "/sessions", + HttpStatus.UNPROCESSABLE_ENTITY); } catch (RuntimeException e) { return SessionRestController.generateErrorResponse(e.getMessage(), "/sessions", HttpStatus.BAD_REQUEST); } @@ -566,11 +567,13 @@ public class SessionRestController { try { sessionId = (String) params.get("session"); } catch (ClassCastException e) { - return SessionRestController.generateErrorResponse("Type error in some parameter", "/tokens", HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("Type error in some parameter", "/tokens", + HttpStatus.BAD_REQUEST); } if (sessionId == null) { - return SessionRestController.generateErrorResponse("\"session\" parameter is mandatory", "/tokens", HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("\"session\" parameter is mandatory", "/tokens", + HttpStatus.BAD_REQUEST); } log.warn("Token API is deprecated. Use Connection API instead (POST {}/sessions/{}/connection)", @@ -578,7 +581,8 @@ public class SessionRestController { final Session session = this.sessionManager.getSessionWithNotActive(sessionId); if (session == null) { - return SessionRestController.generateErrorResponse("Session " + sessionId + " not found", "/tokens", HttpStatus.NOT_FOUND); + return SessionRestController.generateErrorResponse("Session " + sessionId + " not found", "/tokens", + HttpStatus.NOT_FOUND); } ConnectionProperties connectionProperties; @@ -655,14 +659,16 @@ public class SessionRestController { type = (String) params.get("type"); data = (String) params.get("data"); } catch (ClassCastException e) { - return SessionRestController.generateErrorResponse("Type error in some parameter", "/signal", HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("Type error in some parameter", "/signal", + HttpStatus.BAD_REQUEST); } JsonObject completeMessage = new JsonObject(); if (sessionId == null) { // "session" parameter not found - return SessionRestController.generateErrorResponse("\"session\" parameter is mandatory", "/signal", HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("\"session\" parameter is mandatory", "/signal", + HttpStatus.BAD_REQUEST); } Session session = sessionManager.getSession(sessionId); if (session == null) { @@ -689,16 +695,16 @@ public class SessionRestController { JsonArray toArray = gson.toJsonTree(to).getAsJsonArray(); completeMessage.add("to", toArray); } catch (IllegalStateException exception) { - return SessionRestController.generateErrorResponse("\"to\" parameter is not a valid JSON array", "/signal", - HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("\"to\" parameter is not a valid JSON array", + "/signal", HttpStatus.BAD_REQUEST); } } try { sessionManager.sendMessage(completeMessage.toString(), session); } catch (OpenViduException e) { - return SessionRestController.generateErrorResponse("\"to\" array has no valid connection identifiers", "/signal", - HttpStatus.NOT_ACCEPTABLE); + return SessionRestController.generateErrorResponse("\"to\" array has no valid connection identifiers", + "/signal", HttpStatus.NOT_ACCEPTABLE); } return new ResponseEntity<>(HttpStatus.OK); @@ -729,8 +735,8 @@ public class SessionRestController { } else { log.error("Session {} is in the process of closing. Connection couldn't be created", session.getSessionId()); - return SessionRestController.generateErrorResponse("Session " + session.getSessionId() + " not found", REQUEST_PATH, - HttpStatus.NOT_FOUND); + return SessionRestController.generateErrorResponse("Session " + session.getSessionId() + " not found", + REQUEST_PATH, HttpStatus.NOT_FOUND); } } @@ -761,10 +767,11 @@ public class SessionRestController { return new ResponseEntity<>(ipcamParticipant.toJson().toString(), RestUtils.getResponseHeaders(), HttpStatus.OK); } catch (MalformedURLException e) { - return SessionRestController.generateErrorResponse("\"rtspUri\" parameter is not a valid rtsp uri", REQUEST_PATH, - HttpStatus.BAD_REQUEST); + return SessionRestController.generateErrorResponse("\"rtspUri\" parameter is not a valid rtsp uri", + REQUEST_PATH, HttpStatus.BAD_REQUEST); } catch (Exception e) { - return SessionRestController.generateErrorResponse(e.getMessage(), REQUEST_PATH, HttpStatus.INTERNAL_SERVER_ERROR); + return SessionRestController.generateErrorResponse(e.getMessage(), REQUEST_PATH, + HttpStatus.INTERNAL_SERVER_ERROR); } finally { session.closingLock.readLock().unlock(); } diff --git a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/utils/CustomHttpClient.java b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/utils/CustomHttpClient.java index fcc8c43f..e07c329a 100644 --- a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/utils/CustomHttpClient.java +++ b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/utils/CustomHttpClient.java @@ -283,7 +283,7 @@ public class CustomHttpClient { return json; } - private String commonRestString(HttpMethod method, String path, String body, int status) throws Exception { + public String commonRestString(HttpMethod method, String path, String body, int status) throws Exception { path = openviduUrl + (path.startsWith("/") ? path : ("/" + path)); HttpRequest.Builder builder = HttpRequest.newBuilder().uri(new URI(path)); diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduProTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduProTestAppE2eTest.java index aec57ac6..1db828d4 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduProTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduProTestAppE2eTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.awt.Point; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileReader; import java.net.HttpURLConnection; import java.nio.file.Files; @@ -37,6 +38,7 @@ import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.springframework.http.HttpMethod; +import org.springframework.util.ResourceUtils; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -54,8 +56,10 @@ import io.openvidu.java.client.OpenVidu; import io.openvidu.java.client.OpenViduHttpException; import io.openvidu.java.client.OpenViduRole; import io.openvidu.java.client.Recording; +import io.openvidu.java.client.RecordingProperties; import io.openvidu.java.client.Session; import io.openvidu.test.browsers.utils.CustomHttpClient; +import io.openvidu.test.browsers.utils.RecordingUtils; import io.openvidu.test.browsers.utils.Unzipper; import io.openvidu.test.browsers.utils.webhook.CustomWebhook; @@ -228,6 +232,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { io.openvidu.test.browsers.utils.webhook.CustomWebhook.main(new String[0], initLatch); try { + startRtmpServer(); if (!initLatch.await(30, TimeUnit.SECONDS)) { Assertions.fail("Timeout waiting for webhook springboot app to start"); @@ -250,8 +255,10 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { try { - Map newConfig = Map.of("OPENVIDU_WEBHOOK", true, "OPENVIDU_WEBHOOK_ENDPOINT", - "http://127.0.0.1:7777/webhook", "OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", 0); + Map newConfig = Map.of("OPENVIDU_PRO_NETWORK_QUALITY", false, + "OPENVIDU_PRO_SPEECH_TO_TEXT", "disabled", "OPENVIDU_WEBHOOK", true, + "OPENVIDU_WEBHOOK_ENDPOINT", "http://127.0.0.1:7777/webhook", + "OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", 0); restartOpenViduServer(newConfig); OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); @@ -274,7 +281,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { // disconnect: webrtcConnectionDestroyed, participantLeft (and subsequent // lastParticipantLeft triggered events for sessionDestroyed, - // recordingStatusChanged, [broadcastStopped]) + // recordingStatusChanged, broadcastStopped) this.connectTwoUsers(user, restClient, false, true, true); // First user out user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .leave-btn")).click(); @@ -294,6 +301,8 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("lastParticipantLeft", CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString()); } + Assertions.assertEquals("lastParticipantLeft", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("lastParticipantLeft", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); @@ -324,7 +333,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); // forceDisconnectByUser: webrtcConnectionDestroyed, participantLeft - this.connectTwoUsers(user, restClient, true, true, true); + this.connectTwoUsers(user, restClient, true, false, false); user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .force-disconnect-btn")).click(); for (int i = 0; i < 3; i++) { Assertions.assertEquals("forceDisconnectByUser", @@ -336,7 +345,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { // forceDisconnectByServer: webrtcConnectionDestroyed, participantLeft (and // subsequent lastParticipantLeft triggered events for sessionDestroyed, - // recordingStatusChanged, [broadcastStopped]) + // recordingStatusChanged, broadcastStopped) this.connectTwoUsers(user, restClient, false, true, true); String[] connectionIds = restClient .rest(HttpMethod.GET, "/openvidu/api/sessions/TestSession", HttpURLConnection.HTTP_OK) @@ -362,12 +371,14 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("lastParticipantLeft", CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString()); } + Assertions.assertEquals("lastParticipantLeft", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("lastParticipantLeft", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); // sessionClosedByServer: webrtcConnectionDestroyed, participantLeft, - // sessionDestroyed, recordingStatusChanged + // sessionDestroyed, recordingStatusChanged, broadcastStopped this.connectTwoUsers(user, restClient, false, true, true); restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/TestSession", HttpURLConnection.HTTP_NO_CONTENT); @@ -383,13 +394,15 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("sessionClosedByServer", CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString()); } + Assertions.assertEquals("sessionClosedByServer", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("sessionClosedByServer", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); // networkDisconnect: webrtcConnectionDestroyed, participantLeft (and // subsequent lastParticipantLeft triggered events for sessionDestroyed, - // recordingStatusChanged, [broadcastStopped]) + // recordingStatusChanged, broadcastStopped) this.connectTwoUsers(user, restClient, false, true, true); // First user out user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .network-drop-btn")).click(); @@ -409,12 +422,14 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("lastParticipantLeft", CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString()); } + Assertions.assertEquals("lastParticipantLeft", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("lastParticipantLeft", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); // mediaServerDisconnect: webrtcConnectionDestroyed, participantLeft, - // sessionDestroyed, recordingStatusChanged, [broadcastStopped] + // sessionDestroyed, recordingStatusChanged, broadcastStopped this.connectTwoUsers(user, restClient, false, true, true); String mediaNodeId = restClient .rest(HttpMethod.GET, "/openvidu/api/media-nodes", HttpURLConnection.HTTP_OK).get("content") @@ -435,6 +450,8 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("mediaServerDisconnect", CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString()); } + Assertions.assertEquals("mediaServerDisconnect", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("mediaServerDisconnect", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.waitForEvent("mediaNodeStatusChanged", 5); @@ -449,7 +466,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { MediaNodeDockerUtils.stopMediaServerInsideMediaNodeAndRecover(containerId, 400); for (int i = 0; i < 4; i++) { Assertions.assertEquals("mediaServerReconnect", - CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2).get("reason").getAsString()); + CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 6).get("reason").getAsString()); } for (int i = 0; i < 2; i++) { Assertions.assertEquals("mediaServerReconnect", @@ -458,7 +475,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); // nodeCrashed: webrtcConnectionDestroyed, participantLeft, sessionDestroyed, - // recordingStatusChanged, [broadcastStopped] + // recordingStatusChanged, broadcastStopped this.connectTwoUsers(user, restClient, false, true, true); containerId = restClient.rest(HttpMethod.GET, "/openvidu/api/media-nodes", HttpURLConnection.HTTP_OK) .get("content").getAsJsonArray().get(0).getAsJsonObject().get("environmentId").getAsString(); @@ -475,7 +492,9 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { } // Only status "stopped" for recording. Not "ready" if node crash Assertions.assertEquals("nodeCrashed", - CustomWebhook.waitForEvent("recordingStatusChanged", 2).get("reason").getAsString()); + CustomWebhook.waitForEvent("recordingStatusChanged", 10).get("reason").getAsString()); + Assertions.assertEquals("nodeCrashed", + CustomWebhook.waitForEvent("broadcastStopped", 10).get("reason").getAsString()); Assertions.assertEquals("nodeCrashed", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.waitForEvent("mediaNodeStatusChanged", 2); @@ -483,7 +502,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { restartOpenViduServer(new HashMap<>(), true, HttpURLConnection.HTTP_OK); // openviduServerStopped: webrtcConnectionDestroyed, participantLeft, - // sessionDestroyed, recordingStatusChanged, [broadcastStopped] + // sessionDestroyed, recordingStatusChanged, broadcastStopped this.connectTwoUsers(user, restClient, false, true, true); restartOpenViduServer(new HashMap<>(), true, HttpURLConnection.HTTP_OK); for (int i = 0; i < 4; i++) { @@ -498,6 +517,8 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("openviduServerStopped", CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString()); } + Assertions.assertEquals("openviduServerStopped", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("openviduServerStopped", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); for (int i = 0; i < 2; i++) { @@ -523,11 +544,13 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("automaticStop", CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString()); } + Assertions.assertEquals("lastParticipantLeft", + CustomWebhook.waitForEvent("broadcastStopped", 2).get("reason").getAsString()); Assertions.assertEquals("automaticStop", CustomWebhook.waitForEvent("sessionDestroyed", 2).get("reason").getAsString()); CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); - // recordingStoppedByServer + // recordingStoppedByServer: recordingStatusChanged this.connectTwoUsers(user, restClient, false, true, true); String recordingId = restClient .rest(HttpMethod.GET, "/openvidu/api/recordings", HttpURLConnection.HTTP_OK).get("items") @@ -541,8 +564,15 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertEquals("recordingStoppedByServer", CustomWebhook.waitForEvent("recordingStatusChanged", 4).get("reason").getAsString()); } + CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); - // [broadcastStoppedByServer] + // broadcastStoppedByServer: broadcastStopped + this.connectTwoUsers(user, restClient, false, true, true); + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/stop", "{'session':'TestSession'}", + HttpURLConnection.HTTP_OK); + Assertions.assertEquals("broadcastStoppedByServer", + CustomWebhook.waitForEvent("broadcastStopped", 5).get("reason").getAsString()); + CustomWebhook.events.values().forEach(collection -> Assertions.assertTrue(collection.isEmpty())); } finally { Map oldConfig = new HashMap<>(); @@ -557,6 +587,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { } } finally { + stopRtmpServer(); CustomWebhook.shutDown(); } } @@ -1053,6 +1084,54 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { Assertions.assertTrue(connection.adaptativeBitrate() == null, "Wrong adaptativeBitrate property"); Assertions.assertTrue(connection.onlyPlayWithSubscribers() == null, "Wrong onlyPlayWithSubscribers property"); Assertions.assertTrue(connection.getNetworkCache() == null, "Wrong networkCache property"); + + try { + startRtmpServer(); + // Start broadcast + try { + OV.startBroadcast("NOT_EXISTS", "rtmp://172.17.0.1/live"); + Assertions.fail("Expected OpenViduHttpException exception"); + } catch (OpenViduHttpException exception) { + Assertions.assertEquals(HttpURLConnection.HTTP_NOT_FOUND, exception.getStatus(), "Wrong HTTP status"); + } + try { + OV.startBroadcast(session.getSessionId(), "rtmp://172.17.0.1/live"); + Assertions.fail("Expected OpenViduHttpException exception"); + } catch (OpenViduHttpException exception) { + Assertions.assertEquals(HttpURLConnection.HTTP_NOT_ACCEPTABLE, exception.getStatus(), + "Wrong HTTP status"); + } + OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.className("join-btn")).click(); + user.getEventManager().waitUntilEventReaches("streamCreated", 1); + user.getEventManager().waitUntilEventReaches("streamPlaying", 1); + + Assertions.assertTrue(OV.fetch()); + session = OV.getActiveSession("TestSession"); + Assertions.assertFalse(session.fetch()); + + OV.startBroadcast("TestSession", "rtmp://172.17.0.1/live", + new RecordingProperties.Builder().resolution("1280x800").build()); + user.getEventManager().waitUntilEventReaches("broadcastStarted", 1); + Assertions.assertFalse(session.fetch()); + Assertions.assertTrue(session.isBeingBroadcasted()); + + // Stop broadcast + try { + OV.stopBroadcast("NOT_EXISTS"); + Assertions.fail("Expected OpenViduHttpException exception"); + } catch (OpenViduHttpException exception) { + Assertions.assertEquals(HttpURLConnection.HTTP_NOT_FOUND, exception.getStatus(), "Wrong HTTP status"); + } + OV.stopBroadcast("TestSession"); + user.getEventManager().waitUntilEventReaches("broadcastStopped", 1); + Assertions.assertFalse(session.fetch()); + Assertions.assertFalse(session.isBeingBroadcasted()); + + } finally { + stopRtmpServer(); + } } @Test @@ -1068,8 +1147,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); user.getDriver().findElement(By.id("add-user-btn")).click(); user.getDriver().findElement(By.className("join-btn")).click(); - - user.getEventManager().waitUntilEventReaches("connectionCreated", 1); + user.getEventManager().waitUntilEventReaches("streamCreated", 1); user.getEventManager().waitUntilEventReaches("streamPlaying", 1); CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET); @@ -2721,6 +2799,240 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { } } + @Test + @DisplayName("Successfull broadcast Test") + void sucessfullBroadcastTest() throws Exception { + + log.info("Successfull broadcast Test"); + + try { + startRtmpServer(); + + OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); + user.getDriver().findElement(By.id("add-user-btn")).click(); + + user.getDriver().findElement(By.id("session-api-btn-0")).click(); + Thread.sleep(750); + WebElement broadcastUrlField = user.getDriver().findElement(By.id("broadcasturl-id-field")); + broadcastUrlField.clear(); + broadcastUrlField.sendKeys("rtmp://172.17.0.1/live"); + user.getDriver().findElement(By.id("start-broadcast-btn")).click(); + user.getWaiter() + .until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value", "Error [404]")); + + user.getDriver().findElement(By.id("list-sessions-btn")).click(); + user.getWaiter().until(ExpectedConditions.attributeContains(By.id("api-response-text-area"), "value", + "Number: 0. Changes: false")); + + user.getDriver().findElement(By.className("join-btn")).sendKeys(Keys.ENTER); + user.getEventManager().waitUntilEventReaches("streamCreated", 1); + user.getEventManager().waitUntilEventReaches("streamPlaying", 1); + + user.getDriver().findElement(By.id("list-sessions-btn")).click(); + user.getWaiter().until(ExpectedConditions.attributeContains(By.id("api-response-text-area"), "value", + "Number: 1. Changes: true")); + + user.getDriver().findElement(By.id("start-broadcast-btn")).click(); + user.getWaiter().until( + ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value", "Broadcast started")); + user.getEventManager().waitUntilEventReaches("broadcastStarted", 1); + + user.getDriver().findElement(By.id("list-sessions-btn")).click(); + user.getWaiter().until(ExpectedConditions.attributeContains(By.id("api-response-text-area"), "value", + "Number: 1. Changes: false")); + + checkRtmpRecordingIsFine(30); + + user.getDriver().findElement(By.id("stop-broadcast-btn")).click(); + user.getWaiter().until( + ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value", "Broadcast stopped")); + user.getEventManager().waitUntilEventReaches("broadcastStopped", 1); + + user.getDriver().findElement(By.id("list-sessions-btn")).click(); + user.getWaiter().until(ExpectedConditions.attributeContains(By.id("api-response-text-area"), "value", + "Number: 1. Changes: false")); + + gracefullyLeaveParticipants(user, 1); + + } finally { + stopRtmpServer(); + } + } + + @Test + @DisplayName("Wrong broadcast Test") + void wrongBroadcastTest() throws Exception { + + log.info("Wrong broadcast Test"); + + try { + startRtmpServer(); + + OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.className("join-btn")).click(); + user.getEventManager().waitUntilEventReaches("streamCreated", 1); + user.getEventManager().waitUntilEventReaches("streamPlaying", 1); + + /** Start broadcast **/ + CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET); + // 400 + String body = "{}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, HttpURLConnection.HTTP_BAD_REQUEST); + body = "{'session':'TestSession'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, HttpURLConnection.HTTP_BAD_REQUEST); + body = "{'broadcastUrl':'rtmp://172.17.0.1/live'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, HttpURLConnection.HTTP_BAD_REQUEST); + body = "{'session':false,'broadcastUrl':'rtmp://172.17.0.1/live'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, HttpURLConnection.HTTP_BAD_REQUEST); + body = "{'session':'TestSession','broadcastUrl':123}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, HttpURLConnection.HTTP_BAD_REQUEST); + body = "{'session':'TestSession','broadcastUrl':'NOT_A_URL'}"; + restClient.commonRestString(HttpMethod.POST, "/openvidu/api/broadcast/start", body, + HttpURLConnection.HTTP_BAD_REQUEST); + // 404 + body = "{'session':'NOT_EXISTS','broadcastUrl':'rtmp://172.17.0.1/live'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, HttpURLConnection.HTTP_NOT_FOUND); + // 406 + String notActiveSessionId = restClient + .rest(HttpMethod.POST, "/openvidu/api/sessions", body, HttpURLConnection.HTTP_OK).get("id") + .getAsString(); + body = "{'session':'" + notActiveSessionId + "','broadcastUrl':'rtmp://172.17.0.1/live'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, + HttpURLConnection.HTTP_NOT_ACCEPTABLE); + // 422 + body = "{'session':'TestSession','broadcastUrl':'rtmp://172.17.0.1/live','resolution':'99x1280'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", body, 422); + // 500 (Connection refused) + body = "{'session':'TestSession','broadcastUrl':'rtmps://172.17.0.1/live'}"; + String errorResponse = restClient.commonRestString(HttpMethod.POST, "/openvidu/api/broadcast/start", body, + HttpURLConnection.HTTP_INTERNAL_ERROR); + Assertions.assertTrue( + errorResponse.contains("Cannot open connection") + && errorResponse.contains("rtmps://172.17.0.1/live: Connection refused"), + "Broadcast error message does not contain expected message"); + // 500 (Input/output error) + body = "{'session':'TestSession','broadcastUrl':'rtmp://not.exists'}"; + errorResponse = restClient.commonRestString(HttpMethod.POST, "/openvidu/api/broadcast/start", body, + HttpURLConnection.HTTP_INTERNAL_ERROR); + Assertions.assertTrue(errorResponse.contains("rtmp://not.exists: Input/output error"), + "Broadcast error message does not contain expected message"); + // 500 (Protocol not found) + body = "{'session':'TestSession','broadcastUrl':'schemefail://172.17.0.1/live'}"; + errorResponse = restClient.commonRestString(HttpMethod.POST, "/openvidu/api/broadcast/start", body, + HttpURLConnection.HTTP_INTERNAL_ERROR); + Assertions.assertTrue(errorResponse.contains("schemefail://172.17.0.1/live: Protocol not found"), + "Broadcast error message does not contain expected message"); + // Concurrent broadcast + final int PETITIONS = 10; + List responses = new ArrayList<>(); + List exception = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(PETITIONS); + body = "{'session':'TestSession','broadcastUrl':'rtmp://172.17.0.1/live'}"; + for (int i = 0; i < PETITIONS; i++) { + new Thread(() -> { + try { + String response = restClient.commonRestString(HttpMethod.POST, "/openvidu/api/broadcast/start", + "{'session':'TestSession','broadcastUrl':'rtmp://172.17.0.1/live'}", + HttpURLConnection.HTTP_OK); + responses.add(response); + } catch (Exception e) { + // 409 + exception.add(e); + } + latch.countDown(); + }).start(); + } + if (!latch.await(30, TimeUnit.SECONDS)) { + Assertions.fail("Concurrent start of broadcasts did not return in timeout"); + } + Assertions.assertEquals(PETITIONS - 1, exception.size(), "Wrong number of councurrent started broadcasts"); + // 409 + restClient.commonRestString(HttpMethod.POST, "/openvidu/api/broadcast/start", body, + HttpURLConnection.HTTP_CONFLICT); + + user.getEventManager().waitUntilEventReaches("broadcastStarted", 1); + checkRtmpRecordingIsFine(30); + + /** Stop broadcast **/ + // 400 + body = "{}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/stop", body, HttpURLConnection.HTTP_BAD_REQUEST); + body = "{'session':123}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/stop", body, HttpURLConnection.HTTP_BAD_REQUEST); + // 404 + body = "{'session':'NOT_EXISTS'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/stop", body, HttpURLConnection.HTTP_NOT_FOUND); + // 200 + body = "{'session':'TestSession'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/stop", body, HttpURLConnection.HTTP_OK); + user.getEventManager().waitUntilEventReaches("broadcastStopped", 1); + // 409 + body = "{'session':'TestSession'}"; + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/stop", body, HttpURLConnection.HTTP_CONFLICT); + + gracefullyLeaveParticipants(user, 1); + + } finally { + stopRtmpServer(); + } + } + + @Test + @DisplayName("Broadcast and composed recording test") + void broadcastAndComposedRecordingTest() throws Exception { + + log.info("Broadcast and composed recording test"); + + try { + startRtmpServer(); + + } finally { + stopRtmpServer(); + } + } + + // https://github.com/tiangolo/nginx-rtmp-docker + private void startRtmpServer() throws FileNotFoundException { + File file = ResourceUtils.getFile("classpath:broadcast-nginx.conf"); + String dockerRunCommand = "docker run -d --name broadcast-nginx -p 1935:1935 -v " + file.getAbsolutePath() + + ":/etc/nginx/nginx.conf tiangolo/nginx-rtmp"; + commandLine.executeCommand(dockerRunCommand, 10); + } + + private void stopRtmpServer() { + String dockerRemoveCommand = "docker rm -f broadcast-nginx"; + commandLine.executeCommand(dockerRemoveCommand, 10); + } + + private void checkRtmpRecordingIsFine(long secondsTimeout) throws InterruptedException { + final String broadcastRecordingPath = "/opt/openvidu/recordings"; + final String cleanBroadcastPath = "rm -rf " + broadcastRecordingPath + "/tmp"; + try { + final long startTime = System.currentTimeMillis(); + while (false || ((System.currentTimeMillis() - startTime) < (secondsTimeout * 1000))) { + commandLine.executeCommand(cleanBroadcastPath, 10); + commandLine.executeCommand("docker cp broadcast-nginx:/tmp " + broadcastRecordingPath, 30); + commandLine.executeCommand("ffmpeg -i " + broadcastRecordingPath + "/tmp/*.flv -vframes 1 " + + broadcastRecordingPath + "/tmp/rtmp-screenshot.jpg", 30); + File screenshot = new File(broadcastRecordingPath + "/tmp/rtmp-screenshot.jpg"); + if (screenshot.exists() && screenshot.isFile() && screenshot.length() > 0 && screenshot.canRead()) { + Assertions.assertTrue( + this.recordingUtils.thumbnailIsFine(screenshot, RecordingUtils::checkVideoAverageRgbGreen), + "RTMP screenshot " + screenshot.getAbsolutePath() + " is not fine"); + break; + } + log.info("RTMP screenshot could not be generated yet. Trying again"); + Thread.sleep(1000); + } + if ((System.currentTimeMillis() - startTime) >= (secondsTimeout * 1000)) { + Assertions.fail("Timeout of " + secondsTimeout + " seconds elapsed waiting for RTMP sreenshot"); + } + } finally { + commandLine.executeCommand(cleanBroadcastPath, 10); + } + } + private String getOwnConnectionId(OpenViduTestappUser user, int numberOfUser) { return user.getWaiter().until(d -> { List firstOpenviduEvent = d.findElements(By.cssSelector( @@ -2935,7 +3247,10 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { CustomWebhook.waitForEvent("recordingStatusChanged", 3).get("status").getAsString()); } if (startBroadcast) { - + restClient.rest(HttpMethod.POST, "/openvidu/api/broadcast/start", + "{'session':'TestSession','broadcastUrl':'rtmp://172.17.0.1/live'}", HttpURLConnection.HTTP_OK); + user.getEventManager().waitUntilEventReaches("broadcastStarted", 2); + CustomWebhook.waitForEvent("broadcastStarted", 3); } } diff --git a/openvidu-test-e2e/src/test/resources/broadcast-nginx.conf b/openvidu-test-e2e/src/test/resources/broadcast-nginx.conf new file mode 100644 index 00000000..2c25e75d --- /dev/null +++ b/openvidu-test-e2e/src/test/resources/broadcast-nginx.conf @@ -0,0 +1,19 @@ +worker_processes auto; +rtmp_auto_push on; +events {} +rtmp { + server { + listen 1935; + listen [::]:1935 ipv6only=on; + application live { + live on; + recorder all { + record video; + record_path /tmp; + record_max_size 100000K; + record_unique on; + record_suffix rtmp.flv; + } + } + } +} \ No newline at end of file