0


一小时教你用SpringBoot+WebSocket+WebRTC实现视频通话

一小时教你用SpringBoot+WebSocket+WebRTC实现视频通话

1. 运行结果

SpringBoot+WebSocket+WebRTC实现视频通话

上述运行结果中是有声音(比较小而已)及动的画面的(画面不是静止的)。

网上关于webrtc的文档(文章)和视频也挺多的,但是用SpringBoot结合WebRTC的却屈指可数,前一段时间小编我学习了一下WebRTC的相关知识,于是用SpringBoot+WebRTC实现了一个多人的线上自习室(有画面,但是没有声音的那种,开启声音也挺简单,在js代码里设置一下即可[运行结果在最后的总结里])。最近CSDN有活动,正好把前一段时间学习的知识运用起来(下述代码只是实现了,但是其中的逻辑是存在一定问题的,所以如果读者用下述代码,切记需要改动改动哈!)。既然是WebRTC,为什么又和WebSocket扯上关系了呢?因为利用WebSocket技术来发送消息具有实时性,你看我在这端发送一个消息出去,只要另一端处于连接状态,那么就可以接收到这个消息。而如果使用的是http、https等的话,这一端你发送一个消息,另外一段需要刷新一下页面才能看到消息(当然可以搞个定时器)。结合WebSocket技术,能很快速地实现一个视频通话功能。

2. 实现

导入相关jar包的依赖,如下:

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

上述jar包可能有一些不需要的喔!

2.1 后端实现

websocket 配置类
GetHttpSessionConfig.class

packagecom.example.demo.websocket2;importjavax.servlet.http.HttpSession;importjavax.websocket.HandshakeResponse;importjavax.websocket.server.HandshakeRequest;importjavax.websocket.server.ServerEndpointConfig;publicclassGetHttpSessionConfigextendsServerEndpointConfig.Configurator{@OverridepublicvoidmodifyHandshake(ServerEndpointConfig sec,HandshakeRequest request,HandshakeResponse response){HttpSession httpSession =(HttpSession) request.getHttpSession();// 获取httpsession对象

        sec.getUserProperties().put(HttpSession.class.getName(), httpSession);}}

ServerEndpointExporter Bean的定义 Config.class

packagecom.example.demo.websocket2;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.socket.server.standard.ServerEndpointExporter;@ConfigurationpublicclassConfig{@BeanpublicServerEndpointExporterserverEndpointExporter(){returnnewServerEndpointExporter();}}

*websocket服务器类 WebSocketServer *

packagecom.example.demo.websocket2;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpSession;importjavax.websocket.*;importjavax.websocket.server.ServerEndpoint;importjava.io.IOException;importjava.util.Map;importjava.util.Set;importjava.util.concurrent.ConcurrentHashMap;@Component@ServerEndpoint(value ="/video",configurator =GetHttpSessionConfig.class)publicclassWebSocketServer{//存储客户端的连接对象,每个客户端连接都会产生一个连接对象privatestaticConcurrentHashMap<String,WebSocketServer> map =newConcurrentHashMap();//每个连接都会有自己的会话privateSession session;privateString account;@OnOpenpublicvoidopen(Session session,EndpointConfig config){HttpSession httpSession =(HttpSession) config.getUserProperties().get(HttpSession.class.getName());String account =String.valueOf(httpSession.getAttribute("account"));

        map.put(account,this);this.session = session;this.account = account;}@OnClosepublicvoidclose(){
        map.remove(account);}@OnErrorpublicvoiderror(Throwable error){
        error.printStackTrace();}@OnMessagepublicvoidgetMessage(String message)throwsIOException{Set<Map.Entry<String,WebSocketServer>> entries = map.entrySet();for(Map.Entry<String,WebSocketServer> entry : entries){if(!entry.getKey().equals(account)){//将消息转发到其他非自身客户端
                entry.getValue().send(message);}}}publicvoidsend(String message)throwsIOException{if(session.isOpen()){
            session.getBasicRemote().sendText(message);}}publicintgetConnetNum(){return map.size();}}
2.2 前端页面实现

登录界面的代码就不在这儿粘贴了,下面主要展示视频通话界面的代码(包括css样式和js代码都在的)

