websocket 局域网 webrtc 一对一 视频通话的实例

基本介绍

使用websocket来 WebRTC 建立连接时的 数据的传递和交换。

WebRTC 建立连接时,通常需要按照以下顺序执行一些步骤:
1.创建本地 PeerConnection 对象:使用 RTCPeerConnection 构造函数创建本地的 PeerConnection 对象,该对象用于管理 WebRTC 连接。

2.添加本地媒体流:通过调用 getUserMedia 方法获取本地的音视频流,并将其添加到 PeerConnection 对象中。这样可以将本地的音视频数据发送给远程对等方。

3.创建和设置本地 SDP:使用 createOffer 方法创建本地的 Session Description Protocol (SDP),描述本地对等方的音视频设置和网络信息。然后,通过调用 setLocalDescription 方法将本地 SDP 设置为本地 PeerConnection 对象的本地描述。

4.发送本地 SDP:将本地 SDP 发送给远程对等方,可以使用信令服务器或其他通信方式发送。

5.接收远程 SDP:从远程对等方接收远程 SDP,可以通过信令服务器或其他通信方式接收。

6.设置远程 SDP:使用接收到的远程 SDP,调用 PeerConnection 对象的 setRemoteDescription 方法将其设置为远程描述。

7.创建和设置本地 ICE 候选项:使用 onicecandidate 事件监听 PeerConnection 对象的 ICE 候选项生成,在生成候选项后,通过信令服务器或其他通信方式将其发送给远程对等方。

8.接收和添加远程 ICE 候选项:从远程对等方接收到 ICE 候选项后,调用 addIceCandidate 方法将其添加到本地 PeerConnection 对象中。

9.连接建立:一旦本地和远程的 SDP 和 ICE 候选项都设置好并添加完毕,连接就会建立起来。此时,音视频流可以在本地和远程对等方之间进行传输。

windwos+局域网直连的环境,测试过程的问题

1.http协议下安全性原因导致无法调用摄像头和麦克风
chrome://flags/ 配置安全策略 或者 配置本地的https环境
在这里插入图片描述
2. 开启了防火墙,webRTC连接失败,
windows防火墙-高级设置-入站规则-新建规则-端口 ,udp
UDP: 32355-65535 放行
为了方便测试 直接关闭防火墙也行

效果如下 局域网0延迟:

在这里插入图片描述

如下demo级别的代码,复制运行就能直接测试,简单修改后,可实现基于webrtc的 共享桌面、视频录制等功能。

html代码

