Web网页音视频通话之基于Sipjs

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

参考知识:

  • sip.js https://sipjs.com/
  • webRtc开发手册 https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API

效果图

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)
    }
}
拨打

接听

通话中

数据流程图

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
### 回答1: 基于SIP(会话发起协议)和WebRTC(Web实时通信)的音视频通话是一种先进的通信技术。 SIP是一种信令协议,用于建立、修改和终止多媒体通讯会话。它提供了一种灵活的方式,可以实现语音、视频、即时消息等多种通信媒体的传输。SIP基于IP网络,可以在各种网络环境下使用。 WebRTC是一种开放的实时通信技术,可以在网页浏览器中直接使用,无需安装插件或其他软件。它提供了实时音频和视频通信的能力,并支持数据传输和文件共享。WebRTC通过使用JavaScript API和RTCPeerConnection建立点对点连接,实现了浏览器之间的直接通信。 基于SIPWebRTC的音视频通话结合了SIP的信令和WebRTC的音视频传输能力。当两个或多个终端需要进行音视频通话时,首先使用SIP建立会话连接,并交换IP地址和端口信息。然后,使用WebRTC建立点对点的音视频传输通道,进行音频和视频数据的传输和实时编解码。 基于SIPWebRTC的音视频通话具有很多优点。首先,它可以在各种终端设备上使用,包括计算机、手机和平板电脑。其次,它可以在不同的网络环境下使用,包括有线网络和无线网络。此外,它提供了高质量的音视频传输,具有低延迟和稳定性。 总的来说,基于SIPWebRTC的音视频通话是一种先进的通信技术,能够实现高质量、实时的音视频通信。它在各种应用场景中都有广泛的应用,包括在线教育、视频会议、远程医疗等。 ### 回答2: 基于SIP(会话初始协议)和WebRTC(网络实时通信)的音视频通话是一种基于互联网的实时通信技术,可以在不同设备和平台之间进行高质量的音频和视频通话SIP是一种通信协议,用于建立、修改、终止多媒体会话,如音视频通话。它可以在IP网络上传输标准化的语音、视频和其他媒体数据。SIP使用URI(统一资源标识符)作为用户标识,并通过SIP服务器进行信令交换和媒体协商。 WebRTC是一组技术,允许网页和移动应用在不需要任何插件或额外软件的情况下,通过浏览器直接进行音视频通信。WebRTC使用了一些开放标准,如实时传输协议(RTP)和实时传输控制协议(RTCP)来传输媒体数据。 基于SIPWebRTC的音视频通话有以下优点: 1. 跨平台支持:由于WebRTC是基于Web技术,可以在多种设备和平台上运行,包括PC、Mac、移动设备等。 2. 实时性强:音视频通话可以实时进行,避免了延迟和高延迟对通信的影响。 3. 便捷性:使用SIPWebRTC进行音视频通话不需要额外的软件和插件,用户只需要拥有一个支持WebRTC的浏览器。 4. 高质量:由于SIPWebRTC使用了先进的编解码算法和传输协议,音视频通话可以达到高质量的传输效果。 5. 安全性:SIPWebRTC提供了一些安全机制,如加密传输和身份验证,保护音视频通话的隐私和安全性。 综上所述,基于SIPWebRTC的音视频通话是一种灵活、跨平台、实时性强、高质量和安全的通信方式,为用户提供了更便捷和高效的交流体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值