<html><head><title>main</title><linkrel="stylesheet"href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap-theme.min.css"/></head><style>body{background: #eee;padding: 5% 0;}video{background: black;border: 1px solid gray;}.call-page{position: relative;display: block;margin: 0 auto;width: 500px;height: 500px;}#localVideo{width: 150px;height: 150px;position: absolute;top: 15px;right: 15px;}#remoteVideo{width: 500px;height: 500px;}</style><body><divid="callPage"class="call-page"><videoid="localVideo"autoplay></video><videoid="remoteVideo"autoplay></video><divclass="row text-center"><divclass="col-md-12"><inputid="callToUsernameInput"type="text"placeholder="username to call"/><buttonid="callBtn"class="btn-success btn">Call</button><buttonid="hangUpBtn"class="btn-danger btn">Hang Up</button></div></div></div><scripttype="text/javascript">//our usernamevar connectedUser;//connecting to our signaling servervar conn =newWebSocket("ws://localhost:9999/video");

    conn.onopen=function(){
        console.log("Connected to the signaling server");};//when we got a message from a signaling server
    conn.onmessage=function(msg){
        console.log("Got message", msg.data);var data =JSON.parse(msg.data);switch(data.type){case"login":handleLogin(data.success);break;//when somebody wants to call uscase"offer":handleOffer(data.offer, data.name);break;case"answer":handleAnswer(data.answer);break;//when a remote peer sends an ice candidate to uscase"candidate":handleCandidate(data.candidate);break;case"leave":handleLeave();break;default:break;}};

    conn.onerror=function(err){
        console.log("Got error", err);};//alias for sending JSON encoded messagesfunctionsend(message){//attach the other peer username to our messagesif(connectedUser){
            message.name = connectedUser;}

        conn.send(JSON.stringify(message));}//******//UI selectors block//******var callPage = document.querySelector("#callPage");var callToUsernameInput = document.querySelector("#callToUsernameInput");var callBtn = document.querySelector("#callBtn");var hangUpBtn = document.querySelector("#hangUpBtn");var localVideo = document.querySelector("#localVideo");var remoteVideo = document.querySelector("#remoteVideo");var yourConn;var stream;// callPage.style.display = "none";var PeerConnection =(window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCPeerConnection ||undefined);var RTCSessionDescription =(window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription ||undefined);

    navigator.getUserMedia =(navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia ||
        navigator.msGetUserMedia);//**********************//Starting a peer connection//**********************//getting local video stream
    navigator.getUserMedia({video:true,audio:true},function(myStream){
        stream = myStream;//displaying local video stream on the page
        localVideo.srcObject = stream;//using Google public stun servervar configuration ={"iceServers":[]};

        yourConn =newPeerConnection(configuration);// setup stream listening
        yourConn.addStream(stream);//when a remote user adds stream to the peer connection, we display it
        yourConn.onaddstream=function(e){
            remoteVideo.srcObject = e.stream;};// Setup ice handling
        yourConn.onicecandidate=function(event){if(event.candidate){send({type:"candidate",candidate: event.candidate
                });}};},function(error){
        console.log(error);});//initiating a call
    callBtn.addEventListener("click",function(){var callToUsername = callToUsernameInput.value;if(callToUsername.length >0){

            connectedUser = callToUsername;// create an offer
            yourConn.createOffer(function(offer){send({type:"offer",offer: offer
                });

                yourConn.setLocalDescription(offer);},function(error){alert("Error when creating an offer");});}});//when somebody sends us an offerfunctionhandleOffer(offer, name){
        connectedUser = name;
        yourConn.setRemoteDescription(newRTCSessionDescription(offer));//create an answer to an offer
        yourConn.createAnswer(function(answer){
            yourConn.setLocalDescription(answer);send({type:"answer",answer: answer
            });},function(error){alert("Error when creating an answer");});}//when we got an answer from a remote userfunctionhandleAnswer(answer){
        yourConn.setRemoteDescription(newRTCSessionDescription(answer));}//when we got an ice candidate from a remote userfunctionhandleCandidate(candidate){
        yourConn.addIceCandidate(newRTCIceCandidate(candidate));}//hang up
    hangUpBtn.addEventListener("click",function(){send({type:"leave"});handleLeave();});functionhandleLeave(){
        connectedUser =null;
        remoteVideo.src =null;

        yourConn.close();
        yourConn.onicecandidate =null;
        yourConn.onaddstream =null;}</script></body></html>
3. 总结

上述前端代码参考来自这里:webrtc视频演示,上述代码中如果有不懂的读者可以去仔细看看这个链接里的知识,里面关于webrtc有详细的介绍及实现,不过,没有讲多人的,它只讲了一对一的,不过,前一段时间小编在参考一些大佬的实现思路及自己思考下,也实现了一个多人的,运行结果如下:

基于SpringBoot,WebSocket,WebRTC实现多人自习室功能


本文转载自: https://blog.csdn.net/qq_45404396/article/details/131348216
版权归原作者 坚持不懈的大白 所有, 如有侵权,请联系我们删除。

“一小时教你用SpringBoot+WebSocket+WebRTC实现视频通话”的评论:

还没有评论