流媒体协议之 WebRTC 实现 p2p 视频通话(二),kotlin 数组转集合
socket.on('called', callingInfo => { this.called && this.called(callingInfo) }) // 呼叫被拒 socket.on('callRejected', () => { this.getCallReject && this.getCallReject() }) socket.on('iceCandidate', ({ iceCandidate }) => { console.log('远端添加 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) } // 获取本地媒体流 async getLocalMedia() { let localMedia = await navigator.mediaDevices .getUserMedia({ video: { facingMode: "user" }, audio: true }) .catch(e => { console.log(e) }) this.localMedia = localMedia return this } // 设置媒体流到 video setMediaTo(eleId, media) { document.getElementById(eleId).srcObject = media } // 被叫响应 called(callingInfo) { this.calledHandle && this.calledHandle(callingInfo) } // 创建 RTC createLoacalPeer() { this.peer = new RTCPeerConnection() return this } // 将媒体流加入通信 addTrack() { if (!this.peer || !this.localMedia) return //this.localMedia.getTracks().forEach(track => this.peer.addTrack(track, this.localMedia)); this.peer.addStream(this.localMedia) return this } // 创建 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 } // 监听候选加入 listenerCandidateAdd(cb) { this.peer.addEventListener('icecandidate', event => { let iceCandidate = event.candidate; if (iceCandidate) { console.log('发送 candidate 给远端'); cb && cb(iceCandidate); } }) return this } // 检测 ice 协商过程 listenerGatheringstatechange () { this.peer.addEventListener('icegatheringstatechange', e => { console.log('ice 协商中: ', e.target.iceGatheringState); }) 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) return console.log('对方正在通话'); // 初始本地视频 $('.local-video').fadeIn() await chat.getLocalMedia() chat.setMediaTo('local-video', chat.localMedia) chat.createLoacalPeer() .listenerGatheringstatechange() .addTrack() .listenerAddStream(function (stream) { $('.remote-video').fadeIn() chat.setMediaTo('remote-video', stream) }) .listenerCandidateAdd(function (iceCandidate) { chat.sendMessage('iceCandidate', { iceCandidate, id: calledParty.id }) }) .createOffer(function (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> 后端 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('> ``` 《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》 浏览器打开:qq.cn.hn/FTe 免费领取 ``` >>>>', id) socketIO.to(id).emit(name, data) }) return this }, sendTo(id) { return (eventName, data) => { socketIO.to(id).emit(eventName, data) } **2.后端** 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 这就不关键了 const http = require('http') const app = require('../app') const socketIO = require('../socket.js') const server = http.createServer(app.callback()) socketIO.attach(server) server.listen(3003, () => { console.log('server start on 127.0.0.1:3003') }) ### 搭建 STUN/TURN > 因为没有钱买服务器 没试过 [coturn]( )?据说使用它搭建 STUN/TURN 服务非常的方便 # 编译 cd coturn ./configure --prefix=/usr/local/coturn sudo make -j 4 && make install
评论