From 21208aefec4084525a618296cd9d8666990bea70 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Mon, 6 Nov 2023 11:30:50 +0100 Subject: [PATCH] Migrated openvidu-roles-java to Livekit --- openvidu-roles-java/pom.xml | 12 +- .../io/openvidu/js/java/LoginController.java | 55 +- .../io/openvidu/js/java/RoomController.java | 85 + .../openvidu/js/java/SessionController.java | 189 - .../src/main/resources/application.properties | 6 +- .../src/main/resources/static/app.js | 389 +- .../src/main/resources/static/index.html | 306 +- .../static/openvidu-browser-2.27.0.js | 14128 ---------------- .../src/main/resources/static/style.css | 42 +- 9 files changed, 525 insertions(+), 14687 deletions(-) create mode 100644 openvidu-roles-java/src/main/java/io/openvidu/js/java/RoomController.java delete mode 100644 openvidu-roles-java/src/main/java/io/openvidu/js/java/SessionController.java delete mode 100644 openvidu-roles-java/src/main/resources/static/openvidu-browser-2.27.0.js diff --git a/openvidu-roles-java/pom.xml b/openvidu-roles-java/pom.xml index 8ecfd875..66036a26 100644 --- a/openvidu-roles-java/pom.xml +++ b/openvidu-roles-java/pom.xml @@ -48,14 +48,14 @@ spring-boot-devtools - com.googlecode.json-simple - json-simple - 1.1.1 + org.json + json + 20231013 - io.openvidu - openvidu-java-client - 2.27.0 + io.livekit + livekit-server + 0.5.7 diff --git a/openvidu-roles-java/src/main/java/io/openvidu/js/java/LoginController.java b/openvidu-roles-java/src/main/java/io/openvidu/js/java/LoginController.java index 9e97c72b..2eb45ced 100644 --- a/openvidu-roles-java/src/main/java/io/openvidu/js/java/LoginController.java +++ b/openvidu-roles-java/src/main/java/io/openvidu/js/java/LoginController.java @@ -1,33 +1,30 @@ package io.openvidu.js.java; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpSession; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; + import org.springframework.web.bind.annotation.RestController; -import io.openvidu.java.client.OpenViduRole; - +@CrossOrigin(origins = "*") @RestController -@RequestMapping("/api-login") public class LoginController { public class MyUser { String name; String pass; - OpenViduRole role; + String role; - public MyUser(String name, String pass, OpenViduRole role) { + public MyUser(String name, String pass, String role) { this.name = name; this.pass = pass; this.role = role; @@ -37,40 +34,42 @@ public class LoginController { public static Map users = new ConcurrentHashMap<>(); public LoginController() { - users.put("publisher1", new MyUser("publisher1", "pass", OpenViduRole.PUBLISHER)); - users.put("publisher2", new MyUser("publisher2", "pass", OpenViduRole.PUBLISHER)); - users.put("subscriber", new MyUser("subscriber", "pass", OpenViduRole.SUBSCRIBER)); + users.put("publisher1", new MyUser("publisher1", "pass", "PUBLISHER")); + users.put("publisher2", new MyUser("publisher2", "pass", "PUBLISHER")); + users.put("subscriber", new MyUser("subscriber", "pass", "SUBSCRIBER")); } - @RequestMapping(value = "/login", method = RequestMethod.POST) - public ResponseEntity login(@RequestBody String userPass, HttpSession httpSession) throws ParseException { + @PostMapping("/login") + public ResponseEntity login(@RequestBody(required = true) Map params, HttpSession httpSession) { - System.out.println("Logging in | {user, pass}=" + userPass); - // Retrieve params from POST body - JSONObject userPassJson = (JSONObject) new JSONParser().parse(userPass); - String user = (String) userPassJson.get("user"); - String pass = (String) userPassJson.get("pass"); + String user = params.get("user"); + String pass = params.get("pass"); + Map response = new HashMap(); - 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 + if (login(user, pass)) { + // Successful login + // Validate session and return OK + // Value stored in req.session allows us to identify the user in future requests httpSession.setAttribute("loggedUser", user); - return new ResponseEntity<>(HttpStatus.OK); - } else { // Wrong user-pass + return new ResponseEntity<>(new HashMap(), HttpStatus.OK); + } else { + // Credentials are NOT valid // Invalidate session and return error httpSession.invalidate(); - return new ResponseEntity<>("User/Pass incorrect", HttpStatus.UNAUTHORIZED); + response.put("message", "Invalid credentials"); + return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED); } } - @RequestMapping(value = "/logout", method = RequestMethod.POST) - public ResponseEntity logout(HttpSession session) { + @PostMapping("/logout") + public ResponseEntity logout(HttpSession session) { System.out.println("'" + session.getAttribute("loggedUser") + "' has logged out"); session.invalidate(); return new ResponseEntity<>(HttpStatus.OK); } private boolean login(String user, String pass) { + if(user.isEmpty() || pass.isEmpty()) return false; return (users.containsKey(user) && users.get(user).pass.equals(pass)); } diff --git a/openvidu-roles-java/src/main/java/io/openvidu/js/java/RoomController.java b/openvidu-roles-java/src/main/java/io/openvidu/js/java/RoomController.java new file mode 100644 index 00000000..565fe052 --- /dev/null +++ b/openvidu-roles-java/src/main/java/io/openvidu/js/java/RoomController.java @@ -0,0 +1,85 @@ +package io.openvidu.js.java; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpSession; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import io.livekit.server.AccessToken; +import io.livekit.server.CanPublish; +import io.livekit.server.CanSubscribe; +import io.livekit.server.RoomJoin; +import io.livekit.server.RoomName; + +import io.openvidu.js.java.LoginController.MyUser; + +@CrossOrigin(origins = "*") +@RestController +public class RoomController { + + @Value("${LIVEKIT_URL}") + private String LIVEKIT_URL; + + @Value("${LIVEKIT_API_KEY}") + private String LIVEKIT_API_KEY; + + @Value("${LIVEKIT_API_SECRET}") + private String LIVEKIT_API_SECRET; + + /** + * @param params The JSON object with roomName and participantName + * @return The JWT token + */ + @PostMapping("/token") + public ResponseEntity> getToken(@RequestBody(required = true) Map params, HttpSession httpSession) { + + String roomName = params.get("roomName"); + String participantName = params.get("participantName"); + Map response = new HashMap(); + + if(!isUserLogged(httpSession)) { + response.put("message", "User is not logged"); + return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED); + } + + if(roomName == null || participantName == null) { + response.put("message", "roomName and participantName are required"); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } + + MyUser user = LoginController.users.get(httpSession.getAttribute("loggedUser")); + Boolean canPublish = user.role.equals("PUBLISHER"); + + // By default, tokens expire 6 hours after generation. + // You may override this by using token.setTtl(long millis). + AccessToken token = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET); + token.setName(participantName); + token.setIdentity(participantName); + + JSONObject metadata = new JSONObject(); + metadata.put("livekitUrl", LIVEKIT_URL); + metadata.put("user", user.name); + metadata.put("role", user.role); + // add metadata to the token, which will be available in the participant's metadata + token.setMetadata(metadata.toString()); + token.addGrants(new RoomJoin(true), new RoomName(roomName), new CanPublish(canPublish), new CanSubscribe(true)); + + response.put("token", token.toJwt()); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + + private boolean isUserLogged(HttpSession httpSession) { + return httpSession != null && httpSession.getAttribute("loggedUser") != null; + } + +} diff --git a/openvidu-roles-java/src/main/java/io/openvidu/js/java/SessionController.java b/openvidu-roles-java/src/main/java/io/openvidu/js/java/SessionController.java deleted file mode 100644 index d1eca96d..00000000 --- a/openvidu-roles-java/src/main/java/io/openvidu/js/java/SessionController.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.openvidu.js.java; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import javax.servlet.http.HttpSession; - -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import io.openvidu.java.client.OpenVidu; -import io.openvidu.java.client.OpenViduHttpException; -import io.openvidu.java.client.OpenViduJavaClientException; -import io.openvidu.java.client.OpenViduRole; -import io.openvidu.java.client.Session; -import io.openvidu.java.client.ConnectionProperties; -import io.openvidu.java.client.ConnectionType; - -@RestController -@RequestMapping("/api-sessions") -public class SessionController { - - // OpenVidu object as entrypoint of the SDK - private OpenVidu openVidu; - - // Collection to pair session names and OpenVidu Session objects - private Map mapSessions = new ConcurrentHashMap<>(); - // Collection to pair session names and tokens (the inner Map pairs tokens and - // role associated) - private Map> mapSessionNamesTokens = new ConcurrentHashMap<>(); - - // URL where our OpenVidu server is listening - private String OPENVIDU_URL; - // Secret shared with our OpenVidu server - private String SECRET; - - public SessionController(@Value("${openvidu.secret}") String secret, @Value("${openvidu.url}") String openviduUrl) { - this.SECRET = secret; - this.OPENVIDU_URL = openviduUrl; - this.openVidu = new OpenVidu(OPENVIDU_URL, SECRET); - } - - @RequestMapping(value = "/get-token", method = RequestMethod.POST) - public ResponseEntity getToken(@RequestBody String sessionNameParam, HttpSession httpSession) - throws ParseException { - - try { - checkUserLogged(httpSession); - } catch (Exception e) { - return getErrorResponse(e); - } - System.out.println("Getting a token from OpenVidu Server | {sessionName}=" + sessionNameParam); - - JSONObject sessionJSON = (JSONObject) new JSONParser().parse(sessionNameParam); - - // The video-call to connect - String sessionName = (String) sessionJSON.get("sessionName"); - - // 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 connectionProperties object with the serverData and the role - ConnectionProperties connectionProperties = new ConnectionProperties.Builder().type(ConnectionType.WEBRTC).data(serverData).role(role).build(); - - JSONObject responseJson = new JSONObject(); - - if (this.mapSessions.get(sessionName) != null) { - // Session already exists - System.out.println("Existing session " + sessionName); - try { - - // Generate a new Connection with the recently created connectionProperties - String token = this.mapSessions.get(sessionName).createConnection(connectionProperties).getToken(); - - // Update our collection storing the new token - this.mapSessionNamesTokens.get(sessionName).put(token, role); - - // Prepare the response with the token - responseJson.put(0, token); - - // Return the response to the client - return new ResponseEntity<>(responseJson, HttpStatus.OK); - } catch (OpenViduJavaClientException e1) { - // If internal error generate an error message and return it to client - return getErrorResponse(e1); - } catch (OpenViduHttpException e2) { - if (404 == e2.getStatus()) { - // Invalid sessionId (user left unexpectedly). Session object is not valid - // anymore. Clean collections and continue as new session - this.mapSessions.remove(sessionName); - this.mapSessionNamesTokens.remove(sessionName); - } - } - } - - // New session - System.out.println("New session " + sessionName); - try { - - // Create a new OpenVidu Session - Session session = this.openVidu.createSession(); - // Generate a new Connection with the recently created connectionProperties - String token = session.createConnection(connectionProperties).getToken(); - - // Store the session and the token in our collections - this.mapSessions.put(sessionName, session); - this.mapSessionNamesTokens.put(sessionName, new ConcurrentHashMap<>()); - this.mapSessionNamesTokens.get(sessionName).put(token, role); - - // Prepare the response with the token - responseJson.put(0, 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); - } - } - - @RequestMapping(value = "/remove-user", method = RequestMethod.POST) - public ResponseEntity removeUser(@RequestBody String sessionNameToken, HttpSession httpSession) - throws Exception { - - try { - checkUserLogged(httpSession); - } catch (Exception e) { - return getErrorResponse(e); - } - System.out.println("Removing user | {sessionName, token}=" + sessionNameToken); - - // 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 - if (this.mapSessions.get(sessionName) != null && this.mapSessionNamesTokens.get(sessionName) != null) { - - // If the token exists - if (this.mapSessionNamesTokens.get(sessionName).remove(token) != null) { - // User left the session - if (this.mapSessionNamesTokens.get(sessionName).isEmpty()) { - // Last user left: session must be removed - this.mapSessions.remove(sessionName); - } - return new ResponseEntity<>(HttpStatus.OK); - } else { - // The TOKEN wasn't valid - System.out.println("Problems in the app server: the TOKEN wasn't valid"); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - - } else { - // The SESSION does not exist - System.out.println("Problems in the app server: the SESSION does not exist"); - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - private ResponseEntity getErrorResponse(Exception e) { - JSONObject json = new JSONObject(); - json.put("cause", e.getCause()); - json.put("error", e.getMessage()); - json.put("exception", e.getClass()); - return new ResponseEntity<>(json, HttpStatus.INTERNAL_SERVER_ERROR); - } - - private void checkUserLogged(HttpSession httpSession) throws Exception { - if (httpSession == null || httpSession.getAttribute("loggedUser") == null) { - throw new Exception("User not logged"); - } - } - -} diff --git a/openvidu-roles-java/src/main/resources/application.properties b/openvidu-roles-java/src/main/resources/application.properties index 2a7a3853..fdd2327b 100644 --- a/openvidu-roles-java/src/main/resources/application.properties +++ b/openvidu-roles-java/src/main/resources/application.properties @@ -5,5 +5,7 @@ server.ssl.key-store-password: openvidu server.ssl.key-store-type: JKS server.ssl.key-alias: openvidu-selfsigned -openvidu.url: http://localhost:4443/ -openvidu.secret: MY_SECRET + +LIVEKIT_URL: ws://localhost:7880/ +LIVEKIT_API_KEY: devkey +LIVEKIT_API_SECRET: secret \ No newline at end of file diff --git a/openvidu-roles-java/src/main/resources/static/app.js b/openvidu-roles-java/src/main/resources/static/app.js index 44ed3720..57d8ded7 100644 --- a/openvidu-roles-java/src/main/resources/static/app.js +++ b/openvidu-roles-java/src/main/resources/static/app.js @@ -1,261 +1,234 @@ -var OV; -var session; - -var sessionName; // Name of the video session the user will connect to -var token; // Token retrieved from OpenVidu Server - +var LivekitClient = window.LivekitClient; +var room; +var myRoomName; +var token; +var nickname; /* OPENVIDU METHODS */ -function joinSession() { - getToken((token) => { +function joinRoom() { + document.getElementById('join-btn').disabled = true; + document.getElementById('join-btn').innerHTML = 'Joining...'; + const myParticipantName = $('#myParticipantName').val(); + const myRoomName = $('#myRoomName').val(); - // --- 1) Get an OpenVidu object --- + room = new LivekitClient.Room(); - OV = new OpenVidu(); + room.on( + LivekitClient.RoomEvent.TrackSubscribed, + (track, publication, participant) => { + const element = track.attach(); + element.id = track.sid; + element.className = 'removable'; + document.getElementById('video-container').appendChild(element); + if (track.kind === 'video') { + var participantNickname; + try { + participantNickname = JSON.parse(participant.metadata).nickname; + } catch (error) { + console.warn('Error parsing participant metadata: ' + error); + } + appendUserData(element, participant.identity, participantNickname); + } + } + ); - // --- 2) Init a session --- + // On every new Track destroyed... + room.on( + LivekitClient.RoomEvent.TrackUnsubscribed, + (track, publication, participant) => { + track.detach(); + document.getElementById(track.sid)?.remove(); + if (track.kind === 'video') { + removeUserData(participant); + } + } + ); - session = OV.initSession(); + getToken(myRoomName, myParticipantName).then((token) => { + const livekitUrl = getLivekitUrlFromMetadata(token); - // --- 3) Specify the actions when events take place in the session --- - - // On every new Stream received... - session.on('streamCreated', (event) => { - - // Subscribe to the Stream to receive it - // HTML video will be appended to element with 'video-container' id - var subscriber = session.subscribe(event.stream, 'video-container'); - - // When the HTML video has been appended to DOM... - subscriber.on('videoElementCreated', (event) => { - - // Add a new HTML element for the user's name and nickname over its video - appendUserData(event.element, subscriber.stream.connection); - }); - }); - - // On every Stream destroyed... - session.on('streamDestroyed', (event) => { - // Delete the HTML element with the user's name and nickname - removeUserData(event.stream.connection); - }); - - // On every asynchronous exception... - session.on('exception', (exception) => { - console.warn(exception); - }); - - // --- 4) 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) --- - - var nickName = $("#nickName").val(); - session.connect(token, { clientData: nickName }) - .then(() => { - - // --- 5) Set page layout for active call --- - - var userName = $("#user").val(); - $('#session-title').text(sessionName); + room + .connect(livekitUrl, token) + .then(async () => { + var participantName = $('#user').val(); + $('#room-title').text(myRoomName); $('#join').hide(); - $('#session').show(); + $('#room').show(); + const canPublish = room.localParticipant.permissions.canPublish; - // Here we check somehow if the user has 'PUBLISHER' role before - // trying to publish its stream. Even if someone modified the client's code and - // published the stream, it wouldn't work if the token sent in Session.connect - // method is not recognized as 'PUBLIHSER' role by OpenVidu Server - if (isPublisher(userName)) { - - // --- 6) Get your own camera stream --- - - var publisher = OV.initPublisher('video-container', { - audioSource: undefined, // The source of audio. If undefined default microphone - videoSource: undefined, // The source of video. If undefined default webcam - publishAudio: true, // Whether you want to start publishing with your audio unmuted or not - publishVideo: true, // Whether you want to start publishing with your video enabled or not - resolution: '640x480', // The resolution of your video - frameRate: 30, // The frame rate of your video - insertMode: 'APPEND', // How the video is inserted in the target element 'video-container' - mirror: false // Whether to mirror your local video or not - }); - - // --- 7) Specify the actions when events take place in our publisher --- - - // When our HTML video has been added to DOM... - publisher.on('videoElementCreated', (event) => { - // Init the main video with ours and append our data - var userData = { - nickName: nickName, - userName: userName - }; - initMainVideo(event.element, userData); - appendUserData(event.element, userData); - $(event.element).prop('muted', true); // Mute local video - }); - - - // --- 8) Publish your stream --- - - session.publish(publisher); + if (canPublish) { + const [microphonePublication, cameraPublication] = await Promise.all([ + room.localParticipant.setMicrophoneEnabled(true), + room.localParticipant.setCameraEnabled(true), + ]); + const element = cameraPublication.track.attach(); + element.className = 'removable'; + document.getElementById('video-container').appendChild(element); + initMainVideo(element, myParticipantName, nickname); + appendUserData(element, myParticipantName, nickname); } else { - console.warn('You don\'t have permissions to publish'); - initMainVideoThumbnail(); // Show SUBSCRIBER message in main video + initMainVideoThumbnail(); } }) - .catch(error => { - console.warn('There was an error connecting to the session:', error.code, error.message); + .catch((error) => { + console.warn( + 'There was an error connecting to the room:', + error.code, + error.message + ); + enableBtn(); }); }); return false; } -function leaveSession() { - - // --- 9) Leave the session by calling 'disconnect' method over the Session object --- - - session.disconnect(); - session = null; +function leaveRoom() { + room.disconnect(); + room = null; // Removing all HTML elements with the user's nicknames - cleanSessionView(); + cleanRoomView(); $('#join').show(); - $('#session').hide(); + $('#room').hide(); + + enableBtn(); } /* OPENVIDU METHODS */ - +function enableBtn() { + document.getElementById('join-btn').disabled = false; + document.getElementById('join-btn').innerHTML = 'Join!'; +} /* APPLICATION REST METHODS */ function logIn() { - var user = $("#user").val(); // Username - var pass = $("#pass").val(); // Password + nickname = $('#user').val(); + var pass = $('#pass').val(); httpPostRequest( - 'api-login/login', - {user: user, pass: pass}, + 'login', + { user: nickname, pass }, 'Login WRONG', (response) => { - $("#name-user").text(user); - $("#not-logged").hide(); - $("#logged").show(); - // Random nickName and session - $("#sessionName").val("Session " + Math.floor(Math.random() * 10)); - $("#nickName").val("Participant " + Math.floor(Math.random() * 100)); + $('#name-user').text(nickname); + $('#not-logged').hide(); + $('#logged').show(); + // Random myParticipantName and room + $('#myRoomName').val('Room ' + Math.floor(Math.random() * 10)); + $('#myParticipantName').val( + 'Participant ' + Math.floor(Math.random() * 100) + ); } ); } function logOut() { - httpPostRequest( - 'api-login/logout', - {}, - 'Logout WRONG', - (response) => { - $("#not-logged").show(); - $("#logged").hide(); - } - ); + httpPostRequest('logout', {}, 'Logout WRONG', (response) => { + $('#not-logged').show(); + $('#logged').hide(); + }); + + enableBtn(); } -function getToken(callback) { - sessionName = $("#sessionName").val(); // Video-call chosen by the user - - httpPostRequest( - 'api-sessions/get-token', - {sessionName: sessionName}, - 'Request of TOKEN gone WRONG:', - (response) => { - token = response[0]; // Get token from response - console.warn('Request of TOKEN gone WELL (TOKEN:' + token + ')'); - callback(token); // Continue the join operation - } - ); +function getToken(roomName, participantName) { + return new Promise((resolve, reject) => { + // Video-call chosen by the user + httpPostRequest( + 'token', + { roomName, participantName }, + 'Error generating token', + (response) => resolve(response.token) + ); + }); } -function removeUser() { - httpPostRequest( - 'api-sessions/remove-user', - {sessionName: sessionName, token: token}, - 'User couldn\'t be removed from session', - (response) => { - console.warn("You have been removed from session " + sessionName); - } - ); -} +async function httpPostRequest(url, body, errorMsg, successCallback) { + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); -function httpPostRequest(url, body, errorMsg, callback) { - var http = new XMLHttpRequest(); - http.open('POST', url, true); - http.setRequestHeader('Content-type', 'application/json'); - http.addEventListener('readystatechange', processRequest, false); - http.send(JSON.stringify(body)); - - function processRequest() { - if (http.readyState == 4) { - if (http.status == 200) { - try { - callback(JSON.parse(http.responseText)); - } catch (e) { - callback(); - } - } else { - console.warn(errorMsg); - console.warn(http.responseText); - } + if (response.ok) { + const data = await response.json(); + successCallback(data); + } else { + console.warn(errorMsg); + console.warn('Error: ', response); } + } catch (error) { + console.error(error); } } /* APPLICATION REST METHODS */ - - /* APPLICATION BROWSER METHODS */ -window.onbeforeunload = () => { // Gracefully leave session - if (session) { - removeUser(); - leaveSession(); +window.onbeforeunload = () => { + if (room) { + leaveRoom(); + } + logOut(); +}; + +function getLivekitUrlFromMetadata(token) { + if (!token) throw new Error('Trying to get metadata from an empty token'); + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent( + window + .atob(base64) + .split('') + .map((c) => { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join('') + ); + + const payload = JSON.parse(jsonPayload); + if (!payload?.metadata) throw new Error('Token does not contain metadata'); + const metadata = JSON.parse(payload.metadata); + return metadata.livekitUrl; + } catch (error) { + throw new Error('Error decoding and parsing token: ' + error); } } -function appendUserData(videoElement, connection) { - var clientData; - var serverData; - var nodeId; - if (connection.nickName) { // Appending local video data - clientData = connection.nickName; - serverData = connection.userName; - nodeId = 'main-videodata'; - } else { - clientData = JSON.parse(connection.data.split('%/%')[0]).clientData; - serverData = JSON.parse(connection.data.split('%/%')[1]).serverData; - nodeId = connection.connectionId; - } +function appendUserData(videoElement, participantName, nickname) { var dataNode = document.createElement('div'); - dataNode.className = "data-node"; - dataNode.id = "data-" + nodeId; - dataNode.innerHTML = "

" + clientData + "

" + serverData + "

"; + dataNode.className = 'removable'; + dataNode.id = 'data-' + participantName; + dataNode.innerHTML = ` +

${nickname}

+

${participantName}

+ `; videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling); - addClickListener(videoElement, clientData, serverData); + addClickListener(videoElement, participantName); } -function removeUserData(connection) { - var userNameRemoved = $("#data-" + connection.connectionId); - if ($(userNameRemoved).find('p.userName').html() === $('#main-video p.userName').html()) { - cleanMainVideo(); // The participant focused in the main video has left - } - $("#data-" + connection.connectionId).remove(); +function removeUserData(participant) { + var dataNode = document.getElementById('data-' + participant.identity); + dataNode?.parentNode.removeChild(dataNode); } function removeAllUserData() { - $(".data-node").remove(); + var elementsToRemove = document.getElementsByClassName('removable'); + while (elementsToRemove[0]) { + elementsToRemove[0].parentNode.removeChild(elementsToRemove[0]); + } } function cleanMainVideo() { @@ -269,35 +242,33 @@ function addClickListener(videoElement, clientData, serverData) { videoElement.addEventListener('click', function () { var mainVideo = $('#main-video video').get(0); if (mainVideo.srcObject !== videoElement.srcObject) { - $('#main-video').fadeOut("fast", () => { - $('#main-video p.nickName').html(clientData); - $('#main-video p.userName').html(serverData); + $('#main-video').fadeOut('fast', () => { + $('#main-video p.nickname').html(clientData); + $('#main-video p.participantName').html(serverData); mainVideo.srcObject = videoElement.srcObject; - $('#main-video').fadeIn("fast"); + $('#main-video').fadeIn('fast'); }); } }); } -function initMainVideo(videoElement, userData) { +function initMainVideo(videoElement, participantName, nickname) { $('#main-video video').get(0).srcObject = videoElement.srcObject; - $('#main-video p.nickName').html(userData.nickName); - $('#main-video p.userName').html(userData.userName); - $('#main-video video').prop('muted', true); + $('#main-video p.nickname').html(nickname); + $('#main-video p.participantName').html(participantName); } function initMainVideoThumbnail() { - $('#main-video video').css("background", "url('images/subscriber-msg.jpg') round"); + $('#main-video video').css( + 'background', + "url('images/subscriber-msg.jpg') round" + ); } -function isPublisher(userName) { - return userName.includes('publisher'); -} - -function cleanSessionView() { +function cleanRoomView() { removeAllUserData(); cleanMainVideo(); - $('#main-video video').css("background", ""); + $('#main-video video').css('background', ''); } -/* APPLICATION BROWSER METHODS */ \ No newline at end of file +/* APPLICATION BROWSER METHODS */ diff --git a/openvidu-roles-java/src/main/resources/static/index.html b/openvidu-roles-java/src/main/resources/static/index.html index c09a6ba4..b1520ef7 100644 --- a/openvidu-roles-java/src/main/resources/static/index.html +++ b/openvidu-roles-java/src/main/resources/static/index.html @@ -1,130 +1,210 @@ + + openvidu-roles-node - - openvidu-roles-java + + - - + + + + + + - - - - - - - - - - - + + - + + - - - + -
-
-
-
-

- -

-

- -

-

- -

-
- - - - - - - - - - - - - - - - - - - - - -
UserPassRole
publisher1passPUBLISHER
publisher2passPUBLISHER
subscriberpassSUBSCRIBER
-
+
+
+
+ +
+
+

+ +

+

+ +

+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + +
UserPassword + Role +
publisher1passPUBLISHER
publisher2passPUBLISHER
subscriberpassSUBSCRIBER
+
-