<!DOCTYPE>
<html><head><meta charset="UTF-8"><title>WebRTC + WebSocket</title><meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"><style>html,body {margin: 0;padding: 0;}#main {position: absolute;width: 370px;height: 550px;}#localVideo {position: absolute;background: #757474;top: 10px;right: 10px;width: 100px;height: 150px;z-index: 2;}#remoteVideo {position: absolute;top: 0px;left: 0px;width: 100%;height: 100%;background: #222;}#buttons {z-index: 3;bottom: 20px;left: 90px;position: absolute;}#toUser {border: 1px solid #ccc;padding: 7px 0px;border-radius: 5px;padding-left: 5px;margin-bottom: 5px;}#toUser:focus {border-color: #66afe9;outline: 0;-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)}#call {width: 70px;height: 35px;background-color: #00BB00;border: none;margin-right: 25px;color: white;border-radius: 5px;}#hangup {width: 70px;height: 35px;background-color: #FF5151;border: none;color: white;border-radius: 5px;}</style>
</head><body><div id="main"><video id="remoteVideo" playsinline autoplay></video><video id="localVideo" playsinline autoplay muted></video><div id="buttons"><input id="myid" /><input id="toUser" placeholder="输入在线好友账号" /><br /><button id="call">视频通话</button><button id="hangup">挂断</button></div></div>
</body>
<!-- 可引可不引 -->
<!--<script th:src="@{/js/adapter-2021.js}"></script>-->
<script type="text/javascript" th:inline="javascript">function generateRandomLetters(length) {let result = '';const characters = 'abcdefghijklmnopqrstuvwxyz'; // 字母表for (let i = 0; i < length; i++) {const randomIndex = Math.floor(Math.random() * characters.length);const randomLetter = characters[randomIndex];result += randomLetter;}return result;}let username = generateRandomLetters(2);document.getElementById('myid').value = username;let localVideo = document.getElementById('localVideo');let remoteVideo = document.getElementById('remoteVideo');let websocket = null;let peer = null;let candidate = null;/* WebSocket */function WebSocketInit() {//判断当前浏览器是否支持WebSocketif ('WebSocket' in window) {websocket = new WebSocket("ws://192.168.31.14:8181/webrtc/" + username);} else {alert("当前浏览器不支持WebSocket!");}//连接发生错误的回调方法websocket.onerror = function (e) {alert("WebSocket连接发生错误!");};//连接关闭的回调方法websocket.onclose = function () {console.error("WebSocket连接关闭");};//连接成功建立的回调方法websocket.onopen = function () {console.log("WebSocket连接成功");};//接收到消息的回调方法websocket.onmessage = async function (event) {let { type, fromUser, msg, sdp, iceCandidate } = JSON.parse(event.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r"));console.log(type, fromUser, msg, sdp, iceCandidate);if (type === 'hangup') {console.log(msg);document.getElementById('hangup').click();return;}if (type === 'call_start') {let msg = "0"if (confirm(fromUser + "发起视频通话,确定接听吗") == true) {document.getElementById('toUser').value = fromUser;WebRTCInit();msg = "1"}websocket.send(JSON.stringify({type: "call_back",toUser: fromUser,fromUser: username,msg: msg}));return;}if (type === 'call_back') {if (msg === "1") {console.log(document.getElementById('toUser').value + "同意视频通话");//创建本地视频并发送offerlet stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })localVideo.srcObject = stream;console.log(peer);stream.getTracks().forEach(track => {peer.addTrack(track, stream);});let offer = await peer.createOffer();await peer.setLocalDescription(offer);let newOffer = offer.toJSON();newOffer["fromUser"] = username;newOffer["toUser"] = document.getElementById('toUser').value;websocket.send(JSON.stringify(newOffer));} else if (msg === "0") {alert(document.getElementById('toUser').value + "拒绝视频通话");document.getElementById('hangup').click();} else {alert(msg);document.getElementById('hangup').click();}return;}if (type === 'offer') {let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });localVideo.srcObject = stream;stream.getTracks().forEach(track => {peer.addTrack(track, stream);});await peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));let answer = await peer.createAnswer();let newAnswer = answer.toJSON();newAnswer["fromUser"] = username;newAnswer["toUser"] = document.getElementById('toUser').value;websocket.send(JSON.stringify(newAnswer));await peer.setLocalDescription(answer);return;}if (type === 'answer') {peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));return;}if (type === '_ice') {peer.addIceCandidate(iceCandidate);return;}}}/* WebRTC */function WebRTCInit() {peer = new RTCPeerConnection();//icepeer.onicecandidate = function (e) {if (e.candidate) {websocket.send(JSON.stringify({type: '_ice',toUser: document.getElementById('toUser').value,fromUser: username,iceCandidate: e.candidate}));}};//trackpeer.ontrack = function (e) {if (e && e.streams) {remoteVideo.srcObject = e.streams[0];}};}/* 按钮事件 */function ButtonFunInit() {//视频通话document.getElementById('call').onclick = function (e) {document.getElementById('toUser').style.visibility = 'hidden';let toUser = document.getElementById('toUser').value;if (!toUser) {alert("请先指定好友账号,再发起视频通话!");return;}if (peer == null) {WebRTCInit();}websocket.send(JSON.stringify({type: "call_start",fromUser: username,toUser: toUser,}));}//挂断document.getElementById('hangup').onclick = function (e) {document.getElementById('toUser').style.visibility = 'unset';if (localVideo.srcObject) {const videoTracks = localVideo.srcObject.getVideoTracks();videoTracks.forEach(videoTrack => {videoTrack.stop();localVideo.srcObject.removeTrack(videoTrack);});}if (remoteVideo.srcObject) {const videoTracks = remoteVideo.srcObject.getVideoTracks();videoTracks.forEach(videoTrack => {videoTrack.stop();remoteVideo.srcObject.removeTrack(videoTrack);});//挂断同时,通知对方websocket.send(JSON.stringify({type: "hangup",fromUser: username,toUser: document.getElementById('toUser').value,}));}if (peer) {peer.ontrack = null;peer.onremovetrack = null;peer.onremovestream = null;peer.onicecandidate = null;peer.oniceconnectionstatechange = null;peer.onsignalingstatechange = null;peer.onicegatheringstatechange = null;peer.onnegotiationneeded = null;peer.close();peer = null;}localVideo.srcObject = null;remoteVideo.srcObject = null;}}WebSocketInit();ButtonFunInit();</script></html>

websocket java代码

package com.coco.boot.config;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** WebRTC + WebSocket*/
@Slf4j
@Component
@ServerEndpoint(value = "/webrtc/{username}")
public class WebRtcWSServer {/*** 连接集合*/private static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("username") String username, @PathParam("publicKey") String publicKey) {sessionMap.put(username, session);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session) {for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {if (entry.getValue() == session) {sessionMap.remove(entry.getKey());break;}}}/*** 发生错误时调用*/@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic void onMessage(String message, Session session) {try{//jacksonObjectMapper mapper = new ObjectMapper();mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);//JSON字符串转 HashMapHashMap hashMap = mapper.readValue(message, HashMap.class);//消息类型String type = (String) hashMap.get("type");//to userString toUser = (String) hashMap.get("toUser");Session toUserSession = sessionMap.get(toUser);String fromUser = (String) hashMap.get("fromUser");//msgString msg = (String) hashMap.get("msg");//sdpString sdp = (String) hashMap.get("sdp");//iceMap iceCandidate  = (Map) hashMap.get("iceCandidate");HashMap<String, Object> map = new HashMap<>();map.put("type",type);//呼叫的用户不在线if(toUserSession == null){toUserSession = session;map.put("type","call_back");map.put("fromUser","系统消息");map.put("msg","Sorry,呼叫的用户不在线!");send(toUserSession,mapper.writeValueAsString(map));return;}//对方挂断if ("hangup".equals(type)) {map.put("fromUser",fromUser);map.put("msg","对方挂断!");}//视频通话请求if ("call_start".equals(type)) {map.put("fromUser",fromUser);map.put("msg","1");}//视频通话请求回应if ("call_back".equals(type)) {map.put("fromUser",toUser);map.put("msg",msg);}//offerif ("offer".equals(type)) {map.put("fromUser",toUser);map.put("sdp",sdp);}//answerif ("answer".equals(type)) {map.put("fromUser",toUser);map.put("sdp",sdp);}//iceif ("_ice".equals(type)) {map.put("fromUser",toUser);map.put("iceCandidate",iceCandidate);}send(toUserSession,mapper.writeValueAsString(map));}catch(Exception e){e.printStackTrace();}}/*** 封装一个send方法,发送消息到前端*/private void send(Session session, String message) {try {System.out.println(message);session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}
}

springboot 相关依赖和配置

// 1.pom
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>// 2.启用功能
@EnableWebSocket
public class CocoBootApplication3.config		
package com.coco.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

主要参考:
WebRTC + WebSocket 实现视频通话
WebRTC穿透服务器防火墙配置问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/291434.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬取b站音频和视频数据,未合成一个视频

一、首先找到含有音频和视频的url地址 打开一个视频&#xff0c;刷新后&#xff0c;找到这个包&#xff0c;里面有我们所需要的数据 访问这个数据包后&#xff0c;获取字符串数据&#xff0c;用正则提取&#xff0c;再转为json字符串方便提取。 二、获得标题和音频数据后&…

FreeRTOS day1

1.总结keil5下载代码和编译代码需要注意的事项 需要与板子连通 配置完成后才点击下载 2.总结STM32Cubemx的使用方法和需要注意的事项 下载支持包 打开芯片配置界面 3.总结STM32Cubemx配置GPIO的方法

【动手学深度学习-pytorch】9.2长短期记忆网络(LSTM)

长期以来&#xff0c;隐变量模型存在着长期信息保存和短期输入缺失的问题。 解决这一问题的最早方法之一是长短期存储器&#xff08;long short-term memory&#xff0c;LSTM&#xff09; (Hochreiter and Schmidhuber, 1997)。 它有许多与门控循环单元&#xff08; 9.1节&…

目标检测评价标准

主要借鉴&#xff1a;https://github.com/rafaelpadilla/Object-Detection-Metrics?tabreadme-ov-file 主要评价指标、术语&#xff1a; Intersection Over Union (IOU)&#xff1a;两个检测框交集面积与并集面积的比值 True Positive (TP)&#xff1a;IOU大于阈值的检测框…

uniapp实现列表动态添加

1.效果图&#xff1a; 2.代码实现&#xff1a; 这里没有用uniapp提供的uni-list控件 <template> <view id"app"> <!-- 这里为了让标题&#xff08;h&#xff09;居中展示&#xff0c;给h标签设置了父标签&#xff0c;并设置父标签text-…

物联网实战--入门篇之(四)嵌入式-UART驱动

目录 一、串口简介 二、串口驱动设计 三、串口发送 四、串口接收处理 五、PM2.5数据接收处理 六、printf重定义 七、总结 一、串口简介 串口在单片机的开发中属于非常常用的外设&#xff0c;最基本的都会预留一个调试串口用来输出调试信息&#xff0c;串口时序这里就不谈…

既有理论深度又有技术细节——深度学习计算机视觉

推荐序 我曾经试图找到一本既有理论深度、知识广度&#xff0c;又有技术细节、数学原理的关于深度学习的书籍&#xff0c;供自己学习&#xff0c;也推荐给我的学生学习。虽浏览文献无数&#xff0c;但一直没有心仪的目标。两周前&#xff0c;刘升容女士将她的译作《深度学习计…

java中的单例模式

一、描述 单例模式就是程序中一个类只能有一个对象实例 举个例子: //引出单例模式&#xff0c;一个类中只能由一个对象实例 public class Singleton1 {private static Singleton1 instance new Singleton1();//通过这个方法来获取实例public static Singleton1 getInstance…

Verilog语法回顾--门级和开关级模型

目录 门和开关的声明 门和开关类型 支持驱动强度的门 延迟 实例数组 and&#xff0c;nand&#xff0c;nor&#xff0c;or&#xff0c;xor&#xff0c;xnor buf&#xff0c;not bufif1&#xff0c;bufif0&#xff0c;notif1&#xff0c;notif0 MOS switches Bidirecti…

TSINGSEE青犀智慧工厂视频汇聚与安全风险智能识别和预警方案

在智慧工厂的建设中&#xff0c;智能视频监控方案扮演着至关重要的角色。它不仅能够实现全方位、无死角的监控&#xff0c;还能够通过人工智能技术&#xff0c;实现智能识别、预警和分析&#xff0c;为工厂的安全生产和高效运营提供有力保障。 TSINGSEE青犀智慧工厂智能视频监…

公司官网怎么才会被百度收录

在互联网时代&#xff0c;公司官网是企业展示自身形象、产品与服务的重要窗口。然而&#xff0c;即使拥有精美的官网&#xff0c;如果不被搜索引擎收录&#xff0c;就无法被用户发现。本文将介绍公司官网如何被百度收录的一些方法和步骤。 1. 创建和提交网站地图 创建网站地图…

C语言例3-5:阅读下列程序,写出程序运行的结果。

代码如下&#xff1a; #include <stdio.h> int main(void) {int i1,s3;do{si;if(s%70) continue;else i;}while(s<15);printf("%d",i);return 0; } 结果如下&#xff1a; 分析&#xff1a; s314437741111617i3468

四、e2studio VS STM32CubeIDE之STM32CubeIDE线程安全解决方案

目录 一、概述/目的 二、原因和办法 三、线程安全问题的描述 四、STM32解决方案 4.1 通用策略 4.2 RTOS策略 4.3 策略的讲解 4.3.1 裸机应用(策略2、3) 4.3.2 RTOS应用(策略4、5) 五、关键源码 四、e2studio VS STM32CubeIDE之STM32CubeIDE线程安全解决方案 一、概述…

Spring Boot简介及案例

文章目录 Spring Boot简介以下是一个简单的 Spring Boot Web 应用实例**步骤 1&#xff1a;创建 Spring Boot 项目****步骤 2&#xff1a;编写 RESTful 控制器****步骤 3&#xff1a;配置主类****步骤 4&#xff1a;运行并测试应用** Spring Boot简介 Spring Boot 是一个用于简…

怎么让ChatGPT批量写作原创文章

随着人工智能技术的不断发展&#xff0c;自然语言处理模型在文本生成领域的应用也日益广泛。ChatGPT作为其中的佼佼者之一&#xff0c;凭借其强大的文本生成能力和智能对话特性&#xff0c;为用户提供了一种高效、便捷的批量产出内容的解决方案。以下将就ChatGPT批量写作内容进…

【AI】命令行调用大模型

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 【AI】命令行调用大模型引入正文初始化项目撰写脚本全局安装 成果展示 【AI】命令…

Ubuntu20.04LTS+uhd3.15+gnuradio3.8.1源码编译及安装

文章目录 前言一、卸载本地 gnuradio二、安装 UHD 驱动三、编译及安装 gnuradio四、验证 前言 本地 Ubuntu 环境的 gnuradio 是按照官方指导使用 ppa 的方式安装 uhd 和 gnuradio 的&#xff0c;也是最方便的方法&#xff0c;但是存在着一个问题&#xff0c;就是我无法修改底层…

亚信安全联合人保财险推出数字安全保障险方案,双重保障企业数字化转型

数字化发展&#xff0c;新兴技术的应用与落地带来网络攻击的进一步演进升级&#xff0c;同时全球产业链供应链融合协同的不断加深&#xff0c;更让网络威胁的影响范围与危害程度不断加剧。 企业单纯依靠自身安全能力建设&#xff0c;能否跟上网络威胁的进化速度&#xff1f;能否…

Day49:WEB攻防-文件上传存储安全OSS对象分站解析安全解码还原目录执行

目录 文件-解析方案-目录执行权限&解码还原 目录执行权限 解码还原 文件-存储方案-分站存储&OSS对象 分站存储 OSS对象存储 知识点&#xff1a; 1、文件上传-安全解析方案-目录权限&解码还原 2、文件上传-安全存储方案-分站存储&OSS对象 文件-解析方案-目…

【深耕 Python】Data Science with Python 数据科学(2)jupyter-lab和numpy数组

关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a;【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 Jupyter代码片段1&#xff1a;简单数组的定义和排序 import numpy as np np.array([1, 2, 3]) a np.array([9, 6, 2, …