一小时教你用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"?>
<project xmlns="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

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<String,WebSocketServer> 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<String, WebSocketServer>> entries = map.entrySet();
        for (Map.Entry<String, WebSocketServer> 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代码都在的)

<html>

<head>
    <title>main</title>
    <link rel = "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>

<div id = "callPage" class = "call-page">
    <video id = "localVideo" autoplay></video>
    <video id = "remoteVideo" autoplay></video>

    <div class = "row text-center">
        <div class = "col-md-12">
            <input id = "callToUsernameInput" type = "text"
                   placeholder = "username to call" />
            <button id = "callBtn" class = "btn-success btn">Call</button>
            <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
        </div>
    </div>

</div>

<script type="text/javascript">
    //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;
    }
</script>

</body>

</html>
3. 总结

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

基于SpringBoot,WebSocket,WebRTC实现多人自习室功能文章来源地址https://www.toymoban.com/news/detail-496836.html

到了这里,关于一小时教你用SpringBoot+WebSocket+WebRTC实现视频通话的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手把手教你用 Jenkins 自动部署 SpringBoot

    CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。 CI/CD 的核心概念可以总结为三点: 持续集成 持续交付 持续部署 CI/CD 主要针对在集成新代码时所引发的问题(俗称\\\"集成地狱\\\")。 为什么会有集成地狱这个“雅称”呢?大家想想我们一个项目部署的

    2024年02月02日
    浏览(49)
  • 短视频ks(某手)高版本最新抓包方案,教你用hook大法绕过QUIC协议

    一般大多数网站、APP最常用的是http、https协议,而某两款最火的短视频dy(某音)、ks(某手)最新版使用的是quic协议(见附录1),导致fiddler和charles无法直接抓到包(某手7版本以下可以直接抓到包)。 网上有说用fiddler + xposed + justTrustMe能绕过某音的sslpinning,呵呵,别傻了

    2023年04月11日
    浏览(47)
  • websocket 局域网 webrtc 一对一 多对多 视频通话 的示例

    基本介绍 WebRTC(Web Real-Time Communications)是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第

    2024年04月28日
    浏览(46)
  • 教你用JavaScript实现键盘控制小方块移动

    欢迎来的我的小院,我是霍大侠,恭喜你今天又要进步一点点了! 我们来用JavaScript编程实战案例,做一个键盘控制小方块移动的案例。该案例主要实现通过按下键盘的上下左右按钮来控制小方块在页面中的移动。通过实战我们会学习到position定位,键盘监听事件以及动态给

    2024年02月09日
    浏览(37)
  • Vue中webSocket+webRtc实现多人会议,webRtc实现

    已经搭建好 websocket 双端通信(可以先模拟),用于实时交换双方信息。交换的信息也就是所谓的信令。实现 webRtc 进行多人会议,屏幕共享、摄像头共享。 我这里定义的websocket信息格式如下 发给某个人,下面会用【消息格式one】指代 发给会议中所有人,下面会用【消息格式

    2024年04月23日
    浏览(49)
  • 可视化 | 教你用Python实现热力图(一)

    本文正在参与新星计划Python学习方向,详情请看:(93条消息) lifein的博客_CSDN博客-SQL SERVER,计算机三级——数据库领域博主 目录 一、导引 二、内容 (一)地图热力图:(动态地图) 1、环境搭建: 2、地图代码:(原始)         在可视化中,热力图可以使用颜色深浅

    2024年02月05日
    浏览(40)
  • vue+flv.js+SpringBoot+websocket实现视频监控与回放

    需求:vue+springboot的项目,需要在页面展示出海康的硬盘录像机连接的摄像头的实时监控画面以及回放功能. 之前项目里是纯前端实现视频监控和回放功能.但是有局限性.就是ip地址必须固定.新的需求里设备ip不固定.所以必须换一种思路. 通过设备的主动注册,让设备去主动连接服

    2024年02月02日
    浏览(36)
  • 手把手教你用Python实现2048小游戏

    感觉好久没有写小游戏玩了,今天恰巧有空.这次我来用Python做个2048小游戏吧.废话不多说,文中有非常详细的代码示例,需要的朋友可以参考下 目录 一、开发环境 二、环境搭建 三、原理介绍 四、效果图 Python版本:3.6.4 相关模块: pygame模块; 以及一些Python自带的模块。 安装

    2024年04月28日
    浏览(69)
  • 一文3000字教你用Python + Jmeter 实现自动化性能压测

    Step01: Python脚本开发 文件路径: D://wl//testproject//Fone-grpc//project1//test_client.py Python 脚本作用: 通过 grpc 调用底层 c++ 的接口,做数据库的数据插入与查询操作,然后将返回的结果进行拼接与输出。 2. 代码里面将每一次调用后返回的内容进行拼接后,并做了成功信息的统计,输

    2024年02月02日
    浏览(43)
  • 手把手教你用pytorch实现k折交叉验证,解决类别不平衡

    在用深度学习做分类的时候,常常需要进行交叉验证,目前pytorch没有通用的一套代码来实现这个功能。可以借助 sklearn中的 StratifiedKFold,KFold来实现,其中StratifiedKFold可以根据类别的样本量,进行数据划分。以5折为例,它可以实现每个类别的样本都是4:1划分。 代码简单的示

    2024年02月05日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包