基于surging 如何利用peerjs进行语音视频通话
一 、 概述
PeerJS 是一个基于浏览器WebRTC功能实现的js功能包,简化了WebrRTC的开发过程,对底层的细节做了封装,直接调用API即可,再配合surging 协议组件化从而做到稳定,高效可扩展的微服务,再利用RtmpToWebrtc 引擎组件可以做到不仅可以利用httpflv 观看rtmp推流直播,还可以采用基于 Webrtc的peerjs 进行观看,那么今天要讲的是如何结合开发语音视频通话功能。放到手机和电脑上都可以实现语音视频通话。
二、如何测试运行
以下是目录结构,
IDE:consul 注册中心
kayak.client: 网关
kayak.server:微服务
apache-skywalking-apm:skywalking链路跟踪
以上是目录结构,大家不需要一个个运行, 不需要进入管理界面配置网路组件,启动后自带启动端口96的ws协议主机,只要打开video文件夹,里面有两个语音通话的html测试文件,在同一一个局域网只要输入对方的name就可以进行语音通话
打开界面如图
三、基于surging如何开发
以上是没有开发环境的进行下载进行下载测试,那么正在使用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进行语音视频通话的更多相关文章
- 利用peerjs轻松玩转webrtc
随着5G技术的推广,可以预见在不久的将来网速将得到极大提升,实时音视频互动这类对网络传输质量要求较高的应用将是最直接的受益者.而且伴随着webrtc技术的成熟,该领域可能将成为下一个技术热点,但是传统 ...
- uniapp+nvue开发之仿微信语音+视频通话功能 :实现一对一语音视频在线通话
本篇文章是利用uni-app和nvue实现微信效果功能的第三篇了,今天我们基于uniapp + nvue实现的uniapp仿微信音视频通话插件实例项目,实现了以下功能: 1: 语音通话 2: 视频 ...
- uniapp+nvue实现仿微信App聊天应用 —— 成功实现好友聊天+语音视频通话功能
基于uniapp + nvue实现的uniapp仿微信App聊天应用 txim 实例项目,实现了以下功能. 1: 聊天会话管理 2: 好友列表 3: 文字.语音.视频.表情.位置等聊天消息收发 4: ...
- Android利用RecognizerIntent识别语音并简单实现打电话动作
关于Android利用RecognizerIntent识别语音并简单实现打电话,详细看实现代码例如以下: package com.example.recognizerintentactivity; i ...
- 从零开始实现基于微信JS-SDK的录音与语音评价功能
最近接受了一个新的需求,希望制作一个基于微信的英语语音评价页面.即点击录音按钮,用户录音说出预设的英文,根据用户的发音给出对应的评价.以下是简单的Demo: ![](reecode/qrcode.pn ...
- 低代码平台--基于surging开发微服务编排流程引擎构思
前言 微服务对于各位并不陌生,在互联网浪潮下不是在学习微服务的路上,就是在使用改造的路上,每个人对于微服务都有自己理解,有用k8s 就说自己是微服务,有用一些第三方框架spring cloud, du ...
- 基于surging 的stage组件设计,谈谈我眼中的微服务。
一.前言 随着业务的发展,并发量的增多,业务的复杂度越来越大,对于系统架构能力要求越来越高,这时候微服务的设计思想应运而生,但是对于微服务需要引擎进行驱动,这时候基于.NET CORE 的微服务引擎s ...
- 基于surging网络组件多协议适配的平台化发展
前言 Surging 发展已经有快6年的时间,经过这些年的发展,功能框架也趋于成熟,但是针对于商业化需求还需要不断的打磨,前段时间客户找到我想升级成平台化,针对他的需求我 ...
- python基于百度unit实现语音识别与语音交互
一.百度Unit新建机器人 网址:https://ai.baidu.com/tech/speech/asr: 1.新建机器人并添加预置技能步骤 (1).新建机器人(添加预置技能),并填写机器人具体信息 ...
- 基于python3.7利用Motor来异步读写Mongodb提高效率
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_111 如果使用Python做大型海量数据批量任务时,并且backend用mongodb做数据储存时,常常面临大量读写数据库的情况. ...
随机推荐
- 常见的SQL数值型数据处理函数
在数据驱动的时代,SQL 已成为数据分析和管理中不可或缺的工具.无论是处理简单的查询还是复杂的数据分析,SQL 都能帮助我们高效地完成任务. 然而,在处理数值型数据时,你是否感到过困惑,不知道如何运用 ...
- Day 4 - 搜索进阶与模拟
启发式搜索 下面将简要介绍启发式搜索及其用法. 定义 启发式搜索(英文:\(\text{heuristic search}\))是一种在普通搜索算法的基础上引入了启发式函数的搜索算法. 启发式函数的作 ...
- SQL:聚集索引和非聚集索引
聚集(clustered)索引,也叫聚簇索引 定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引. 注:第一列的地址表示该行数据在磁盘中的物理地址,后面三列 ...
- 毕设项目:springboot+vue实现的在线求职平台
一.前言 随着信息技术的飞速发展和互联网的普及,线上求职已成为众多求职者和企业招聘的重要渠道.为满足市场需求,我们利用Spring Boot和Vue技术栈,开发了一款功能全面.用户友好的在线求职平台. ...
- 配置Sprig security后Post请求无法使用
在学习过程中发现在配置完Spring security后,Post请求失效,无法增删改数据,这里可以通过在Spring Security 的Config类中增加 也可以自定义csrf,不过目前还不是很 ...
- 题解:P10732 [NOISG2019 Prelim] Palindromic FizzBuzz
题解:P10732 [NOISG2019 Prelim] Palindromic FizzBuzz 题意 题意十分明了,给予你一个区间,判断区间中每一个数是否是回文数. 思路 思路比较简单,首先将每一 ...
- RIME:用交叉熵 loss 大小分辨 preference 是否正确 + 内在奖励预训练 reward model
文章题目:RIME: Robust Preference-based Reinforcement Learning with Noisy Preferences,ICML 2024 Spotlight ...
- RHCA rh442 002 监控工具 脏页 块设备名 缓存
sar 看某一个时间的数据 sar -d 1 5 与iostat类似 计算机识别设备按编号识别 0-15预留出 8 为iscsi设备 做一个块设备名 名字不重要是给人看的,重要的是编号 8 17(主编 ...
- BCLinux 8.2安装配置图解教程--龙蜥社区国产移动云系统
社区镜像下载地址:https://openanolis.cn/download 安装参考地址:https://www.osyunwei.com/archives/13017.html 1安装系统 界面 ...
- Vue Hook 封装通用型表格
一.创建通用型表格的需求 实现一个通用型表格组件,具备以下功能: 动态列配置. 分页功能. 排序功能. 可扩展的行操作功能. 二.设计通用型表格组件 首先,需要设计一个基础的表格组件,它接受列配置.数 ...