- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
PeerJS 是一个基于浏览器WebRTC功能实现的js功能包,简化了WebrRTC的开发过程,对底层的细节做了封装,直接调用API即可,再配合surging 协议组件化从而做到稳定,高效可扩展的微服务,再利用RtmpToWebrtc 引擎组件可以做到不仅可以利用httpflv 观看rtmp推流直播,还可以采用基于 Webrtc的peerjs 进行观看,那么今天要讲的是如何结合开发语音视频通话功能。放到手机和电脑上都可以实现语音视频通话.
一键运行打包成品下载:https://pan.baidu.com/s/1MVsjKtVYpUonauAz9ZXtPg?pwd=1q2g 。
测试用户:fanly 。
测试密码:123456 。
为了让大家节约时间,能尽快运行产品看到效果,上面有 一键运行打包成品可以进行下载测试运行.
以下是目录结构.
IDE:consul 注册中心 。
kayak.client: 网关 。
kayak.server:微服务 。
apache-skywalking-apm:skywalking链路跟踪 。
。
以上是目录结构, 不需要进入管理界面配置网络组件,启动后自带端口96的ws协议主机,只要打开video文件夹,里面有两个语音通话的html测试文件,在同一一个局域网只要输入对方的name就可以进行语音通话 。
打开界面如图 。
。
以上是没有开发环境的进行下载进行下载测试,那么正在使用surging 的如何开发此功能呢?
1. 创建服务接口,继承于IServiceKey 。
[ServiceBundle("Device/{Service}")] public interface IChatService : IServiceKey { }
2. 创建中间服务,继承于WSBehavior, IChatService 。
internal class ChatService : WSBehavior, IChatService { private static readonly ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>(); private static readonly ConcurrentDictionary<string, string> _clients = new ConcurrentDictionary<string, string>(); protected override void OnOpen() { var _name = Context.QueryString["name"]; if (!string.IsNullOrEmpty(_name)) { _clients[ID] = _name; _users[_name] = ID; } } protected override void OnError( WebSocketCore.ErrorEventArgs e) { var msg = e.Message; } protected override void OnMessage(MessageEventArgs e) { if (_clients.ContainsKey(ID)) { var message = JsonConvert.DeserializeObject<Dictionary<string, object>>(e.Data); //消息类型 message.TryGetValue("type",out object @type); message.TryGetValue("toUser", out object toUser); message.TryGetValue("fromUser", out object fromUser); message.TryGetValue("msg", out object msg); message.TryGetValue("sdp", out object sdp); message.TryGetValue("iceCandidate", out object iceCandidate); Dictionary<String, Object> result = new Dictionary<String, Object>(); result.Add("type", @type); //呼叫的用户不在线 if (!_users.ContainsKey(toUser?.ToString())) { result["type"]= "call_back"; result.Add("fromUser", "系统消息"); result.Add("msg", "Sorry,呼叫的用户不在线!"); this.Client().SendTo(JsonConvert.SerializeObject(result), ID); return; } //对方挂断 if ("hangup".Equals(@type)) { result.Add("fromUser", fromUser); result.Add("msg", "对方挂断!"); } //视频通话请求 if ("call_start".Equals(@type)) { result.Add("fromUser", fromUser); result.Add("msg", "1"); } //视频通话请求回应 if ("call_back".Equals(type)) { result.Add("fromUser", toUser); result.Add("msg", msg); } //offer if ("offer".Equals(type)) { result.Add("fromUser", toUser); result.Add("sdp", sdp); } //answer if ("answer".Equals(type)) { result.Add("fromUser", toUser); result.Add("sdp", sdp); } //ice if ("_ice".Equals(type)) { result.Add("fromUser", toUser); result.Add("iceCandidate", iceCandidate); } this.Client().SendTo(JsonConvert.SerializeObject(result), _users.GetValueOrDefault(toUser?.ToString())); } } protected override void OnClose(CloseEventArgs e) { if( _clients.TryRemove(ID, out string name)) _users.TryRemove (name, out string value); } }
3.设置surgingSettings的WSPort端口配置,默认96 。
以上就是利用websocket协议中转消息,下面是页面如何编号,代码如下:
<!DOCTYPE> <!--解决idea thymeleaf 表达式模板报红波浪线--> <!--suppress ALL --> <html xmlns:th="http://www.thymeleaf.org"> <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="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"> let username = "fanly"; let localVideo = document.getElementById('localVideo'); let remoteVideo = document.getElementById('remoteVideo'); let websocket = null; let peer = null; WebSocketInit(); ButtonFunInit(); /* WebSocket */ function WebSocketInit(){ //判断当前浏览器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://127.0.0.1:961/device/chat?name="+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); 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 + "同意视频通话"); //创建本地视频并发送offer let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) localVideo.srcObject = stream; stream.getTracks().forEach(track => { peer.addTrack(track, stream); }); let offer = await peer.createOffer(); await peer.setLocalDescription(offer); let newOffer = offer; 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; 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(); //ice peer.onicecandidate = function (e) { if (e.candidate) { websocket.send(JSON.stringify({ type: '_ice', toUser:document.getElementById('toUser').value, fromUser:username, iceCandidate: e.candidate })); } }; //track peer.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; } } </script> </html>
以上是页面的代码,如需要添加其它账号测试只要更改username ,或者ws地址也可以更改标记红色的区域.
。
本人正在开发平台,如有疑问可以联系作者,QQ群:744677125 。
最后此篇关于基于surging如何利用peerjs进行语音视频通话的文章就讲到这里了,如果你想了解更多关于基于surging如何利用peerjs进行语音视频通话的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在后台开启了“URL Rewrite”,看起来一切正常,可是点击某一栏目的时候却怎么都进不去,显示的依然是论坛的首页。看了一下论坛目录下,原来这下面并没有自己的.htaccess文件,所以默认使用的
本文实例为大家分享了.net发送邮件的实现代码,供大家参考,具体内容如下 关键代码: 需要引用命名空间: using System.Net.Mail; using System.Net;
今天的一个小测试是老师让用.NET用控件来制作一个拉菜单要求如下: 将鼠标移到父菜单上弹出3个子菜单,而且每个子菜单都有超链接。 以下是我自己做的代码: 复制代
我有以下内容 static const unsigned int chromosome = 6; double bestFitness[chromosomes]; for(int i = 0; i
关于附图,我需要一个计算算法来将 A 轴向下移动 n 英寸,将 B 轴从左向右移动 m 英寸,以便组件圆 D 遵循抛物线的曲线;圆 D 并不总是 10 英寸,可以更小。我不是数学专业的,所以这对我来说
我正在尝试利用我的格式字符串错误,它存在于这个程序中: #include #include #include #include #include void foo(char* tmp, ch
用Matplotlib和Seaborn这类Python库可以画出很好看的图,但是这些图只是静态的,难以动态且美观地呈现数值变化。要是在你下次的演示、视频、社交媒体Po文里能用短视频呈现数据变化,是不
1、进程介绍 进程:正在执行的程序,由程序、数据和进程控制块组成,是正在执行的程序,程序的一次执行过程,是资源调度的基本单位。 程序:没有执行的代码,是一个静态的。 2、线程
1、前言 在开发过程中,有时会遇到需要控制任务并发执行数量的需求。 例如一个爬虫程序,可以通过限制其并发任务数量来降低请求频率,从而避免由于请求过于频繁被封禁问题的发生。 接下来
Opera 管理着一个漏洞赏金计划,研究人员可以在该计划中报告 Opera 软件中的漏洞并获得奖励。 这篇文章就是我发现的一个漏洞——网页可能会从用户那里检索本地文件的屏幕截图。 考虑到 O
C++ 文件查找 在C++中我们要如何查找文件呢?我们需要一个结构体和几个大家可能不太熟悉的函数。这些函数和结构体在的头文件中,结构体为struct _finddata_t ,函数为_findfi
1、前言 本文利用 fsockopen() 函数,编写一个功能简单的端口扫描器。 2、关键技术 本实例的端口号是固定的,通过对数组的遍历,利用 fsockopen() 函数连接,如果连接成功,
最近在将一些项目的rest api迁移到.net core中,最开始是用的Nginx做反向代理,将已经完成切换的部分切入系统,如下图所示: 由于迁移过程中也在进行代码重构,需要经常比较频繁的测
前言 最近学习了python,感觉挺多地方能用到它的。打包 测试 上传 爬电影....而且代码量是真少。人生苦短,我用python。而今天写的这个是因为下载电影时总会发现除了视频还会有这两个文件,
1、Monkey测试简介 Monkey测试是Android平台自动化测试的一种手段,通过Monkey程序模拟用户触摸屏幕、滑动Trackball、按键等操作来对设备上的程序进行压力测试,检测程序
一直想写一套生成静态页面的文章系统 但面对生成静态后的一些复杂数据库交互问题。又望而却步! 于是就想 有没有 在不耽误数据交互的情况下,而又能降低服务器负
Qt 利用大量第 3 方库进行图像编码、压缩、加密、音频和视频编解码器支持等。 从历史上看,当我想使用它们时,我总是必须将它们作为附加依赖项包含在内。我一直想知道是否有一种方法可以简单地重用 Qt 已
我想知道是否可以使用属性将功能“混合”到类/方法/属性中。 就像是: [TrackChanges] public Foo { get; set; } 如果可能的话,有谁会如何实现? 最佳答
有些站点位于共享主机(Windows 2003 Server)上,因此我无法访问服务器配置。 我到处都读到关于杠杆浏览器缓存的信息,特别是静态文件(jpg,css,js等)的信息,但是...在我的情况
我想在我的项目中使用 Julia 的主要原因之一是它的速度,尤其是在计算积分方面。 我想在某个区间 [a,b] 上积分一维函数 f(x)。一般来说,Julia 的 quadgk 函数将是一个快速而准确
我是一名优秀的程序员,十分优秀!