webRtc视频通话(jquery,node)

程序

nodejs,jquery

定义

WebRTC(Web Real-Time Communication) 网页即时通信 ,是一个支持网页浏览器进行实时语音、视频对话的API。
于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准

核心API

  1. getUserMedia:可以获取本地的媒体流,一个流包含几个轨道,比如视频和音频轨道。
  2. RTCPeerConnection:用于建立 P2P 连接以及传输多媒体数据。
  3. RTCDataChannel:建立一个双向通信的数据通道,可以传递多种数据类型。

获取本地媒体流

通过 getUserMedia 函数,我们可以发起获取本地媒体流的请求:

navigator.getUserMedia(constraints, successCallback, errorCallback);

函数有三个参数,分别是约束条件,成功的回调和失败的回调。成功获取媒体流后,媒体流可以提供给本地的音频或视频元素进行播放、后期处理的 JavaScript 代理,或者发送给另一端。

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div class="page-container">
        <div class="message-box">
            <div class="message-list"></div>
            <div class="send-box">
                <textarea class="send-content"></textarea>
                <button class="sendbtn">发送</button>
            </div>
        </div>
        <div class="user-box">
            <video id="local-video" autoplay class="local-video"></video>
            <video id="remote-video" autoplay class="remote-video"></video>
            <p class="title">在线用户</p>
            <ul class="user-list"></ul>
        </div>
        <div class="mask">
            <div class="mask-content">
                <input class="myname" type="text" placeholder="输入用户名加入房间">
                <button class="add-room">加入</button>
            </div>
        </div>
        <div class="video-box">

        </div>
    </div>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/socket.io/2.1.1/socket.io.js"></script>
    <script>
        class Chat{
            constructor({ calledHandle, host, socketPath, getCallReject } = {}) {
                this.host = host;
                this.socketPath = socketPath;
                this.socket = null;
                this.calledHandle =calledHandle;
                this.getCallReject = getCallReject;
                this.peer = null
                this.localMedia = null
            }
            async init(){
                this.socket = await this.connentSocket();
                return this;
            }
            async connentSocket(){
                if(this.socket){
                    return this.socket;
                }
                return new Promise((resolve, reject) => {
                    let socket = io(this.host,{
                        path: this.socketPath
                    })
                    socket.on('connect', () => {
                        console.log('连接成功');
                        resolve(socket);
                    })
                    socket.on('connect_error', e => {
                        console.log('连接失败');
                        throw e;
                        reject()
                    })

                    /*
                    * @desc 呼叫被接受
                    * @example
                    * setRemoteDescription方法,改变与连接相关的远端描述。这个描述定义了连接的属性,例如:连接的编码方式。
                    * 连接会受到它的改变的影响,而且连接必须能同时支持新的以及旧的描述。
                    * 这个方法可以接收三个参数,一个RTCSessionDescription 对象包含设置信息,
                    * 还有两个回调函数,它们分别是方法调用成功以及失败的回调函数。
                    * */
                    socket.on('answer', ({answer}) => {
                        this.peer && this.peer.setRemoteDescription(answer);
                    })

                    /*
                    *  @desc 被呼叫
                    * */
                    socket.on('called', (callingInfo) => {
                        this.called && this.called(callingInfo);
                    })

                    /*
                    * @desc 呼叫被拒绝
                    * */
                    socket.on('callRejected', () => {
                        this.getCallReject && this.getCallReject()
                    })

                    socket.on('iceCandidate', ({ iceCandidate }) => {
                        this.peer && this.peer.addIceCandidate(new RTCIceCandidate(iceCandidate))
                    })
                })
            }

            addEvent(name, cb) {
                if (!this.socket) return
                this.socket.on(name, data => {
                    cb.call(this, data)
                })
            }

            sendMessage(name, data) {
                if (!this.socket) return
                this.socket.emit(name, data)
            }

            /*
            * @desc 获取本地媒体流
            * */
            async getLocalMedia(){
                let localMedia = await navigator.mediaDevices
                    .getUserMedia({ video: { facingMode: "user" }, audio: true })
                    .catch(e => {
                        console.log(e)
                    })
                this.localMedia = localMedia;
                return this;
            }

            /*
            * @desc 设置媒体流到video
            * */
            setMediaTo(eleId, media) {
                document.getElementById(eleId).srcObject = media;
            }

            /*
            * @dsec 被叫响应
            * */
            called(callingInfo){
                this.calledHandle && this.calledHandle(callingInfo);
            }

            /*
            * @desc 创建RTC
            * */
            createLoacalPeer(){
                var PeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.RTCPeerConnection;
                this.peer = new PeerConnection()
                return this;
            }

            /*
            * @desc 将媒体流加入通信
            * */
            addTrack(){
                if (!this.peer || !this.localMedia) return
                this.peer.addStream(this.localMedia);
                return this;
            }

            /*
            * @desc 创建 SDP offer
            * */
            async createOffer(cb) {
                if (!this.peer) return
                let offer = await this.peer.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true })
                this.peer.setLocalDescription(offer)
                cb && cb(offer)
                return this
            }
            async createAnswer(offer, cb) {
                if (!this.peer) return
                this.peer.setRemoteDescription(offer)
                let answer = await this.peer.createAnswer({ offerToReceiveAudio: true, offerToReceiveVideo: true })
                this.peer.setLocalDescription(answer)
                cb && cb(answer)
                return this

            }

            listenerAddStream(cb){
                this.peer.addEventListener('addstream', event => {
                    console.log('addstream事件触发', event.stream);
                    cb && cb(event.stream);
                })
                return this;
            }

            /*
            * @desc 监听候选加入
            * */
            listenerCandidateAdd(cb) {
                this.peer.addEventListener('icecandidate', event => {
                    let iceCandidate = event.candidate;
                    if (iceCandidate) {
                        console.log('发送candidate给远端');
                        cb && cb(iceCandidate);
                    }

                })
                return this
            }

            // 检测ice协商过程
            listenerGatheringstatechange  () {
                return this;
            }

            // 关闭RTC
            closeRTC() {
                // ....
            }
        }
    </script>
    <script>
        $(function () {
            let chat = new Chat({
                host: 'http://127.0.0.1:3003',
                socketPath:'/websocket',
                calledHandle: calledHandle,
                getCallReject: getCallReject
            })

            // 更新视图
            function updateUserList(list) {
                $(".user-list").html(list.reduce((temp, li) => {
                    temp += `<li class="user-li">${li.name}<button data-calling=${li.calling} data-id=${li.id} class=${li.id === this.socket.id || li.calling ? 'cannot-call' : 'can-call'}>通话</button></li>`
                    return temp
                }, ''))
            }

            // 更新消息li表视图
            function updateMessageList(msg) {
                $('.message-list').append(`<li class=${msg.userId === this.socket.id ? 'left' : 'right'}>${msg.user}: ${msg.content}</li>`)
            }

            // 加入房间
            $('.add-room').on('click', async () => {
                let name = $('.myname').val();
                if(!name){
                    return;
                }
                $('.mask').fadeOut();
                await chat.init();
                chat.addEvent('updateUserList', updateUserList);
                chat.addEvent('updateMessageList', updateMessageList);
                chat.sendMessage('addUser', {name})
            })

            // 发送消息
            $('.sendbtn').on('click', () => {
                let sendContent = $('.send-content').val()
                if (!sendContent) return
                $('.send-content').val('')
                chat.sendMessage('sendMessage', { content: sendContent })
            })

            // 视频
            $('.user-list').on('click', '.can-call', async function () {
                // 被叫方信息
                let calledParty = $(this).data();
                if(calledParty.calling){
                    alert('对方正在通话中');
                    return;
                }
                // 初始本地视频
                $('.local-video').fadeIn();
                await chat.getLocalMedia();
                chat.setMediaTo('local-video', chat.localMedia);
                chat.createLoacalPeer().listenerGatheringstatechange().addTrack().listenerAddStream(stream => {
                    $('.remote-video').fadeIn();
                    chat.setMediaTo('remote-video', stream);
                }).listenerCandidateAdd(iceCandidate => {
                    chat.sendMessage('iceCandidate', { iceCandidate, id: calledParty.id })
                }).createOffer(offer => {
                    chat.sendMessage('offer', { offer, ...calledParty })
                })
            })

            //呼叫被拒绝
            function getCallReject() {
                chat.closeRTC()
                $('.local-video').fadeIn()
                console.log('呼叫被拒');
            }

            // 被叫
            async function calledHandle(callingInfo) {
                if (!confirm(`是否接受${callingInfo.name}的视频通话`)) {
                    chat.sendMessage('rejectCall', callingInfo.id)
                    return
                }

                $('.local-video').fadeIn()
                await chat.getLocalMedia()
                chat.setMediaTo('local-video', chat.localMedia)

                chat.createLoacalPeer()
                    .listenerGatheringstatechange()
                    .addTrack()
                    .listenerCandidateAdd(function (iceCandidate) {

                        chat.sendMessage('iceCandidate', { iceCandidate, id: callingInfo.id })

                    })
                    .listenerAddStream(function (stream) {

                        $('.remote-video').fadeIn()
                        chat.setMediaTo('remote-video', stream)

                    })
                    .createAnswer(callingInfo.offer, function (answer) {

                        chat.sendMessage('answer', { answer, id: callingInfo.id })

                    })


            }
        })
    </script>
