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 462710d5..37c06607 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 @@ -668,8 +668,8 @@ public class SessionRestController { public ResponseEntity publishIpcam(@PathVariable("sessionId") String sessionId, @RequestBody Map params) { if (params == null) { - return this.generateErrorResponse("Error in body parameters. Cannot be empty", "/api/rtsp", - HttpStatus.BAD_REQUEST); + return this.generateErrorResponse("Error in body parameters. Cannot be empty", + "/api/sessions/" + sessionId + "/connection", HttpStatus.BAD_REQUEST); } log.info("REST API: POST /api/sessions/{}/connection {}", sessionId, params.toString()); diff --git a/openvidu-test-e2e/docker/bionic/Dockerfile b/openvidu-test-e2e/docker/bionic/Dockerfile index 1e9df6fa..27a75762 100644 --- a/openvidu-test-e2e/docker/bionic/Dockerfile +++ b/openvidu-test-e2e/docker/bionic/Dockerfile @@ -9,7 +9,7 @@ RUN apt-get update && apt-get -y upgrade RUN apt-get install -y software-properties-common && apt-get install -y --no-install-recommends apt-utils # Install Kurento Media Server (KMS) -RUN echo "deb [arch=amd64] http://ubuntu.openvidu.io/6.11.0 bionic kms6" | tee /etc/apt/sources.list.d/kurento.list \ +RUN echo "deb [arch=amd64] http://ubuntu.openvidu.io/6.12.0 bionic kms6" | tee /etc/apt/sources.list.d/kurento.list \ && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 5AFA7A83 \ && apt-get update \ && apt-get -y install kurento-media-server diff --git a/openvidu-test-e2e/docker/xenial/Dockerfile b/openvidu-test-e2e/docker/xenial/Dockerfile index 55e7edb8..aa4c7143 100644 --- a/openvidu-test-e2e/docker/xenial/Dockerfile +++ b/openvidu-test-e2e/docker/xenial/Dockerfile @@ -9,7 +9,7 @@ RUN apt-get update && apt-get -y upgrade RUN apt-get install -y software-properties-common && apt-get install -y --no-install-recommends apt-utils # Install Kurento Media Server (KMS) -RUN echo "deb [arch=amd64] http://ubuntu.openvidu.io/6.11.0 xenial kms6" | tee /etc/apt/sources.list.d/kurento.list \ +RUN echo "deb [arch=amd64] http://ubuntu.openvidu.io/6.12.0 xenial kms6" | tee /etc/apt/sources.list.d/kurento.list \ && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 5AFA7A83 \ && apt-get update \ && apt-get -y install kurento-media-server diff --git a/openvidu-test-e2e/jenkins/Jenkinsfile b/openvidu-test-e2e/jenkins/Jenkinsfile index b109223b..7de49c33 100644 --- a/openvidu-test-e2e/jenkins/Jenkinsfile +++ b/openvidu-test-e2e/jenkins/Jenkinsfile @@ -47,10 +47,10 @@ node('container') { sh 'cd openvidu/openvidu-node-client && npm install --unsafe-perm && npm run build && npm link' } stage('OpenVidu TestApp build') { - sh 'cd openvidu/openvidu-testapp && npm install --unsafe-perm && npm link openvidu-browser && npm link openvidu-node-client && ./node_modules/@angular/cli/bin/ng build --prod' + sh 'cd openvidu/openvidu-testapp && npm install --unsafe-perm && npm link openvidu-browser && npm link openvidu-node-client && export NG_CLI_ANALYTICS=ci && ./node_modules/@angular/cli/bin/ng build --prod' } stage('OpenVidu Server build') { - sh 'cd openvidu/openvidu-server/src/dashboard && npm install --unsafe-perm && npm link openvidu-browser && ./node_modules/@angular/cli/bin/ng build --prod --output-path ../main/resources/static' + sh 'cd openvidu/openvidu-server/src/dashboard && npm install --unsafe-perm && npm link openvidu-browser && export NG_CLI_ANALYTICS=ci && ./node_modules/@angular/cli/bin/ng build --prod --output-path ../main/resources/static' sh 'cd openvidu/openvidu-server && mvn --batch-mode clean compile package' } stage ('Environment Launch') { diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 06f917ec..ffc35cf1 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -2752,7 +2752,9 @@ public class OpenViduTestAppE2eTest { WebElement pubBtn = user.getDriver().findElements(By.cssSelector("#openvidu-instance-1 .pub-btn")).get(0); pubBtn.click(); - user.getEventManager().waitUntilEventReaches("streamDestroyed", 3); // This is not real, only in testapp + // This is not real, only in testapp (user is not streaming media after KMS + // restarted) + user.getEventManager().waitUntilEventReaches("streamDestroyed", 3); pubBtn.click(); user.getEventManager().waitUntilEventReaches("streamCreated", 4); user.getEventManager().waitUntilEventReaches("streamPlaying", 4); @@ -2783,7 +2785,7 @@ public class OpenViduTestAppE2eTest { try { if (!initLatch.await(30, TimeUnit.SECONDS)) { - Assert.fail("Tiemout waiting for webhook springboot app to start"); + Assert.fail("Timeout waiting for webhook springboot app to start"); CustomWebhook.shutDown(); return; } @@ -2923,6 +2925,148 @@ public class OpenViduTestAppE2eTest { } } + @Test + @DisplayName("IP camera test") + void ipCameraTest() throws Exception { + isRecordingTest = true; + + log.info("IP camera test"); + + CountDownLatch initLatch = new CountDownLatch(1); + io.openvidu.test.browsers.utils.CustomWebhook.main(new String[0], initLatch); + + try { + + if (!initLatch.await(30, TimeUnit.SECONDS)) { + Assert.fail("Timeout waiting for webhook springboot app to start"); + CustomWebhook.shutDown(); + return; + } + + CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET); + + // Wrong session [404] + restClient.rest(HttpMethod.POST, "/api/sessions/WRONG_SESSION/connection", "{}", HttpStatus.SC_NOT_FOUND); + + // Init a session and publish IP camera AS FIRST PARTICIPANT + restClient.rest(HttpMethod.POST, "/api/sessions", "{'customSessionId':'IP_CAM_SESSION'}", HttpStatus.SC_OK, + true, "{'id': 'STR', 'createdAt': 0}"); + + // No rtspUri [400] + restClient.rest(HttpMethod.POST, "/api/sessions/IP_CAM_SESSION/connection", "{}", + HttpStatus.SC_BAD_REQUEST); + + // Wrong rtspUri (invalid url format) [400] + restClient.rest(HttpMethod.POST, "/api/sessions/IP_CAM_SESSION/connection", "{'rtspUri': 'NOT_A_URL'}", + HttpStatus.SC_BAD_REQUEST); + + // Publish IP camera. Dummy URL because no user will subscribe to it [200] + String ipCamBody = "{'type':'IPCAM','rtspUri':'rtsp://dummyurl.com','adaptativeBitrate':true,'onlyPlayWithSubscribers':true,'data':'MY_IP_CAMERA'}"; + org.json.JSONObject response = restClient.rest(HttpMethod.POST, "/api/sessions/IP_CAM_SESSION/connection", + ipCamBody, HttpStatus.SC_OK, true, + "{'connectionId':'STR','createdAt':0,'location':'STR','platform':'STR','token':'STR','role':'STR','serverData':'STR','clientData':'STR','publishers':[],'subscribers':[]}"); + + CustomWebhook.waitForEvent("sessionCreated", 1); + CustomWebhook.waitForEvent("participantJoined", 1); + CustomWebhook.waitForEvent("webrtcConnectionCreated", 1); + + Assert.assertEquals("Wrong serverData property", "MY_IP_CAMERA", response.get("serverData")); + Assert.assertEquals("Wrong platform property", "IPCAM", response.get("platform")); + Assert.assertEquals("Wrong role property", "PUBLISHER", response.get("role")); + + Assert.assertEquals("Wrong number of publishers in IPCAM participant", 1, + response.getJSONArray("publishers").length()); + org.json.JSONObject ipCamPublisher = response.getJSONArray("publishers").getJSONObject(0); + Assert.assertEquals("Wrong number of properties in IPCAM publisher", 4, ipCamPublisher.length()); + Assert.assertEquals("Wrong rtspUri property", "rtsp://dummyurl.com", ipCamPublisher.get("rtspUri")); + org.json.JSONObject mediaOptions = ipCamPublisher.getJSONObject("mediaOptions"); + Assert.assertEquals("Wrong number of properties in MediaOptions", 10, mediaOptions.length()); + Assert.assertTrue("Wrong adaptativeBitrate property", mediaOptions.getBoolean("adaptativeBitrate")); + Assert.assertTrue("Wrong onlyPlayWithSubscribers property", + mediaOptions.getBoolean("onlyPlayWithSubscribers")); + + // Can't delete the stream [405] + restClient.rest(HttpMethod.DELETE, + "/api/sessions/IP_CAM_SESSION/stream/" + ipCamPublisher.getString("streamId"), + HttpStatus.SC_METHOD_NOT_ALLOWED); + + // Can delete the connection [204] + restClient.rest(HttpMethod.DELETE, + "/api/sessions/IP_CAM_SESSION/connection/" + response.getString("connectionId"), + HttpStatus.SC_NO_CONTENT); + + CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 1); + CustomWebhook.waitForEvent("participantLeft", 1); + CustomWebhook.waitForEvent("sessionDestroyed", 1); + + setupBrowser("chrome"); + + // Record a session to get an MP4 file + + 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("accessAllowed", 1); + user.getEventManager().waitUntilEventReaches("streamCreated", 1); + user.getEventManager().waitUntilEventReaches("streamPlaying", 1); + final int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size(); + Assert.assertEquals("Expected 1 video but found " + numberOfVideos, 1, numberOfVideos); + Assert.assertTrue("Video was expected to have audio and video tracks", user.getEventManager() + .assertMediaTracks(user.getDriver().findElements(By.tagName("video")), true, true)); + + CustomWebhook.waitForEvent("sessionCreated", 1); + CustomWebhook.waitForEvent("participantJoined", 1); + CustomWebhook.waitForEvent("webrtcConnectionCreated", 1); + + restClient.rest(HttpMethod.POST, "/api/recordings/start", "{'session':'TestSession'}", HttpStatus.SC_OK); + user.getEventManager().waitUntilEventReaches("recordingStarted", 1); // Started + CustomWebhook.waitForEvent("recordingStatusChanged", 1); + Thread.sleep(4000); + restClient.rest(HttpMethod.POST, "/api/recordings/stop/TestSession", HttpStatus.SC_OK); + user.getEventManager().waitUntilEventReaches("recordingStopped", 1); + CustomWebhook.waitForEvent("recordingStatusChanged", 1); // Stopped + CustomWebhook.waitForEvent("recordingStatusChanged", 1); // Ready + + String recPath = restClient.rest(HttpMethod.GET, "/config", HttpStatus.SC_OK) + .getString("openviduRecordingPath"); + recPath = recPath.endsWith("/") ? recPath : (recPath + "/"); + String fullRecordingPath = "file://" + recPath + "TestSession/TestSession.mp4"; + ipCamBody = "{'type':'IPCAM','rtspUri':'" + fullRecordingPath + + "','adaptativeBitrate':true,'onlyPlayWithSubscribers':true,'data':'MY_IP_CAMERA'}"; + + response = restClient.rest(HttpMethod.POST, "/api/sessions/TestSession/connection", ipCamBody, + HttpStatus.SC_OK, true, + "{'connectionId':'STR','createdAt':0,'location':'STR','platform':'STR','token':'STR','role':'STR','serverData':'STR','clientData':'STR','publishers':[],'subscribers':[]}"); + + user.getEventManager().waitUntilEventReaches("connectionCreated", 2); + user.getEventManager().waitUntilEventReaches("streamCreated", 2); + user.getEventManager().waitUntilEventReaches("streamPlaying", 2); + + CustomWebhook.waitForEvent("participantJoined", 1); + CustomWebhook.waitForEvent("webrtcConnectionCreated", 1); + CustomWebhook.waitForEvent("webrtcConnectionCreated", 1); + + // Removing browser user shouldn't close the session if IP cam participant + // remains + + user.getDriver().findElement(By.id("remove-user-btn")).click(); + user.getEventManager().waitUntilEventReaches("sessionDisconnected", 1); + + CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 1); + CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 1); + CustomWebhook.waitForEvent("participantLeft", 1); + + restClient.rest(HttpMethod.DELETE, "/api/sessions/TestSession", HttpStatus.SC_NO_CONTENT); + + CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 1); + CustomWebhook.waitForEvent("participantLeft", 1); + CustomWebhook.waitForEvent("sessionDestroyed", 1); + + } finally { + CustomWebhook.shutDown(); + } + } + private void listEmptyRecordings() { // List existing recordings (empty) user.getDriver().findElement(By.id("list-recording-btn")).click();