简述

本文是以FreeSwitch作为信令服务器,通过sipjs(基于webRtc) 进行媒体协商,网络协商后,进行P2P媒体传输。

参考知识:

效果图

HTML

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>视频通话demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="shortcut icon" th:href="@{/ico/logo.ico}" type="image/x-icon" />
<link rel="stylesheet" th:href="@{/layui/css/layui.css}">
<link rel="stylesheet" th:href="@{/css/baiban.css}">
<script th:inline="javascript">
const pub = [[${pub}]];
</script>
</head>
<body>
<div id="app">
<!--头部导航-->
<ul class="layui-nav layui-bg-blue" lay-bar="disabled">
<li class="layui-nav-item"><a target="_blank">视频通话</a></li>
</ul>
<!--音视频通话-->
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
<legend>音视频通话</legend>
</fieldset>
<div class="layui-row" style="border: 1px solid #f0f0f0;margin-left: 10px;margin-right: 10px;padding-top: 10px">
<div class="layui-col-xs12 layui-col-md4" >
<form class="layui-form">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">我的号码:</label>
<div class="layui-input-inline">
<input id="myNumber" value="1000" autocomplete="off" class="layui-input">
</div>
</div>
</div> <div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">信令地址:</label>
<div class="layui-input-inline">
<input id="sipAddr" th:value="${fs}" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Websocket端口:</label>
<div class="layui-input-block">
<input type="radio" name="wsUrl" value="5066" title="http" checked>
<input type="radio" name="wsUrl" value="7443" title="https">
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">拨打号码</label>
<div class="layui-input-inline">
<input id="sip_phone_number" value="1001" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-form-item" style="text-align: center">
<div class="layui-inline">
<button type="button" class="layui-btn layui-btn-primary" onclick="softPhone.start()"
id="register">注册
</button>
<button type="button" class="layui-btn layui-btn-normal operate layui-btn-disabled" disabled
onclick="PHONE.call()" id="call">拨打
</button>
<button type="button" class="layui-btn layui-btn-normal operate layui-btn-disabled" disabled
onclick="PHONE.hangUp()" id="hangup">挂断
</button>
</div>
</form>
</div>
<!--视频展示区-->
<div class="layui-col-xs12 layui-col-md4" style="border:5px solid #3385FF;width: 615px;height: 600px">
<div id="playVideo">
<video id="youVideo" style="padding-right:10px" width="600px" height="600px" muted autoplay
onmousedown="move(this)" th:poster="@{/img/webrtc.png}"
playsinline></video>
</div>
<div style="position:relative;top:-168px;left:1px">
<video id="meVideo" width="100px" height="100px" th:poster="@{/img/webrtc.png}" autoplay playsinline></video>
</div>
</div>
</div>
</div>
<canvas id="capture" style="display: none"></canvas>
<script th:src="@{/javascript/jquery-2.1.4.js}" type="application/javascript"></script>
<script th:src="@{/javascript/sip-0.15.10.js}" type="application/javascript"></script>
<script th:src="@{/layui/layui.js}" type="application/javascript"></script>
<!--操作-->
<script th:src="@{/javascript/sip-pc/operation.js}" type="application/javascript"></script>

javaScript

  let table = null;
