Broadcast SDKs. Broadcast tests in OpenViduProTestaAppE2eTest

pull/780/head
pabloFuente 2023-02-16 14:43:00 +01:00
parent ca5887ac06
commit bd23cfcd71
6 changed files with 649 additions and 42 deletions

View File

@ -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 <a href=
* "/en/stable/reference-docs/REST-API/#start-broadcast">REST
* API</a>)
*/
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}:
* <ul>
* <li>{@link RecordingProperties.Builder#hasAudio(boolean)}</li>
* <li>{@link RecordingProperties.Builder#resolution(String)}</li>
* <li>{@link RecordingProperties.Builder#frameRate(int)}</li>
* <li>{@link RecordingProperties.Builder#recordingLayout(RecordingLayout)}</li>
* <li>{@link RecordingProperties.Builder#customLayout(String)}</li>
* <li>{@link RecordingProperties.Builder#shmSize(long)}</li>
* <li>{@link RecordingProperties.Builder#mediaNode(String)}</li>
* </ul>
*
*
* @throws OpenViduJavaClientException
* @throws OpenViduHttpException The status code carries a specific
* meaning
* {@link io.openvidu.java.client.OpenViduHttpException#getStatus()}
* (see <a href=
* "/en/stable/reference-docs/REST-API/#start-broadcast">REST
* API</a>)
*/
public void startBroadcast(String sessionId, String broadcastUrl, RecordingProperties properties)
throws OpenViduJavaClientException, OpenViduHttpException {
final HttpClientResponseHandler<Void> responseHandler = new HttpClientResponseHandler<Void>() {
@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 <a href=
* "/en/stable/reference-docs/REST-API/#stop-broadcast">REST
* API</a>)
*/
public void stopBroadcast(String sessionId) throws OpenViduJavaClientException, OpenViduHttpException {
final HttpClientResponseHandler<Void> responseHandler = new HttpClientResponseHandler<Void>() {
@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. <strong>This value will remain unchanged
* since the last time method {@link io.openvidu.java.client.OpenVidu#fetch()}

View File

@ -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<void>;
public startBroadcast(sessionId: string, broadcastUrl: string, properties: RecordingProperties): Promise<void>;
/**
* 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<void> {
return new Promise<void>((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<void> {
return new Promise<void>((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}

View File

@ -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<String, ?> 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();
}

View File

@ -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));

View File

@ -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<String, Object> newConfig = Map.of("OPENVIDU_WEBHOOK", true, "OPENVIDU_WEBHOOK_ENDPOINT",
"http://127.0.0.1:7777/webhook", "OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", 0);
Map<String, Object> 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<String, Object> 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<String> responses = new ArrayList<>();
List<Exception> 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<WebElement> 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);
}
}

View File

@ -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;
}
}
}
}