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

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

        • 1. 运行结果
        • 2. 实现
          • 2.1 后端实现
          • 2.2 前端页面实现
        • 3. 总结
1. 运行结果

SpringBoot+WebSocket+WebRTC实现视频通话

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

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

2. 实现

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


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.10
         
    
    com.example
    demo
    0.0.1-SNAPSHOT
    demo
    Demo project for Spring Boot
    
        1.8
    
    

        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        

        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
            org.springframework.boot
            spring-boot-starter-websocket
        

        
            org.projectlombok
            lombok
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    



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

2.1 后端实现

websocket 配置类

GetHttpSessionConfig.class

package com.example.demo.websocket2;

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {

        HttpSession httpSession = (HttpSession) request.getHttpSession();

        // 获取httpsession对象

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

ServerEndpointExporter Bean的定义 Config.class

package com.example.demo.websocket2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class Config {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();
    }
}

*websocket服务器类 WebSocketServer *

package com.example.demo.websocket2;

import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint(value = "/video",configurator = GetHttpSessionConfig.class)
public class WebSocketServer {

    //存储客户端的连接对象,每个客户端连接都会产生一个连接对象
    private static ConcurrentHashMap map = new ConcurrentHashMap();
    //每个连接都会有自己的会话
    private Session session;
    private String account;

    @OnOpen
    public void open(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;
    }

    @OnClose
    public void close(){
        map.remove(account);
    }

    @OnError
    public void error(Throwable error){
        error.printStackTrace();
    }

    @OnMessage
    public void getMessage(String message) throws IOException {

        Set<Map.Entry> entries = map.entrySet();
        for (Map.Entry entry : entries) {
            if(!entry.getKey().equals(account)){//将消息转发到其他非自身客户端
                entry.getValue().send(message);
            }
        }
    }

    public void send(String message) throws IOException {
        if(session.isOpen()){
            session.getBasicRemote().sendText(message);
        }
    }

    public int  getConnetNum(){
        return map.size();
    }
}

2.2 前端页面实现

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



    main
    





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






    
    

    
        
            
            
            
        
    




    //our username
    var connectedUser;

    //connecting to our signaling server
    var conn = new WebSocket("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 us
            case "offer":
                handleOffer(data.offer, data.name);
                break;
            case "answer":
                handleAnswer(data.answer);
                break;
            //when a remote peer sends an ice candidate to us
            case "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 messages
    function send(message) {
        //attach the other peer username to our messages
        if (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 server
        var configuration = {
            "iceServers": []
        };

        yourConn = new PeerConnection(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 offer
    function handleOffer(offer, name) {
        connectedUser = name;
        yourConn.setRemoteDescription(new RTCSessionDescription(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 user
    function handleAnswer(answer) {
        yourConn.setRemoteDescription(new RTCSessionDescription(answer));
    }

    //when we got an ice candidate from a remote user
    function handleCandidate(candidate) {
        yourConn.addIceCandidate(new RTCIceCandidate(candidate));
    }

    //hang up
    hangUpBtn.addEventListener("click", function () {

        send({
            type: "leave"
        });

        handleLeave();
    });

    function handleLeave() {
        connectedUser = null;
        remoteVideo.src = null;

        yourConn.close();
        yourConn.onicecandidate = null;
        yourConn.onaddstream = null;
    }





3. 总结

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

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

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/e788cd20b7.html