let form = null;
let layer = null;
let laydate = null;
let upload = null;
// 拨打电话确认框
let confirmIndex = null;
layui.use(['table', 'form', 'layer', 'laydate', 'upload'], function () {
$(function () {
table = layui.table;
form = layui.form;
layer = layui.layer;
upload = layui.upload;
if (window.location.protocol === 'http:') {
$('input[name="wsUrl"][value="5066"]').attr("checked", true);
} else {
$('input[name="wsUrl"][value="7443"]').attr("checked", true);
}
});
}); // 本地视频
var localVideo = document.getElementById('meVideo');
// 远端视频
var remoteVideo = document.getElementById('youVideo'); var softPhoneUA = null;
var currentSession = null; /**
* 注册模块
* @type {{logout(): void, start(): void, unregister(): void, UAEvent(*): void, register(): void, sessionEvent(*): void}}
*/
const softPhone = {
/**
* 登陆软电话
*/
start() {
// 获取我的号码
var myNumber = $.trim($('#myNumber').val());
// 获取sip地址
var sipAddr = $.trim($('#sipAddr').val());
// sip url 拼接
var sip_uri = myNumber + '@' + sipAddr
// 信令密码
var sip_password = $.trim($('#sipPassword').val());
// 获取WS连接端口
var wsPort = $.trim($('input[name="wsUrl"]:checked').val());
// var wsPort = "5060";
var ws_uri = wsPort == '5066' ? 'ws://' + sipAddr + ':' + wsPort : 'wss://' + sipAddr + ':' + wsPort var config = {
uri: sip_uri,
transportOptions: {
wsServers: [ws_uri]
},
// 授权号
authorizationUser: myNumber,
// 登陆密码
password: '1234',
displayName: myNumber,
register: true
};
//v 就绪软电话、监听软电话连接状态、监听电话呼入、拨打电话、登出软电话系统
softPhoneUA = new SIP.UA(config);
softPhone.UAEvent(softPhoneUA);
// 有电话呼入
softPhoneUA.on('invite', function (session) {
currentSession = session;
softPhone.sessionEvent(session);
layer.confirm('有电话呼入 ... 请注意是否接听)', {
btn: ['取消', '接听', '拒绝'],
btn1: function () {
layer.close(index);
},
btn2: function () {
PHONE.answer();
},
btn3: function () {
softPhone.hangUp();
}
});
})
}, /** 就绪 */
register() {
softPhoneUA.register({ // 注册
register: true
});
}, /**
* 绑定ua事件
* @param {*} ua
*/
UAEvent(ua) {
// 开始尝试连接
ua.on('connecting', function (args) {
console.log('%c connecting - 开始尝试连接', 'color: #f00');
});
// 连接完毕
ua.on('connected', function () {
console.log('%c connected - 连接完毕', 'color: #f00');
});
// 主动取消注册或注册后定期重新注册失败
ua.on('unregistered', function (response, cause) {
$('#register').removeClass("layui-btn-disabled").removeAttr('disabled');
console.log('%c unregistered - 主动取消注册或注册后定期重新注册失败', 'color: #f00');
});
// 注册成功
ua.on('registered', function () {
layer.msg("注册成功", {icon: 1, time: 1500});
console.log('%c registered -- 注册成功', 'color: #f00');
btnHide(['register','shard'])
btnShow(['call'])
})
// websocket 连接失败
ua.on('disconnected', function () {
console.log('%c disconnected - 连接失败', 'color: #f00');
})
}, /**
* 绑定session事件
* @param {} session
*/
sessionEvent(session) {
session.on("rejected", function (response, cause) {
layer.close(confirmIndex);
});
session.on("bye", function (response, cause) {
// 不可用
btnHide(['hangup','mute','unmute','openVideo','closeVideo','shard'])
localVideo.srcObject = null;
remoteVideo.srcObject = null;
});
// 会话被接入
session.on("accepted", function (response, cause) {
layer.close(confirmIndex);
btnShow(['hangup','mute','unmute','openVideo','closeVideo','shard','capturePic'])
var pc = session.sessionDescriptionHandler.peerConnection;
var remoteStream = new MediaStream();
pc.getReceivers().forEach(function (receiver) {
remoteStream.addTrack(receiver.track);
});
remoteVideo.srcObject = remoteStream;
if (pc.getSenders()) {
var localStream = new MediaStream();
pc.getSenders().forEach(function (sender) {
localStream.addTrack(sender.track);
});
localVideo.srcObject = localStream;
}
});
session.on("cancel", function (response, cause) {
layer.close(confirmIndex);
});
}
}

operation.js

