README steps presentation improved
This commit is contained in:
parent
6a70ef515b
commit
84e5c64d9d
@ -70,345 +70,352 @@ This is a very basic web application with a pretty simple vanilla JS/HTML/CSS fr
|
||||
|
||||
Let's describe the code following this scenario: a user logs in to the app and connects to the video-call "TUTORIAL", where he publishes his webcam. A second user will connect to the same video-call just after that and publish its own webcam. Both of them will leave the call after a while.
|
||||
|
||||
1. **User logs in**
|
||||
---
|
||||
|
||||
We have implemented a method for making HTTP requests to the backend, as we will need to make at least three of them: one for logging in, one for getting the sessionId and a valid token from openvidu-server and a one for letting know our backend when any user leaves the video-call. The header of the method looks like this:
|
||||
|
||||
```javascript
|
||||
function httpRequest(method, url, body, errorMsg, callback)
|
||||
```
|
||||
|
||||
Where `method` is whether "POST" or "GET", `url` the path of the REST operation, `body` the data to be passed, `errorMsg` the output error message if something goes wrong and `callback` the function to execute in case of success. As mentioned above, we need to call this method three times for each user that LOGS IN 🡒 CONNECTS TO A VIDEO-CALL 🡒 LEAVES THE VIDEO-CALL.
|
||||
### 1) User logs in
|
||||
|
||||
`index.html` will first show a form to log in:
|
||||
We have implemented a method for making HTTP requests to the backend, as we will need to make at least three of them: one for logging in, one for getting the sessionId and a valid token from openvidu-server and a one for letting know our backend when any user leaves the video-call. The header of the method looks like this:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
</p>
|
||||
```javascript
|
||||
function httpRequest(method, url, body, errorMsg, callback)
|
||||
```
|
||||
|
||||
`app.js` sends an HTTP request to "/api-login/login" passing the username and the password retrieved from the HTML form whenever "Log in" button is clicked:
|
||||
|
||||
```javascript
|
||||
function logIn() {
|
||||
var user = $("#user").val(); // Username
|
||||
var pass = $("#pass").val(); // Password
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'user': user,
|
||||
'pass': pass
|
||||
});
|
||||
|
||||
httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
|
||||
function successCallback(response){ // Send POST request
|
||||
console.warn(userName + ' login');
|
||||
// HTML shows logged-in page ...
|
||||
});
|
||||
}
|
||||
```
|
||||
Where `method` is whether "POST" or "GET", `url` the path of the REST operation, `body` the data to be passed, `errorMsg` the output error message if something goes wrong and `callback` the function to execute in case of success. As mentioned above, we need to call this method three times for each user that LOGS IN 🡒 CONNECTS TO A VIDEO-CALL 🡒 LEAVES THE VIDEO-CALL.
|
||||
|
||||
`LoginController.java` checks the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object):
|
||||
`index.html` will first show a form to log in:
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/api-login/login", method = RequestMethod.POST)
|
||||
public ResponseEntity<Object> login(@RequestBody String userPass, HttpSession httpSession)
|
||||
throws ParseException {
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
</p>
|
||||
|
||||
// Retrieve params from POST body
|
||||
JSONObject userPassJson = (JSONObject) new JSONParser().parse(userPass);
|
||||
String user = (String) userPassJson.get("user");
|
||||
String pass = (String) userPassJson.get("pass");
|
||||
`app.js` sends an HTTP request to "/api-login/login" passing the username and the password retrieved from the HTML form whenever "Log in" button is clicked:
|
||||
|
||||
if (login(user, pass)){ // Correct user-pass
|
||||
// Validate session and return OK
|
||||
// Value stored in HttpSession allows us to identify the user in future requests
|
||||
httpSession.setAttribute("loggedUser", user);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and return error
|
||||
httpSession.invalidate();
|
||||
return new ResponseEntity<>("User/Pass incorrect", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **User connects to "TUTORIAL" video-call**
|
||||
|
||||
HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
</p>
|
||||
|
||||
`app.js` will execute `joinSession()` method, which starts like this:
|
||||
|
||||
```javascript
|
||||
function joinSession() {
|
||||
getSessionIdAndToken(function () { ...
|
||||
```
|
||||
So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
|
||||
|
||||
```javascript
|
||||
function getSessionIdAndToken(callback) {
|
||||
sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'session': sessionName
|
||||
});
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
|
||||
'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
|
||||
sessionId = response[0]; // Get sessionId from response
|
||||
token = response[1]; // Get token from response
|
||||
callback(); // Continue the join operation
|
||||
});
|
||||
}
|
||||
```
|
||||
Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `SessionController.java`. First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```java
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
private OpenVidu openVidu;
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
private Map<String, Session> mapSessions = new ConcurrentHashMap<>();
|
||||
// Collection to pair sessionId's and tokens (the inner Map pairs tokens and role associated)
|
||||
private Map<String, Map<String, OpenViduRole>> mapSessionIdsTokens = new ConcurrentHashMap<>();
|
||||
|
||||
// URL where our OpenVidu server is listening
|
||||
private String OPENVIDU_URL;
|
||||
// Secret shared with our OpenVidu server
|
||||
private String SECRET;
|
||||
```
|
||||
|
||||
Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/api-sessions/get-sessionid-token", method = RequestMethod.POST)
|
||||
public ResponseEntity<JSONObject> getSessionIdToken(@RequestBody String sessionNameParam,
|
||||
HttpSession httpSession) throws ParseException {
|
||||
// Check the user is logged ...
|
||||
|
||||
JSONObject sessionJSON = (JSONObject) new JSONParser().parse(sessionNameParam);
|
||||
|
||||
// The video-call to connect ("TUTORIAL")
|
||||
String sessionName = (String) sessionJSON.get("session");
|
||||
|
||||
// Role associated to this user
|
||||
OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the HttpSession object on login
|
||||
String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
|
||||
|
||||
JSONObject responseJson = new JSONObject();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```java
|
||||
else {
|
||||
// New session: return a new sessionId and a new token
|
||||
try {
|
||||
|
||||
// Create a new OpenVidu Session
|
||||
Session session = this.openVidu.createSession();
|
||||
// Get the sessionId
|
||||
String sessionId = session.getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = session.generateToken(tokenOptions);
|
||||
|
||||
// Store the session and the token in our collections
|
||||
this.mapSessions.put(sessionName, session);
|
||||
this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Prepare the response with the sessionId and the token
|
||||
responseJson.put(0, sessionId);
|
||||
responseJson.put(1, token);
|
||||
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
|
||||
```javascript
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
OV = new OpenVidu();
|
||||
session = OV.initSession(sessionId);
|
||||
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
```javascript
|
||||
function logIn() {
|
||||
var user = $("#user").val(); // Username
|
||||
var pass = $("#pass").val(); // Password
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'user': user,
|
||||
'pass': pass
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
|
||||
httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
|
||||
function successCallback(response){ // Send POST request
|
||||
console.warn(userName + ' login');
|
||||
// HTML shows logged-in page ...
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
`LoginController.java` checks the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object):
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/api-login/login", method = RequestMethod.POST)
|
||||
public ResponseEntity<Object> login(@RequestBody String userPass, HttpSession httpSession)
|
||||
throws ParseException {
|
||||
|
||||
// Retrieve params from POST body
|
||||
JSONObject userPassJson = (JSONObject) new JSONParser().parse(userPass);
|
||||
String user = (String) userPassJson.get("user");
|
||||
String pass = (String) userPassJson.get("pass");
|
||||
|
||||
if (login(user, pass)){ // Correct user-pass
|
||||
// Validate session and return OK
|
||||
// Value stored in HttpSession allows us to identify the user in future requests
|
||||
httpSession.setAttribute("loggedUser", user);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and return error
|
||||
httpSession.invalidate();
|
||||
return new ResponseEntity<>("User/Pass incorrect", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2) User connects to "TUTORIAL" video-call
|
||||
|
||||
HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
</p>
|
||||
|
||||
`app.js` will execute `joinSession()` method, which starts like this:
|
||||
|
||||
```javascript
|
||||
function joinSession() {
|
||||
getSessionIdAndToken(function () { ...
|
||||
```
|
||||
So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
|
||||
|
||||
```javascript
|
||||
function getSessionIdAndToken(callback) {
|
||||
sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'session': sessionName
|
||||
});
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
|
||||
'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
|
||||
sessionId = response[0]; // Get sessionId from response
|
||||
token = response[1]; // Get token from response
|
||||
callback(); // Continue the join operation
|
||||
});
|
||||
}
|
||||
```
|
||||
Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `SessionController.java`. First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```java
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
private OpenVidu openVidu;
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
private Map<String, Session> mapSessions = new ConcurrentHashMap<>();
|
||||
// Collection to pair sessionId's and tokens (the inner Map pairs tokens and role associated)
|
||||
private Map<String, Map<String, OpenViduRole>> mapSessionIdsTokens = new ConcurrentHashMap<>();
|
||||
|
||||
// URL where our OpenVidu server is listening
|
||||
private String OPENVIDU_URL;
|
||||
// Secret shared with our OpenVidu server
|
||||
private String SECRET;
|
||||
```
|
||||
|
||||
Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/api-sessions/get-sessionid-token", method = RequestMethod.POST)
|
||||
public ResponseEntity<JSONObject> getSessionIdToken(@RequestBody String sessionNameParam,
|
||||
HttpSession httpSession) throws ParseException {
|
||||
// Check the user is logged ...
|
||||
|
||||
JSONObject sessionJSON = (JSONObject) new JSONParser().parse(sessionNameParam);
|
||||
|
||||
// The video-call to connect ("TUTORIAL")
|
||||
String sessionName = (String) sessionJSON.get("session");
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
// Role associated to this user
|
||||
OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
|
||||
|
||||
session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the HttpSession object on login
|
||||
String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
|
||||
|
||||
JSONObject responseJson = new JSONObject();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```java
|
||||
else {
|
||||
// New session: return a new sessionId and a new token
|
||||
try {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
// Create a new OpenVidu Session
|
||||
Session session = this.openVidu.createSession();
|
||||
// Get the sessionId
|
||||
String sessionId = session.getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = session.generateToken(tokenOptions);
|
||||
|
||||
// Store the session and the token in our collections
|
||||
this.mapSessions.put(sessionName, session);
|
||||
this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Prepare the response with the sessionId and the token
|
||||
responseJson.put(0, sessionId);
|
||||
responseJson.put(1, token);
|
||||
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
|
||||
```javascript
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
OV = new OpenVidu();
|
||||
session = OV.initSession(sessionId);
|
||||
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher()) {
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
});
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher()) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
|
||||
// HTML shows session page ...
|
||||
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
---
|
||||
|
||||
### 3) Another user connects to the video-call
|
||||
|
||||
The process would be exactly the same as before until `SessionController.java` executes `getSessionIdAndToken()` method. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
try {
|
||||
|
||||
// Get the existing sessionId from our collection with
|
||||
// the sessionName param ("TUTORIAL")
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = this.mapSessions.get(sessionName).generateToken(tokenOptions);
|
||||
|
||||
// HTML shows session page ...
|
||||
// Update our collection storing the new token
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Prepare the response with the sessionId and the token
|
||||
responseJson.put(0, sessionId);
|
||||
responseJson.put(1, token);
|
||||
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
|
||||
---
|
||||
|
||||
### 4) Users leave the video-call
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
|
||||
For the POST operation, in `app.js` we run:
|
||||
|
||||
```javascript
|
||||
function removeUser() {
|
||||
// Body of POST request with the name of the session and the token of the leaving user
|
||||
var jsonBody = JSON.stringify({
|
||||
'sessionName': sessionName,
|
||||
'token': token
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/remove-user', jsonBody,
|
||||
'User couldn\'t be removed from session', function successCallback(response) {
|
||||
console.warn(userName + ' correctly removed from session ' + sessionName);
|
||||
});
|
||||
}
|
||||
```
|
||||
And in `SessionController.java` we update the collections:
|
||||
|
||||
3. **Another user connects to the video-call**
|
||||
```java
|
||||
@RequestMapping(value = "/api-sessions/remove-user", method = RequestMethod.POST)
|
||||
public ResponseEntity<JSONObject> removeUser(@RequestBody String sessionNameToken,
|
||||
HttpSession httpSession) throws Exception {
|
||||
// Check the user is logged ...
|
||||
|
||||
The process would be exactly the same as before until `SessionController.java` executes `getSessionIdAndToken()` method. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
// Retrieve the params from BODY
|
||||
JSONObject sessionNameTokenJSON = (JSONObject) new JSONParser().parse(sessionNameToken);
|
||||
String sessionName = (String) sessionNameTokenJSON.get("sessionName");
|
||||
String token = (String) sessionNameTokenJSON.get("token");
|
||||
|
||||
```java
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
try {
|
||||
|
||||
// Get the existing sessionId from our collection with
|
||||
// the sessionName param ("TUTORIAL")
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = this.mapSessions.get(sessionName).generateToken(tokenOptions);
|
||||
|
||||
// Update our collection storing the new token
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Prepare the response with the sessionId and the token
|
||||
responseJson.put(0, sessionId);
|
||||
responseJson.put(1, token);
|
||||
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
|
||||
4. **Users leave the video-call**
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
|
||||
For the POST operation, in `app.js` we run:
|
||||
|
||||
```javascript
|
||||
function removeUser() {
|
||||
// Body of POST request with the name of the session and the token of the leaving user
|
||||
var jsonBody = JSON.stringify({
|
||||
'sessionName': sessionName,
|
||||
'token': token
|
||||
});
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/remove-user', jsonBody,
|
||||
'User couldn\'t be removed from session', function successCallback(response) {
|
||||
console.warn(userName + ' correctly removed from session ' + sessionName);
|
||||
});
|
||||
}
|
||||
```
|
||||
And in `SessionController.java` we update the collections:
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/api-sessions/remove-user", method = RequestMethod.POST)
|
||||
public ResponseEntity<JSONObject> removeUser(@RequestBody String sessionNameToken,
|
||||
HttpSession httpSession) throws Exception {
|
||||
// Check the user is logged ...
|
||||
|
||||
// Retrieve the params from BODY
|
||||
JSONObject sessionNameTokenJSON = (JSONObject) new JSONParser().parse(sessionNameToken);
|
||||
String sessionName = (String) sessionNameTokenJSON.get("sessionName");
|
||||
String token = (String) sessionNameTokenJSON.get("token");
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
|
||||
if (this.mapSessionIdsTokens.containsKey(sessionId)) {
|
||||
// If the token exists
|
||||
if (this.mapSessionIdsTokens.get(sessionId).remove(token) != null) {
|
||||
// Token has been removed
|
||||
if (this.mapSessionIdsTokens.get(sessionId).isEmpty()) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
this.mapSessions.remove(sessionName);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
} else {
|
||||
// The TOKEN wasn't valid
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
if (this.mapSessionIdsTokens.containsKey(sessionId)) {
|
||||
// If the token exists
|
||||
if (this.mapSessionIdsTokens.get(sessionId).remove(token) != null) {
|
||||
// Token has been removed
|
||||
if (this.mapSessionIdsTokens.get(sessionId).isEmpty()) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
this.mapSessions.remove(sessionName);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
} else {
|
||||
// The SESSIONID wasn't valid
|
||||
// The TOKEN wasn't valid
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
// The SESSION does not exist
|
||||
// The SESSIONID wasn't valid
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
// The SESSION does not exist
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `this.mapSessions.remove(sessionName);` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `this.mapSessions.remove(sessionName);` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -70,261 +70,162 @@ This is a very basic web application with a pretty simple vanilla JS/HTML/CSS fr
|
||||
|
||||
Let's describe the code following this scenario: a user logs in to the app and connects to the video-call "TUTORIAL", where he publishes his webcam. A second user will connect to the same video-call just after that and publish its own webcam. Both of them will leave the call after a while.
|
||||
|
||||
1. **User logs in**
|
||||
---
|
||||
|
||||
We have implemented a method for making HTTP requests to the backend, as we will need to make at least three of them: one for logging in, one for getting the sessionId and a valid token from openvidu-server and a one for letting know our backend when any user leaves the video-call. The header of the method looks like this:
|
||||
|
||||
```javascript
|
||||
function httpRequest(method, url, body, errorMsg, callback)
|
||||
```
|
||||
|
||||
Where `method` is whether "POST" or "GET", `url` the path of the REST operation, `body` the data to be passed, `errorMsg` the output error message if something goes wrong and `callback` the function to execute in case of success. As mentioned above, we need to call this method three times for each user that LOGS IN 🡒 CONNECTS TO A VIDEO-CALL 🡒 LEAVES THE VIDEO-CALL.
|
||||
### 1) User logs in
|
||||
|
||||
`index.html` will first show a form to log in:
|
||||
We have implemented a method for making HTTP requests to the backend, as we will need to make at least three of them: one for logging in, one for getting the sessionId and a valid token from openvidu-server and a one for letting know our backend when any user leaves the video-call. The header of the method looks like this:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
</p>
|
||||
```javascript
|
||||
function httpRequest(method, url, body, errorMsg, callback)
|
||||
```
|
||||
|
||||
`app.js` sends an HTTP request to "/api-login/login" passing the username and the password retrieved from the HTML form whenever "Log in" button is clicked:
|
||||
|
||||
```javascript
|
||||
function logIn() {
|
||||
var user = $("#user").val(); // Username
|
||||
var pass = $("#pass").val(); // Password
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'user': user,
|
||||
'pass': pass
|
||||
});
|
||||
|
||||
httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
|
||||
function successCallback(response){ // Send POST request
|
||||
console.warn(userName + ' login');
|
||||
// HTML shows logged-in page ...
|
||||
});
|
||||
}
|
||||
```
|
||||
Where `method` is whether "POST" or "GET", `url` the path of the REST operation, `body` the data to be passed, `errorMsg` the output error message if something goes wrong and `callback` the function to execute in case of success. As mentioned above, we need to call this method three times for each user that LOGS IN 🡒 CONNECTS TO A VIDEO-CALL 🡒 LEAVES THE VIDEO-CALL.
|
||||
|
||||
`server.js` at `/api-login/login` checks the params are correct and if so sets an active session for the newly logged user (adding a _loggedUser_ property with its username in the _req.session_ object):
|
||||
`index.html` will first show a form to log in:
|
||||
|
||||
```javascript
|
||||
app.post('/api-login/login', function (req, res) {
|
||||
|
||||
// Retrieve params from POST body
|
||||
var user = req.body.user;
|
||||
var pass = req.body.pass;
|
||||
|
||||
if (login(user, pass)) { // Correct user-pass
|
||||
// Validate session and return OK
|
||||
// Value stored in req.session allows us to identify the user in future requests
|
||||
req.session.loggedUser = user;
|
||||
res.status(200).send();
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and return error
|
||||
req.session.destroy();
|
||||
res.status(401).send('User/Pass incorrect');
|
||||
}
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
</p>
|
||||
|
||||
`app.js` sends an HTTP request to "/api-login/login" passing the username and the password retrieved from the HTML form whenever "Log in" button is clicked:
|
||||
|
||||
```javascript
|
||||
function logIn() {
|
||||
var user = $("#user").val(); // Username
|
||||
var pass = $("#pass").val(); // Password
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'user': user,
|
||||
'pass': pass
|
||||
});
|
||||
```
|
||||
|
||||
2. **User connects to "TUTORIAL" video-call**
|
||||
|
||||
HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
</p>
|
||||
|
||||
`app.js` will execute `joinSession()` method, which starts like this:
|
||||
|
||||
```javascript
|
||||
function joinSession() {
|
||||
getSessionIdAndToken(function () { ...
|
||||
```
|
||||
So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
|
||||
|
||||
```javascript
|
||||
function getSessionIdAndToken(callback) {
|
||||
sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'session': sessionName
|
||||
});
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
|
||||
'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
|
||||
sessionId = response[0]; // Get sessionId from response
|
||||
token = response[1]; // Get token from response
|
||||
callback(); // Continue the join operation
|
||||
});
|
||||
}
|
||||
```
|
||||
Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `server.js` controller at `/api-sessions/get-sessionid-token`. First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```javascript
|
||||
// Environment variable: URL where our OpenVidu server is listening
|
||||
var OPENVIDU_URL = process.argv[2];
|
||||
// Environment variable: secret shared with our OpenVidu server
|
||||
var OPENVIDU_SECRET = process.argv[3];
|
||||
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
var mapSessionNameSession = {};
|
||||
// Collection to pair sessionId's (identifiers of Session objects) and tokens
|
||||
var mapSessionIdTokens = {};
|
||||
```
|
||||
|
||||
Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
|
||||
```javascript
|
||||
app.post('/api-sessions/get-sessionid-token', function (req, res) {
|
||||
// Check the user is logged ...
|
||||
|
||||
// The video-call to connect ("TUTORIAL")
|
||||
var sessionName = req.body.session;
|
||||
|
||||
// Role associated to this user
|
||||
var role = users.find(u => (u.user === req.session.loggedUser)).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the req.session object on login
|
||||
var serverData = '{"serverData": "' + req.session.loggedUser + '"}';
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
var tokenOptions = new TokenOptions.Builder()
|
||||
.data(serverData)
|
||||
.role(role)
|
||||
.build();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```javascript
|
||||
else { // New session: return a new sessionId and a new token
|
||||
// Create a new OpenVidu Session
|
||||
var mySession = OV.createSession();
|
||||
|
||||
// Get the sessionId asynchronously
|
||||
mySession.getSessionId(function (sessionId) {
|
||||
|
||||
// Store the new Session in the collection of Sessions
|
||||
mapSessionNameSession[sessionName] = mySession;
|
||||
// Store a new empty array in the collection of tokens
|
||||
mapSessionIdTokens[sessionId] = [];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
mySession.generateToken(tokenOptions, function (token) {
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionIdTokens[sessionId].push(token);
|
||||
|
||||
// Return the sessionId and token to the client
|
||||
res.status(200).send({
|
||||
0: sessionId,
|
||||
1: token
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
|
||||
```javascript
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
OV = new OpenVidu();
|
||||
session = OV.initSession(sessionId);
|
||||
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
|
||||
function successCallback(response){ // Send POST request
|
||||
console.warn(userName + ' login');
|
||||
// HTML shows logged-in page ...
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
}
|
||||
```
|
||||
|
||||
`server.js` at `/api-login/login` checks the params are correct and if so sets an active session for the newly logged user (adding a _loggedUser_ property with its username in the _req.session_ object):
|
||||
|
||||
```javascript
|
||||
app.post('/api-login/login', function (req, res) {
|
||||
|
||||
// Retrieve params from POST body
|
||||
var user = req.body.user;
|
||||
var pass = req.body.pass;
|
||||
|
||||
if (login(user, pass)) { // Correct user-pass
|
||||
// Validate session and return OK
|
||||
// Value stored in req.session allows us to identify the user in future requests
|
||||
req.session.loggedUser = user;
|
||||
res.status(200).send();
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and return error
|
||||
req.session.destroy();
|
||||
res.status(401).send('User/Pass incorrect');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2) User connects to "TUTORIAL" video-call
|
||||
|
||||
HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
</p>
|
||||
|
||||
`app.js` will execute `joinSession()` method, which starts like this:
|
||||
|
||||
```javascript
|
||||
function joinSession() {
|
||||
getSessionIdAndToken(function () { ...
|
||||
```
|
||||
So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
|
||||
|
||||
```javascript
|
||||
function getSessionIdAndToken(callback) {
|
||||
sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
|
||||
var jsonBody = JSON.stringify({ // Body of POST request
|
||||
'session': sessionName
|
||||
});
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher()) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
|
||||
// HTML shows session page ...
|
||||
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
|
||||
'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
|
||||
sessionId = response[0]; // Get sessionId from response
|
||||
token = response[1]; // Get token from response
|
||||
callback(); // Continue the join operation
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
}
|
||||
```
|
||||
Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `server.js` controller at `/api-sessions/get-sessionid-token`. First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```javascript
|
||||
// Environment variable: URL where our OpenVidu server is listening
|
||||
var OPENVIDU_URL = process.argv[2];
|
||||
// Environment variable: secret shared with our OpenVidu server
|
||||
var OPENVIDU_SECRET = process.argv[3];
|
||||
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
var mapSessionNameSession = {};
|
||||
// Collection to pair sessionId's (identifiers of Session objects) and tokens
|
||||
var mapSessionIdTokens = {};
|
||||
```
|
||||
|
||||
Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
|
||||
3. **Another user connects to the video-call**
|
||||
```javascript
|
||||
app.post('/api-sessions/get-sessionid-token', function (req, res) {
|
||||
// Check the user is logged ...
|
||||
|
||||
The process would be exactly the same as before until `server.js` executes controller at `/api-sessions/get-sessionid-token`. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
// The video-call to connect ("TUTORIAL")
|
||||
var sessionName = req.body.session;
|
||||
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
// Role associated to this user
|
||||
var role = users.find(u => (u.user === req.session.loggedUser)).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the req.session object on login
|
||||
var serverData = '{"serverData": "' + req.session.loggedUser + '"}';
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
var tokenOptions = new TokenOptions.Builder()
|
||||
.data(serverData)
|
||||
.role(role)
|
||||
.build();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```javascript
|
||||
else { // New session: return a new sessionId and a new token
|
||||
// Create a new OpenVidu Session
|
||||
var mySession = OV.createSession();
|
||||
|
||||
// Get the sessionId asynchronously
|
||||
mySession.getSessionId(function (sessionId) {
|
||||
|
||||
// Get the existing Session from the collection
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
// Store the new Session in the collection of Sessions
|
||||
mapSessionNameSession[sessionName] = mySession;
|
||||
// Store a new empty array in the collection of tokens
|
||||
mapSessionIdTokens[sessionId] = [];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
mySession.generateToken(tokenOptions, function (token) {
|
||||
|
||||
// Get the existing sessionId
|
||||
var sessionId = mySession.getSessionId();
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionIdTokens[sessionId].push(token);
|
||||
|
||||
@ -334,68 +235,174 @@ Let's describe the code following this scenario: a user logs in to the app and c
|
||||
1: token
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
});
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
|
||||
4. **Users leave the video-call**
|
||||
```javascript
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
|
||||
For the POST operation, in `app.js` we run:
|
||||
OV = new OpenVidu();
|
||||
session = OV.initSession(sessionId);
|
||||
|
||||
```javascript
|
||||
function removeUser() {
|
||||
// Body of POST request with the name of the session and the token of the leaving user
|
||||
var jsonBody = JSON.stringify({
|
||||
'sessionName': sessionName,
|
||||
'token': token
|
||||
});
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/remove-user', jsonBody,
|
||||
'User couldn\'t be removed from session', function successCallback(response) {
|
||||
console.warn(userName + ' correctly removed from session ' + sessionName);
|
||||
});
|
||||
}
|
||||
```
|
||||
And in `server.js` we update the collections in `/api-sessions/remove-user`:
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
```javascript
|
||||
app.post('/api-sessions/remove-user', function (req, res) {
|
||||
// Check the user is logged ...
|
||||
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
var token = req.body.token;
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
if (mySession) {
|
||||
var tokens = mapSessionIdTokens[mySession.getSessionId()];
|
||||
if (tokens) {
|
||||
var index = tokens.indexOf(token);
|
||||
|
||||
// If the token exists
|
||||
if (index !== -1) {
|
||||
// Token removed!
|
||||
tokens.splice(index, 1);
|
||||
} else {
|
||||
res.status(500).send('The TOKEN wasn\'t valid');
|
||||
}
|
||||
if (mapSessionIdTokens[mySession.getSessionId()].length == 0) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
delete mapSessionNameSession[sessionName];
|
||||
}
|
||||
res.status(200).send();
|
||||
} else {
|
||||
res.status(500).send('The SESSIONID wasn\'t valid');
|
||||
}
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
});
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher()) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
res.status(500).send('The SESSION does not exist');
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `delete mapSessionNameSession[sessionName]` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
|
||||
// HTML shows session page ...
|
||||
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
---
|
||||
|
||||
### 3) Another user connects to the video-call
|
||||
|
||||
The process would be exactly the same as before until `server.js` executes controller at `/api-sessions/get-sessionid-token`. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
|
||||
// Get the existing Session from the collection
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
mySession.generateToken(tokenOptions, function (token) {
|
||||
|
||||
// Get the existing sessionId
|
||||
var sessionId = mySession.getSessionId();
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionIdTokens[sessionId].push(token);
|
||||
|
||||
// Return the sessionId and token to the client
|
||||
res.status(200).send({
|
||||
0: sessionId,
|
||||
1: token
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
|
||||
---
|
||||
|
||||
### 4) Users leave the video-call
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
|
||||
For the POST operation, in `app.js` we run:
|
||||
|
||||
```javascript
|
||||
function removeUser() {
|
||||
// Body of POST request with the name of the session and the token of the leaving user
|
||||
var jsonBody = JSON.stringify({
|
||||
'sessionName': sessionName,
|
||||
'token': token
|
||||
});
|
||||
|
||||
// Send POST request
|
||||
httpRequest('POST', '/api-sessions/remove-user', jsonBody,
|
||||
'User couldn\'t be removed from session', function successCallback(response) {
|
||||
console.warn(userName + ' correctly removed from session ' + sessionName);
|
||||
});
|
||||
}
|
||||
```
|
||||
And in `server.js` we update the collections in `/api-sessions/remove-user`:
|
||||
|
||||
```javascript
|
||||
app.post('/api-sessions/remove-user', function (req, res) {
|
||||
// Check the user is logged ...
|
||||
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
var token = req.body.token;
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
if (mySession) {
|
||||
var tokens = mapSessionIdTokens[mySession.getSessionId()];
|
||||
if (tokens) {
|
||||
var index = tokens.indexOf(token);
|
||||
|
||||
// If the token exists
|
||||
if (index !== -1) {
|
||||
// Token removed!
|
||||
tokens.splice(index, 1);
|
||||
} else {
|
||||
res.status(500).send('The TOKEN wasn\'t valid');
|
||||
}
|
||||
if (mapSessionIdTokens[mySession.getSessionId()].length == 0) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
delete mapSessionNameSession[sessionName];
|
||||
}
|
||||
res.status(200).send();
|
||||
} else {
|
||||
res.status(500).send('The SESSIONID wasn\'t valid');
|
||||
}
|
||||
} else {
|
||||
res.status(500).send('The SESSION does not exist');
|
||||
}
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `delete mapSessionNameSession[sessionName]` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -68,334 +68,342 @@ OpenVidu assumes you can identify your users so you can tell which users can con
|
||||
|
||||
Let's describe the code following this scenario: a user logs in to the app and connects to the video-call "TUTORIAL", where he publishes his webcam. A second user will connect to the same video-call just after that and publish its own webcam. Both of them will leave the call after a while.
|
||||
|
||||
1. **User logs in**
|
||||
---
|
||||
|
||||
At path `/` a login form will be displayed:
|
||||
### 1) User logs in
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
At path `/` a login form will be displayed:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
</p>
|
||||
|
||||
The form will execute a POST operation to path `/dashboard` whenever "Log in" button is clicked, passing the username and the password:
|
||||
|
||||
```html
|
||||
<form action="/dashboard" method="post">
|
||||
<p>
|
||||
<label>User</label> <input type="text" name="user" required="true"></input>
|
||||
</p>
|
||||
|
||||
The form will execute a POST operation to path `/dashboard` whenever "Log in" button is clicked, passing the username and the password:
|
||||
|
||||
```html
|
||||
<form action="/dashboard" method="post">
|
||||
<p>
|
||||
<label>User</label> <input type="text" name="user" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Pass</label> <input type="password" name="pass" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Log in</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
|
||||
`LoginController.java` first checks if the user is already logged (maybe he has just refreshed `/dashboard` page), and if so it just redirects to the dashboard itself. If the user is actually logging in, the method checks that the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object). Finally it returns `dashboard.html` template:
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/dashboard", method = { RequestMethod.GET, RequestMethod.POST })
|
||||
public String login(@RequestParam(name = "user", required = false) String user,
|
||||
@RequestParam(name = "pass", required = false) String pass,
|
||||
Model model, HttpSession httpSession) {
|
||||
|
||||
// Check if the user is already logged in
|
||||
String userName = (String) httpSession.getAttribute("loggedUser");
|
||||
if (userName != null) {
|
||||
// User is already logged. Immediately return dashboard
|
||||
model.addAttribute("username", userName);
|
||||
return "dashboard";
|
||||
}
|
||||
|
||||
// User wasn't logged and wants to
|
||||
if (login(user, pass)) { // Correct user-pass
|
||||
|
||||
// Validate session and return OK
|
||||
// Value stored in HttpSession allows us to identify the user in future requests
|
||||
httpSession.setAttribute("loggedUser", user);
|
||||
model.addAttribute("username", user);
|
||||
|
||||
// Return dashboard.html template
|
||||
return "dashboard";
|
||||
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and redirect to index.html
|
||||
httpSession.invalidate();
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **User connects to "TUTORIAL" video-call**
|
||||
|
||||
`dashboard.html` template will display a form asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
<p>
|
||||
<label>Pass</label> <input type="password" name="pass" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Log in</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
|
||||
The form will execute a POST operation to path `/session` whenever "Join!" button is clicked, passing the nickname and the session name:
|
||||
`LoginController.java` first checks if the user is already logged (maybe he has just refreshed `/dashboard` page), and if so it just redirects to the dashboard itself. If the user is actually logging in, the method checks that the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object). Finally it returns `dashboard.html` template:
|
||||
|
||||
```html
|
||||
<form action="/session" method="post">
|
||||
<p>
|
||||
<label>Name: </label> <input name="data" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Session: </label> <input name="session-name" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
```java
|
||||
@RequestMapping(value = "/dashboard", method = { RequestMethod.GET, RequestMethod.POST })
|
||||
public String login(@RequestParam(name = "user", required = false) String user,
|
||||
@RequestParam(name = "pass", required = false) String pass,
|
||||
Model model, HttpSession httpSession) {
|
||||
|
||||
When `SessionController.java` receives a request at `/session` path is when things get interesting.
|
||||
First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```java
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
private OpenVidu openVidu;
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
private Map<String, Session> mapSessions = new ConcurrentHashMap<>();
|
||||
// Collection to pair sessionId's and tokens (the inner Map pairs tokens and role associated)
|
||||
private Map<String, Map<String, OpenViduRole>> mapSessionIdsTokens = new ConcurrentHashMap<>();
|
||||
|
||||
// URL where our OpenVidu server is listening
|
||||
private String OPENVIDU_URL;
|
||||
// Secret shared with our OpenVidu server
|
||||
private String SECRET;
|
||||
```
|
||||
|
||||
Rest controller method receives both params sent by the client (whatever nickname the user has chosen and "TUTORIAL" as the sessionName). First it prepares a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/session", method = RequestMethod.POST)
|
||||
public String joinSession(@RequestParam(name = "data") String clientData,
|
||||
@RequestParam(name = "session-name") String sessionName,
|
||||
Model model, HttpSession httpSession) {
|
||||
// Check the user is logged ...
|
||||
|
||||
// Role associated to this user
|
||||
OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the HttpSession object on login
|
||||
String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```java
|
||||
else {
|
||||
// New session: return a new sessionId and a new token
|
||||
try {
|
||||
|
||||
// Create a new OpenVidu Session
|
||||
Session session = this.openVidu.createSession();
|
||||
// Get the sessionId
|
||||
String sessionId = session.getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = session.generateToken(tokenOptions);
|
||||
|
||||
// Store the session and the token in our collections
|
||||
this.mapSessions.put(sessionName, session);
|
||||
this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Add all the needed attributes to the template
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
model.addAttribute("token", token);
|
||||
model.addAttribute("nickName", clientData);
|
||||
model.addAttribute("userName", httpSession.getAttribute("loggedUser"));
|
||||
model.addAttribute("sessionName", sessionName);
|
||||
|
||||
// Return session.html template
|
||||
return "session";
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error just return dashboard.html template
|
||||
model.addAttribute("username", httpSession.getAttribute("loggedUser"));
|
||||
return "dashboard";
|
||||
}
|
||||
// Check if the user is already logged in
|
||||
String userName = (String) httpSession.getAttribute("loggedUser");
|
||||
if (userName != null) {
|
||||
// User is already logged. Immediately return dashboard
|
||||
model.addAttribute("username", userName);
|
||||
return "dashboard";
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `session.html` JavaScript code (preceded by a tag `<script th:inline="javascript">`) we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
```javascript
|
||||
// Get all the attributes from the template in Thymeleaf style
|
||||
var sessionId = [[${sessionId}]];
|
||||
var token = [[${token}]];
|
||||
var nickName = [[${nickName}]];
|
||||
var userName = [[${userName}]];
|
||||
var sessionName = [[${sessionName}]];
|
||||
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
var OV = new OpenVidu();
|
||||
var session = OV.initSession(sessionId);
|
||||
// User wasn't logged and wants to
|
||||
if (login(user, pass)) { // Correct user-pass
|
||||
|
||||
// Validate session and return OK
|
||||
// Value stored in HttpSession allows us to identify the user in future requests
|
||||
httpSession.setAttribute("loggedUser", user);
|
||||
model.addAttribute("username", user);
|
||||
|
||||
// Return dashboard.html template
|
||||
return "dashboard";
|
||||
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and redirect to index.html
|
||||
httpSession.invalidate();
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
### 2) User connects to "TUTORIAL" video-call
|
||||
|
||||
`dashboard.html` template will display a form asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
</p>
|
||||
|
||||
The form will execute a POST operation to path `/session` whenever "Join!" button is clicked, passing the nickname and the session name:
|
||||
|
||||
```html
|
||||
<form action="/session" method="post">
|
||||
<p>
|
||||
<label>Name: </label> <input name="data" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Session: </label> <input name="session-name" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
|
||||
When `SessionController.java` receives a request at `/session` path is when things get interesting.
|
||||
First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```java
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
private OpenVidu openVidu;
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
private Map<String, Session> mapSessions = new ConcurrentHashMap<>();
|
||||
// Collection to pair sessionId's and tokens (the inner Map pairs tokens and role associated)
|
||||
private Map<String, Map<String, OpenViduRole>> mapSessionIdsTokens = new ConcurrentHashMap<>();
|
||||
|
||||
// URL where our OpenVidu server is listening
|
||||
private String OPENVIDU_URL;
|
||||
// Secret shared with our OpenVidu server
|
||||
private String SECRET;
|
||||
```
|
||||
|
||||
Rest controller method receives both params sent by the client (whatever nickname the user has chosen and "TUTORIAL" as the sessionName). First it prepares a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/session", method = RequestMethod.POST)
|
||||
public String joinSession(@RequestParam(name = "data") String clientData,
|
||||
@RequestParam(name = "session-name") String sessionName,
|
||||
Model model, HttpSession httpSession) {
|
||||
// Check the user is logged ...
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
// Role associated to this user
|
||||
OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the HttpSession object on login
|
||||
String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```java
|
||||
else {
|
||||
// New session: return a new sessionId and a new token
|
||||
try {
|
||||
|
||||
// Create a new OpenVidu Session
|
||||
Session session = this.openVidu.createSession();
|
||||
// Get the sessionId
|
||||
String sessionId = session.getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = session.generateToken(tokenOptions);
|
||||
|
||||
// Store the session and the token in our collections
|
||||
this.mapSessions.put(sessionName, session);
|
||||
this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Add all the needed attributes to the template
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
model.addAttribute("token", token);
|
||||
model.addAttribute("nickName", clientData);
|
||||
model.addAttribute("userName", httpSession.getAttribute("loggedUser"));
|
||||
model.addAttribute("sessionName", sessionName);
|
||||
|
||||
// Return session.html template
|
||||
return "session";
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
} catch (Exception e) {
|
||||
// If error just return dashboard.html template
|
||||
model.addAttribute("username", httpSession.getAttribute("loggedUser"));
|
||||
return "dashboard";
|
||||
}
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `session.html` JavaScript code (preceded by a tag `<script th:inline="javascript">`) we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
```javascript
|
||||
// Get all the attributes from the template in Thymeleaf style
|
||||
var sessionId = [[${sessionId}]];
|
||||
var token = [[${token}]];
|
||||
var nickName = [[${nickName}]];
|
||||
var userName = [[${userName}]];
|
||||
var sessionName = [[${sessionName}]];
|
||||
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
var OV = new OpenVidu();
|
||||
var session = OV.initSession(sessionId);
|
||||
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
});
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + nickName + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher(userName)) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
});
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
});
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + nickName + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher(userName)) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
3. **Another user connects to the video-call**
|
||||
|
||||
The process would be exactly the same as before until `SessionController.java` executes `getSessionIdAndToken()` method. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
try {
|
||||
|
||||
// Get the existing sessionId from our collection with
|
||||
// the sessionName param ("TUTORIAL")
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = this.mapSessions.get(sessionName).generateToken(tokenOptions);
|
||||
|
||||
// Update our collection storing the new token
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Add all the needed attributes to the template
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
model.addAttribute("token", token);
|
||||
model.addAttribute("nickName", clientData);
|
||||
model.addAttribute("userName", httpSession.getAttribute("loggedUser"));
|
||||
model.addAttribute("sessionName", sessionName);
|
||||
|
||||
// Return session.html template
|
||||
return "session";
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error just return dashboard.html template
|
||||
model.addAttribute("username", httpSession.getAttribute("loggedUser"));
|
||||
return "dashboard";
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
```
|
||||
The code executed in `session.html` _< script >_ tag would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
4. **Users leave the video-call**
|
||||
---
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `session.disconnect()` (triggered in `leaveSession()` _onclick_ method) to destroy the connection on openvidu-server, we need another POST operation to let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens.
|
||||
|
||||
In `session.html` template the "Leave session" button actually performs a POST operation to path `/leave-session` with a hidden form. Notice that when the user clicks the submit button, a POST operation will be triggered but also the `leaveSession()` method. First updates our backend. Second updates our openvidu-server.
|
||||
### 3) Another user connects to the video-call
|
||||
|
||||
```html
|
||||
<form action="/leave-session" method="post">
|
||||
<input type="hidden" name="session-name" th:value="${sessionName}"></input>
|
||||
<input type="hidden" name="token" th:value="${token}"></input>
|
||||
<button type="submit" onclick="leaveSession()">Leave session</button>
|
||||
</form>
|
||||
```
|
||||
The process would be exactly the same as before until `SessionController.java` executes `getSessionIdAndToken()` method. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
|
||||
```java
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
try {
|
||||
|
||||
In `SessionController.java` we update the collections:
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/leave-session", method = RequestMethod.POST)
|
||||
public String removeUser(@RequestParam(name = "session-name") String sessionName,
|
||||
@RequestParam(name = "token") String token,
|
||||
Model model, HttpSession httpSession) throws Exception {
|
||||
// Check the user is logged ...
|
||||
// Get the existing sessionId from our collection with
|
||||
// the sessionName param ("TUTORIAL")
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = this.mapSessions.get(sessionName).generateToken(tokenOptions);
|
||||
|
||||
// Update our collection storing the new token
|
||||
this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
|
||||
|
||||
// Add all the needed attributes to the template
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
model.addAttribute("token", token);
|
||||
model.addAttribute("nickName", clientData);
|
||||
model.addAttribute("userName", httpSession.getAttribute("loggedUser"));
|
||||
model.addAttribute("sessionName", sessionName);
|
||||
|
||||
// Return session.html template
|
||||
return "session";
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
} catch (Exception e) {
|
||||
// If error just return dashboard.html template
|
||||
model.addAttribute("username", httpSession.getAttribute("loggedUser"));
|
||||
return "dashboard";
|
||||
}
|
||||
}
|
||||
```
|
||||
The code executed in `session.html` _< script >_ tag would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
|
||||
if (this.mapSessionIdsTokens.containsKey(sessionId)) {
|
||||
// If the token exists
|
||||
if (this.mapSessionIdsTokens.get(sessionId).remove(token) != null) {
|
||||
// Token has been removed
|
||||
if (this.mapSessionIdsTokens.get(sessionId).isEmpty()) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
this.mapSessions.remove(sessionName);
|
||||
}
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
return "redirect:/dashboard";
|
||||
} else {
|
||||
// The TOKEN wasn't valid
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
return "redirect:/dashboard";
|
||||
---
|
||||
|
||||
### 4) Users leave the video-call**
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `session.disconnect()` (triggered in `leaveSession()` _onclick_ method) to destroy the connection on openvidu-server, we need another POST operation to let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens.
|
||||
|
||||
In `session.html` template the "Leave session" button actually performs a POST operation to path `/leave-session` with a hidden form. Notice that when the user clicks the submit button, a POST operation will be triggered but also the `leaveSession()` method. First updates our backend. Second updates our openvidu-server.
|
||||
|
||||
```html
|
||||
<form action="/leave-session" method="post">
|
||||
<input type="hidden" name="session-name" th:value="${sessionName}"></input>
|
||||
<input type="hidden" name="token" th:value="${token}"></input>
|
||||
<button type="submit" onclick="leaveSession()">Leave session</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
In `SessionController.java` we update the collections:
|
||||
|
||||
```java
|
||||
@RequestMapping(value = "/leave-session", method = RequestMethod.POST)
|
||||
public String removeUser(@RequestParam(name = "session-name") String sessionName,
|
||||
@RequestParam(name = "token") String token,
|
||||
Model model, HttpSession httpSession) throws Exception {
|
||||
// Check the user is logged ...
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
if (this.mapSessions.get(sessionName) != null) {
|
||||
String sessionId = this.mapSessions.get(sessionName).getSessionId();
|
||||
|
||||
if (this.mapSessionIdsTokens.containsKey(sessionId)) {
|
||||
// If the token exists
|
||||
if (this.mapSessionIdsTokens.get(sessionId).remove(token) != null) {
|
||||
// Token has been removed
|
||||
if (this.mapSessionIdsTokens.get(sessionId).isEmpty()) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
this.mapSessions.remove(sessionName);
|
||||
}
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
return "redirect:/dashboard";
|
||||
} else {
|
||||
// The SESSIONID wasn't valid
|
||||
// The TOKEN wasn't valid
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
return "redirect:/dashboard";
|
||||
}
|
||||
} else {
|
||||
// The SESSION does not exist
|
||||
// The SESSIONID wasn't valid
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
return "redirect:/dashboard";
|
||||
}
|
||||
} else {
|
||||
// The SESSION does not exist
|
||||
model.addAttribute("sessionId", sessionId);
|
||||
return "redirect:/dashboard";
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `this.mapSessions.remove(sessionName);` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `this.mapSessions.remove(sessionName);` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -71,264 +71,161 @@ OpenVidu assumes you can identify your users so you can tell which users can con
|
||||
|
||||
Let's describe the code following this scenario: a user logs in to the app and connects to the video-call "TUTORIAL", where he publishes his webcam. A second user will connect to the same video-call just after that and publish its own webcam. Both of them will leave the call after a while.
|
||||
|
||||
1. **User logs in**
|
||||
---
|
||||
|
||||
At path `/` a login form will be displayed:
|
||||
### 1) User logs in
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
At path `/` a login form will be displayed:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSMlh0QkZoYmpQMkE">
|
||||
</p>
|
||||
|
||||
The form will execute a POST operation to path `/dashboard` whenever "Log in" button is clicked, passing the username and the password:
|
||||
|
||||
```html
|
||||
<form action="/dashboard" method="post">
|
||||
<p>
|
||||
<label>User</label> <input type="text" name="user" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Pass</label> <input type="password" name="pass" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Log in</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
|
||||
The form will execute a POST operation to path `/dashboard` whenever "Log in" button is clicked, passing the username and the password:
|
||||
|
||||
```html
|
||||
<form action="/dashboard" method="post">
|
||||
<p>
|
||||
<label>User</label> <input type="text" name="user" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Pass</label> <input type="password" name="pass" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Log in</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
`server.js` at `/dashboard` first checks if the user is already logged (maybe he has just refreshed `/dashboard` page), and if so it just redirects to the dashboard itself. If the user is actually logging in, the method checks that the params are correct and if so sets a new _express-session_ for the newly logged user (adding a _loggedUser_ property with its username in the _req.session_ object). Finally it returns `dashboard.ejs` template:
|
||||
|
||||
`server.js` at `/dashboard` first checks if the user is already logged (maybe he has just refreshed `/dashboard` page), and if so it just redirects to the dashboard itself. If the user is actually logging in, the method checks that the params are correct and if so sets a new _express-session_ for the newly logged user (adding a _loggedUser_ property with its username in the _req.session_ object). Finally it returns `dashboard.ejs` template:
|
||||
```javascript
|
||||
app.post('/dashboard', dashboardController);
|
||||
app.get('/dashboard', dashboardController);
|
||||
|
||||
```javascript
|
||||
app.post('/dashboard', dashboardController);
|
||||
app.get('/dashboard', dashboardController);
|
||||
|
||||
function dashboardController(req, res) {
|
||||
|
||||
// Check if the user is already logged in
|
||||
if (isLogged(req.session)) {
|
||||
// User is already logged. Immediately return dashboard
|
||||
user = req.session.loggedUser;
|
||||
function dashboardController(req, res) {
|
||||
|
||||
// Check if the user is already logged in
|
||||
if (isLogged(req.session)) {
|
||||
// User is already logged. Immediately return dashboard
|
||||
user = req.session.loggedUser;
|
||||
res.render('dashboard.ejs', {
|
||||
user: user
|
||||
});
|
||||
} else {
|
||||
// User wasn't logged and wants to
|
||||
|
||||
// Retrieve params from POST body
|
||||
var user = req.body.user;
|
||||
var pass = req.body.pass;
|
||||
|
||||
if (login(user, pass)) { // Correct user-pass
|
||||
// Validate session and return OK
|
||||
// Value stored in req.session allows us to identify the user in future requests
|
||||
req.session.loggedUser = user;
|
||||
res.render('dashboard.ejs', {
|
||||
user: user
|
||||
});
|
||||
} else {
|
||||
// User wasn't logged and wants to
|
||||
|
||||
// Retrieve params from POST body
|
||||
var user = req.body.user;
|
||||
var pass = req.body.pass;
|
||||
|
||||
if (login(user, pass)) { // Correct user-pass
|
||||
// Validate session and return OK
|
||||
// Value stored in req.session allows us to identify the user in future requests
|
||||
req.session.loggedUser = user;
|
||||
res.render('dashboard.ejs', {
|
||||
user: user
|
||||
});
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and return index template
|
||||
req.session.destroy();
|
||||
res.redirect('/');
|
||||
}
|
||||
} else { // Wrong user-pass
|
||||
// Invalidate session and return index template
|
||||
req.session.destroy();
|
||||
res.redirect('/');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **User connects to "TUTORIAL" video-call**
|
||||
}
|
||||
```
|
||||
|
||||
`dashboard.html` template will display a form asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
---
|
||||
|
||||
### 2) User connects to "TUTORIAL" video-call
|
||||
|
||||
`dashboard.html` template will display a form asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSWkJsOFltSXhYbmc">
|
||||
</p>
|
||||
|
||||
The form will execute a POST operation to path `/session` whenever "Join!" button is clicked, passing the nickname and the session name:
|
||||
|
||||
```html
|
||||
<form action="/session" method="post">
|
||||
<p>
|
||||
<label>Name: </label> <input name="data" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Session: </label> <input name="sessionname" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
|
||||
The form will execute a POST operation to path `/session` whenever "Join!" button is clicked, passing the nickname and the session name:
|
||||
When `server.js` receives a request at `/session` path is when things get interesting.
|
||||
First of all there are some important attributes in this class we must mention:
|
||||
|
||||
```html
|
||||
<form action="/session" method="post">
|
||||
<p>
|
||||
<label>Name: </label> <input name="data" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<label>Session: </label> <input name="sessionname" required="true"></input>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
```
|
||||
```javascript
|
||||
// Environment variable: URL where our OpenVidu server is listening
|
||||
var OPENVIDU_URL = process.argv[2];
|
||||
// Environment variable: secret shared with our OpenVidu server
|
||||
var OPENVIDU_SECRET = process.argv[3];
|
||||
|
||||
When `server.js` receives a request at `/session` path is when things get interesting.
|
||||
First of all there are some important attributes in this class we must mention:
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
|
||||
```javascript
|
||||
// Environment variable: URL where our OpenVidu server is listening
|
||||
var OPENVIDU_URL = process.argv[2];
|
||||
// Environment variable: secret shared with our OpenVidu server
|
||||
var OPENVIDU_SECRET = process.argv[3];
|
||||
|
||||
// OpenVidu object to ask openvidu-server for sessionId and token
|
||||
var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
var mapSessionNameSession = {};
|
||||
// Collection to pair sessionId's (identifiers of Session objects) and tokens
|
||||
var mapSessionIdTokens = {};
|
||||
```
|
||||
// Collection to pair session names and OpenVidu Session objects
|
||||
var mapSessionNameSession = {};
|
||||
// Collection to pair sessionId's (identifiers of Session objects) and tokens
|
||||
var mapSessionIdTokens = {};
|
||||
```
|
||||
|
||||
Rest controller method receives both params sent by the client (whatever nickname the user has chosen and "TUTORIAL" as the sessionName). First it prepares a param we will need a little further on: `tokenOptions`.
|
||||
Rest controller method receives both params sent by the client (whatever nickname the user has chosen and "TUTORIAL" as the sessionName). First it prepares a param we will need a little further on: `tokenOptions`.
|
||||
|
||||
```javascript
|
||||
app.post('/session', (req, res) => {
|
||||
// Check the user is logged ...
|
||||
|
||||
// The nickname sent by the client
|
||||
var clientData = req.body.data;
|
||||
// The video-call to connect ("TUTORIAL")
|
||||
var sessionName = req.body.sessionname;
|
||||
|
||||
// Role associated to this user
|
||||
var role = users.find(u => (u.user === req.session.loggedUser)).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the req.session object on login
|
||||
var serverData = '{"serverData": "' + req.session.loggedUser + '"}';
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
var tokenOptions = new TokenOptions.Builder()
|
||||
.data(serverData)
|
||||
.role(role)
|
||||
.build();
|
||||
```
|
||||
```javascript
|
||||
app.post('/session', (req, res) => {
|
||||
// Check the user is logged ...
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
// The nickname sent by the client
|
||||
var clientData = req.body.data;
|
||||
// The video-call to connect ("TUTORIAL")
|
||||
var sessionName = req.body.sessionname;
|
||||
|
||||
// Role associated to this user
|
||||
var role = users.find(u => (u.user === req.session.loggedUser)).role;
|
||||
|
||||
// Optional data to be passed to other users when this user connects to the video-call
|
||||
// In this case, a JSON with the value we stored in the req.session object on login
|
||||
var serverData = '{"serverData": "' + req.session.loggedUser + '"}';
|
||||
|
||||
// Build tokenOptions object with the serverData and the role
|
||||
var tokenOptions = new TokenOptions.Builder()
|
||||
.data(serverData)
|
||||
.role(role)
|
||||
.build();
|
||||
```
|
||||
|
||||
Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) { ...
|
||||
```
|
||||
In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
|
||||
|
||||
```javascript
|
||||
else { // New session: return a new sessionId and a new token
|
||||
// Create a new OpenVidu Session
|
||||
var mySession = OV.createSession();
|
||||
|
||||
```javascript
|
||||
else { // New session: return a new sessionId and a new token
|
||||
// Create a new OpenVidu Session
|
||||
var mySession = OV.createSession();
|
||||
// Get the sessionId asynchronously
|
||||
mySession.getSessionId(function (sessionId) {
|
||||
|
||||
// Get the sessionId asynchronously
|
||||
mySession.getSessionId(function (sessionId) {
|
||||
|
||||
// Store the new Session in the collection of Sessions
|
||||
mapSessionNameSession[sessionName] = mySession;
|
||||
// Store a new empty array in the collection of tokens
|
||||
mapSessionIdTokens[sessionId] = [];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
mySession.generateToken(tokenOptions, function (token) {
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionIdTokens[sessionId].push(token);
|
||||
|
||||
// Return session template with all the needed attributes
|
||||
res.render('session.ejs', {
|
||||
sessionId: sessionId,
|
||||
token: token,
|
||||
nickName: clientData,
|
||||
userName: req.session.loggedUser,
|
||||
sessionName: sessionName
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `session.html` JavaScript code (preceded by a tag `<script>`) we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
```javascript
|
||||
// Get all the attributes from the template in EJS style
|
||||
var sessionId = <%- JSON.stringify(sessionId) %>;
|
||||
var token = <%- JSON.stringify(token) %>;
|
||||
var nickName = <%- JSON.stringify(nickName) %>;
|
||||
var userName = <%- JSON.stringify(userName) %>;
|
||||
var sessionName = <%- JSON.stringify(sessionName) %>;
|
||||
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
var OV = new OpenVidu();
|
||||
var session = OV.initSession(sessionId);
|
||||
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
});
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + nickName + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher(userName)) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
3. **Another user connects to the video-call**
|
||||
|
||||
The process would be exactly the same as before until `server.js` executes controller at `/api-sessions/get-sessionid-token`. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
|
||||
// Get the existing Session from the collection
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
// Store the new Session in the collection of Sessions
|
||||
mapSessionNameSession[sessionName] = mySession;
|
||||
// Store a new empty array in the collection of tokens
|
||||
mapSessionIdTokens[sessionId] = [];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
mySession.generateToken(tokenOptions, function (token) {
|
||||
|
||||
// Get the existing sessionId
|
||||
var sessionId = mySession.getSessionId();
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionIdTokens[sessionId].push(token);
|
||||
|
||||
@ -341,62 +238,173 @@ Let's describe the code following this scenario: a user logs in to the app and c
|
||||
sessionName: sessionName
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
The code executed in `session.html` _< script >_ tag would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
});
|
||||
}
|
||||
```
|
||||
We are almost there! Now in `session.html` JavaScript code (preceded by a tag `<script>`) we can init a new Session with _sessionId_ and connect to it with _token_:
|
||||
```javascript
|
||||
// Get all the attributes from the template in EJS style
|
||||
var sessionId = <%- JSON.stringify(sessionId) %>;
|
||||
var token = <%- JSON.stringify(token) %>;
|
||||
var nickName = <%- JSON.stringify(nickName) %>;
|
||||
var userName = <%- JSON.stringify(userName) %>;
|
||||
var sessionName = <%- JSON.stringify(sessionName) %>;
|
||||
|
||||
4. **Users leave the video-call**
|
||||
// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `session.disconnect()` (triggered in `leaveSession()` _onclick_ method) to destroy the connection on openvidu-server, we need another POST operation to let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens.
|
||||
var OV = new OpenVidu();
|
||||
var session = OV.initSession(sessionId);
|
||||
|
||||
|
||||
// --- 2) Specify the actions when events take place ---
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', function (event) {
|
||||
|
||||
In `session.html` template the "Leave session" button actually performs a POST operation to path `/leave-session` with a hidden form. Notice that when the user clicks the submit button, a POST operation will be triggered but also the `leaveSession()` method. First updates our backend. Second updates our openvidu-server.
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'subscriber' id
|
||||
var subscriber = session.subscribe(event.stream, 'subscriber');
|
||||
|
||||
```html
|
||||
<form action="/leave-session" method="post">
|
||||
<input type="hidden" name="session-name" th:value="${sessionName}"></input>
|
||||
<input type="hidden" name="token" th:value="${token}"></input>
|
||||
<button type="submit" onclick="leaveSession()">Leave session</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
In `server.js` we update the collections at `/leave-session`:
|
||||
|
||||
```javascript
|
||||
app.post('/leave-session', function (req, res) {
|
||||
// Check the user is logged ...
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', function (event) {
|
||||
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionname;
|
||||
var token = req.body.token;
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
if (mySession) {
|
||||
var tokens = mapSessionIdTokens[mySession.getSessionId()];
|
||||
if (tokens) {
|
||||
var index = tokens.indexOf(token);
|
||||
|
||||
// If the token exists
|
||||
if (index !== -1) {
|
||||
// Token removed!
|
||||
tokens.splice(index, 1);
|
||||
} else {
|
||||
res.status(500).send('The TOKEN wasn\'t valid');
|
||||
}
|
||||
if (mapSessionIdTokens[mySession.getSessionId()].length == 0) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
delete mapSessionNameSession[sessionName];
|
||||
}
|
||||
res.redirect('/dashboard');
|
||||
} else {
|
||||
res.status(500).send('The SESSIONID wasn\'t valid');
|
||||
}
|
||||
// Add a new <p> element for the user's name and nickname just below its video
|
||||
appendUserData(event.element, subscriber.stream.connection);
|
||||
});
|
||||
});
|
||||
|
||||
// On every Stream destroyed...
|
||||
session.on('streamDestroyed', function (event) {
|
||||
// Delete the HTML element with the user's name and nickname
|
||||
removeUserData(event.stream.connection);
|
||||
});
|
||||
|
||||
|
||||
// --- 3) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
session.connect(token, '{"clientData": "' + nickName + '"}', function (err) {
|
||||
|
||||
// If the connection is successful, initialize a publisher and publish to the session
|
||||
if (!err) {
|
||||
|
||||
// Here we check somehow if the user has at least 'PUBLISHER' role before
|
||||
// trying to publish its stream. Even if someone modified the client's code and
|
||||
// published the stream, it won't work if the token sent in Session.connect
|
||||
// method doesn't belong to a 'PUBLIHSER' role
|
||||
if (isPublisher(userName)) {
|
||||
|
||||
// --- 4) Get your own camera stream and publish it ---
|
||||
|
||||
var publisher = OV.initPublisher('publisher', {
|
||||
audio: true,
|
||||
video: true,
|
||||
quality: 'MEDIUM'
|
||||
});
|
||||
|
||||
// --- 5) Publish your stream ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
} else {
|
||||
res.status(500).send('The SESSION does not exist');
|
||||
console.warn('You don\'t have permissions to publish');
|
||||
}
|
||||
} else {
|
||||
console.warn('Error connecting to the session:', error.code, error.message);
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `delete mapSessionNameSession[sessionName]` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
});
|
||||
```
|
||||
The user will now see its own video on the page. The connection to the session has completed!
|
||||
|
||||
---
|
||||
|
||||
### 3) Another user connects to the video-call
|
||||
|
||||
The process would be exactly the same as before until `server.js` executes controller at `/api-sessions/get-sessionid-token`. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
|
||||
|
||||
```javascript
|
||||
if (mapSessionNameSession[sessionName]) {
|
||||
// Session already exists: return existing sessionId and a new token
|
||||
|
||||
// Get the existing Session from the collection
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
mySession.generateToken(tokenOptions, function (token) {
|
||||
|
||||
// Get the existing sessionId
|
||||
var sessionId = mySession.getSessionId();
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionIdTokens[sessionId].push(token);
|
||||
|
||||
// Return session template with all the needed attributes
|
||||
res.render('session.ejs', {
|
||||
sessionId: sessionId,
|
||||
token: token,
|
||||
nickName: clientData,
|
||||
userName: req.session.loggedUser,
|
||||
sessionName: sessionName
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
The code executed in `session.html` _< script >_ tag would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
|
||||
|
||||
---
|
||||
|
||||
### 4) Users leave the video-call
|
||||
|
||||
After a while both users decide to leave the session. Apart from calling `session.disconnect()` (triggered in `leaveSession()` _onclick_ method) to destroy the connection on openvidu-server, we need another POST operation to let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens.
|
||||
|
||||
In `session.html` template the "Leave session" button actually performs a POST operation to path `/leave-session` with a hidden form. Notice that when the user clicks the submit button, a POST operation will be triggered but also the `leaveSession()` method. First updates our backend. Second updates our openvidu-server.
|
||||
|
||||
```html
|
||||
<form action="/leave-session" method="post">
|
||||
<input type="hidden" name="session-name" th:value="${sessionName}"></input>
|
||||
<input type="hidden" name="token" th:value="${token}"></input>
|
||||
<button type="submit" onclick="leaveSession()">Leave session</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
In `server.js` we update the collections at `/leave-session`:
|
||||
|
||||
```javascript
|
||||
app.post('/leave-session', function (req, res) {
|
||||
// Check the user is logged ...
|
||||
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionname;
|
||||
var token = req.body.token;
|
||||
|
||||
// If the session exists ("TUTORIAL" in this case)
|
||||
var mySession = mapSessionNameSession[sessionName];
|
||||
if (mySession) {
|
||||
var tokens = mapSessionIdTokens[mySession.getSessionId()];
|
||||
if (tokens) {
|
||||
var index = tokens.indexOf(token);
|
||||
|
||||
// If the token exists
|
||||
if (index !== -1) {
|
||||
// Token removed!
|
||||
tokens.splice(index, 1);
|
||||
} else {
|
||||
res.status(500).send('The TOKEN wasn\'t valid');
|
||||
}
|
||||
if (mapSessionIdTokens[mySession.getSessionId()].length == 0) {
|
||||
// Last user left: session "TUTORIAL" must be removed
|
||||
delete mapSessionNameSession[sessionName];
|
||||
}
|
||||
res.redirect('/dashboard');
|
||||
} else {
|
||||
res.status(500).send('The SESSIONID wasn\'t valid');
|
||||
}
|
||||
} else {
|
||||
res.status(500).send('The SESSION does not exist');
|
||||
}
|
||||
}
|
||||
```
|
||||
When the last user leaves the session `delete mapSessionNameSession[sessionName]` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user