</body>
</html>

node端

  • server.js
const SocketIO = require('socket.io')
const socketIO = new SocketIO({
    path: '/websocket'
})

let userRoom = {
    list: [],
    add(user) {
        this.list.push(user)
        return this
    },
    del(id) {
        this.list = this.list.filter(u => u.id !== id)
        return this
    },
    sendAllUser(name, data) {
        this.list.forEach(({ id }) => {
            console.log('>>>>>', id)
            socketIO.to(id).emit(name, data)
        })
        return this
    },
    sendTo(id) {
        return (eventName, data) => {
            socketIO.to(id).emit(eventName, data)
        }
    },
    findName(id) {
        return this.list.find(u => u.id === id).name
    }
}

socketIO.on('connection', function(socket) {
    console.log('连接加入.', socket.id)

    socket.on('addUser', function(data) {
        console.log(data.name, '加入房间')
        let user = {
            id: socket.id,
            name: data.name,
            calling: false
        }
        userRoom.add(user).sendAllUser('updateUserList', userRoom.list)
    })

    socket.on('sendMessage', ({ content }) => {
        console.log('转发消息:', content)
        userRoom.sendAllUser('updateMessageList', { userId: socket.id, content, user: userRoom.findName(socket.id) })
    })

    socket.on('iceCandidate', ({ id, iceCandidate }) => {
        console.log('转发信道')
        userRoom.sendTo(id)('iceCandidate', { iceCandidate, id: socket.id })
    })

    socket.on('offer', ({id, offer}) => {
        console.log('转发offer')
        userRoom.sendTo(id)('called', { offer, id: socket.id, name: userRoom.findName(socket.id)})
    })

    socket.on('answer', ({id, answer}) => {
        console.log('接受视频');
        userRoom.sendTo(id)('answer', {answer})
    })

    socket.on('rejectCall', id => {
        console.log('转发拒接视频')
        userRoom.sendTo(id)('callRejected')
    })

    socket.on('disconnect', () => {
        // 断开删除
        console.log('连接断开', socket.id)
        userRoom.del(socket.id).sendAllUser('updateUserList', userRoom.list)
    })
})

module.exports = socketIO
  • www.js
// www.js 这就不关键了
const http = require('http')
const socketIO = require('./server.js')
const server = http.createServer()
socketIO.attach(server)
server.listen(3003, () => {
    console.log('server start on 127.0.0.1:3003')
})

启动

node www.js
火狐浏览器打开html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值