/**
* 拨打、接听、挂断 模块
* @type {{call(): void, answer(): void, hangUp(): void}}
*/
const PHONE = {
/**
* 拨打
*/
call() {
const telNumber = $.trim($('#sip_phone_number').val());
var sipAddr = $.trim($('#sipAddr').val());
const inviteUrl = telNumber + '@' + sipAddr
currentSession = softPhoneUA.invite(inviteUrl, {
sessionDescriptionHandlerOptions: {
constraints: {
audio: {
autoGainControl: true,
// 噪音消除
noiseSuppression: true,
// 设置降噪
echoCancellation: true
},
video: true
},
alwaysAcquireMediaFirst: true // 此参数是sip.js官方修复在firefox遇到的bug所设置
})
confirmIndex = layer.confirm('呼叫中....', {
btn: ['取消'],
btn1: function (index) {
currentSession.cancel();
layer.close(index);
}
});
// 拨打后 监听
currentSession.on("rejected", function (response, cause) {
layer.msg("请求通话被拒绝", {icon: 1, time: 1500});
console.log(response)
console.log(cause)
});
// 本次通话结束
currentSession.on("bye", function (response, cause) {
layer.msg("本次通话结束", {icon: 1, time: 1500});
localVideo.srcObject = null;
remoteVideo.srcObject = null;
});
// 对方接听
currentSession.on("accepted", function (response, cause) {
layer.msg("对方接听", {icon: 1, time: 1500});
$('#call').addClass('layui-btn-disabled').attr('disabled', 'disabled');
$('#hangup').removeClass('layui-btn-disabled').removeAttr('disabled');
$('#mute').removeClass('layui-btn-disabled').removeAttr('disabled');
$('#unmute').removeClass('layui-btn-disabled').removeAttr('disabled');
var pc = currentSession.sessionDescriptionHandler.peerConnection;
var remoteStream = new MediaStream();
pc.getReceivers().forEach(function (receiver) {
remoteStream.addTrack(receiver.track);
});
remoteVideo.srcObject = remoteStream;
if (pc.getSenders()) {
var localStream = new MediaStream();
pc.getSenders().forEach(function (sender) {
localStream.addTrack(sender.track);
});
localVideo.srcObject = localStream;
}
});
// 取消通话
currentSession.on("cancel", function (response, cause) {
layer.msg("取消通话", {icon: 1, time: 1500});
});
},
/**
* 挂断
*/
hangUp() {
if (currentSession instanceof Object) {
if (currentSession.hasAnswer) {
currentSession.bye();
} else if (currentSession.isCanceled === false) {
currentSession.cancel();
} else {
currentSession.reject();
}
}
},
/**
* 接听
*/
answer() {
var option = {
sessionDescriptionHandlerOptions: {
constraints: {
audio: {
autoGainControl: true,
// 噪音消除
noiseSuppression: true,
// 设置降噪
echoCancellation: true
},
video: true
},
alwaysAcquireMediaFirst: true, // 此参数是sip.js官方修复在firefox遇到的bug所设置
rtcConfiguration: {
iceServers: [
{
url: "stun:124.222.83.153:3478",
username: "test",//可选
credential: "test123"//可选
},
{
url: "turn:124.222.83.153:3478",
"username": "test",//可选
"credential": "test123"//可选
}
]
}
}
}
currentSession.accept(option)
}
}

拨打

接听

通话中

数据流程图

Web网页音视频通话之基于Sipjs的更多相关文章

  1. 基于 Web SDK 实现视频通话场景 | 声网 SDK 教程

    声网视频 SDK 被广泛应用于多种实时互动场景中,例如视频会议.视频通话.音视频社交.在线教育等.为了让刚刚接触声网 SDK 的开发者,可以更顺畅地实现基础的视频通话功能,我们基于声网 Web SDK ...

  2. iOS下WebRTC音视频通话(一)

    在iOS下做IM功能时,难免都会涉及到音频通话和视频通话.QQ中的QQ电话和视频通话效果就非常好,但是如果你没有非常深厚的技术,也没有那么大的团队,很难做到QQ那么快速和稳定的通话效果. 但是利用We ...

  3. 一、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信的IM聊天APP,支持各类消息收发,音视频通话,附vue实现源码(已开源)-项目引言

    项目文章索引 1.项目引言 2.腾讯云后台配置TXIM 3.配置项目并实现IM登录 4.会话好友列表的实现 5.聊天输入框的实现 6.聊天界面容器的实现 7.聊天消息项的实现 8.聊天输入框扩展面板的 ...

  4. web网页中使用vlc插件播放相机rtsp流视频

    可参考: 使用vlc播放器做rtsp服务器 使用vlc播放器播放rtsp视频 使用vlc进行二次开发做自己的播放器 vlc功能还是很强大的,有很多的现成的二次开发接口,不需配置太多即可轻松做客户端播放 ...

  5. iOS下WebRTC音视频通话(二)-局域网内音视频通话

    这里是iOS 下WebRTC音视频通话开发的第二篇,在这一篇会利用一个局域网内音视频通话的例子介绍WebRTC中常用的API. 如果你下载并编译完成之后,会看到一个iOS 版的WebRTC Demo. ...

  6. web网页练习

    一. HTML部分 1. XHTML和HTML有什么区别 HTML是一种基本的WEB网页设计语言,XHTML是一个基于XML的置标语言最主要的不同: XHTML 元素必须被正确地嵌套. XHTML 元 ...

  7. TI IPNC Web网页之网页修改教程

    web网页程序修改 打开gStudio之后,点击菜单栏中Help->Contents.先把这个诡异的编程语言看一遍吧.这里搬一些东西出来. GoDB简介 从第一副图片中,我们可以看出,从源文件到 ...

  8. TI IPNC Web网页之GoDB开发环境

    介绍 下面介绍DM8127/DM385 IPNC RDK中网页制作相关的东东. 具体来说,各位获得这个RDK包时有以下文件: IPNC_RDK_DM812x_DM385_Version3.5.0.ta ...

  9. WinForm嵌入Web网页的解决方案

    企业级信息化系统绝大部分采用BS架构实现,如门户网站.OA系统.电商网站等,通过浏览器输入Web网址即可访问,对于使用者来说非常便捷,对于开发维护者来说也非常方便,程序维护只需更新服务器即可,使用者无 ...

  10. C#开发BIMFACE系列49 Web网页中加载模型与图纸的技术方案

    BIMFACE二次开发系列目录     [已更新最新开发文章,点击查看详细] 在BIMFACE二次系列博客中详细介绍了服务器端API的调用方式,如下列表 C#开发BIMFACE系列1   BIMFAC ...

随机推荐

  1. token验证

    token验证 什么是token?我相信很多开发者都或多或少听过基于 token 的用户鉴权和基于 session 的用户鉴权,而今天说的 token 验证就是第一种了.token 的意思是" ...

  2. Hooks与事件绑定

    Hooks与事件绑定 在React中,我们经常需要为组件添加事件处理函数,例如处理表单提交.处理点击事件等.通常情况下,我们需要在类组件中使用this关键字来绑定事件处理函数的上下文,以便在函数中使用 ...

  3. API 网关日志的价值,你了解多少?

    本文介绍了 API 网关日志的价值,并以知名网关 Apache APISIX 为例,展示如何集成 API 网关日志. 作者钱勇,API7.ai 技术工程师,Apache APISIX Committe ...

  4. 微服务为什么要用到 API 网关?

    本文介绍了 API 网关日志的价值,并以知名网关 Apache APISIX 为例,展示如何集成 API 网关日志. 作者程小兰,API7.ai 技术工程师,Apache APISIX Contrib ...

  5. PYTHON数据分析——python基础

    利用命令行创建python文件 C:\Users\Your Name>python myfile.py Python 变量命名规则: 变量名必须以字母或下划线字符开头 变量名称不能以数字开头 变 ...

  6. HDCTF_2023

    pwnner 附件 有后门函数,seed是一个固定值, //伪随机数 #include <stdio.h> #include <stdlib.h> int main() { i ...

  7. \n 和 std::endl 的区别

    std::cout << std::endl; 等价于 std::cout << '\n' << std::flush; 除了写入换行符,std::endl 还会刷 ...

  8. ES6模块化之import的使用方式

    1.引入外部文件: <script src='文件路径' type='module'><!--PS:这个type="module" 必须要写,否则浏览器会报错-- ...

  9. 理解 React 中的 useEffect、useMemo 与 useCallback

    useEffect 先理解 useEffect 有助于学习 useMemo 和 useCallback.因为 useMemo 和 useCallback 的实现实际上都是基于 useEffect 的. ...

  10. 【CSS】画出宽度为1像素的线或边框

    由于多倍的设计图在移动设备上显示时会将设计图进行缩小到视口宽度,而1px的边框没有随着页面进行缩小而导致效果太粗,想要还原设计图1px的显示效果,因此需要一些方法来实现边框宽度小于1px. 实现方法很 ...