后端部分:1、简单起一个项目,引入依赖peer 2、代码
const { PeerServer } = require('peer');
const peerServer = PeerServer({
port: 9000, // 一致
path: '/myapp', // 前后端保持一致
allow_discovery:true, //设置为真可以查看所有连接的用户
ssl: {},
});
// http://localhost:9000/myapp/peer/peers 查看所有连接的用户
前端部分: 1、引入peerjs 1.3.1 2、sdk js部分
/*
* @Descripttion:
* @Author: hanb
* @Date: 2024-03-21 19:57:43
* @LastEditors: hanb
* @LastEditTime: 2024-03-21 23:15:08
*/
import Peer from 'peerjs';
// import { MessageBox, Message } from 'element-ui';
export class AudioTalk {
constructor(options) {
this.options = options // id、constraints(媒体配置对象)、callFn(任何处理结果的回调)
this._this = options._this
this.peer = null
this.conn = null
this.connectAnotherId = null
this.caller = null
this.localStream = null
this.anotherName = ''
this.waitTimer = null
this.init()
}
init() {
this.peer = new Peer(this.options.id, {
host: this.options.hostname || location.hostname,
debug: 1,
port: 9000,
path: this.options.path || '/myapp' // 后台peer服务器配置的路径与这里一致
})
// 定义创建媒体对象的option
console.log(window.navigator,window.navigator.mediaDevices,'window.navigator')
window.navigator.mediaDevices.getUserMedia(this.options.constraints || {video: false, audio: true}).then( stream => {
// 将本地媒体流存储
this.localStream = stream
}).catch(err => {
// 如果没有麦克风设备申请的话 会报错
console.log("u got an error:" + err)
});
// 监听被call事件 接收端
this.peer.on('call', (call) => {
this.waitTimer = setTimeout(() => {
// 超时未响应 拒绝
this._this.$Modal.remove()
this.conn && this.conn.close()
call && call.close()
clearTimeout(this.waitTimer)
this.waitTimer = null
}, 15000)
setTimeout(() => {
this._this.$Modal.confirm({
title: '消息提醒',
content: `<p>用户${this.anotherName || call.peer}请求通话,是否接通?</p>`,
okText: '接听',
cancelText:'拒绝',
onOk: () => {
clearTimeout(this.waitTimer)
this.waitTimer = null
call.answer(this.localStream)
call.on('stream', (stream) => {
// 接收方返回同意 2
this.options.callFn && this.options.callFn({
accept: 2,
stream
})
});
this.conn.send(JSON.stringify({
apply: 2,
user: this.options.id
}))
},
onCancel: () => {
clearTimeout(this.waitTimer)
this.waitTimer = null
this.options.callFn && this.options.callFn({
accept: 3,
stream: null
})
this.conn.send(JSON.stringify({
apply: 3,
user: this.options.id
}))
this.conn.close()
call.close()
console.log("call denied");
}
});
}, 50);
});
this.peer.on('open', function () {
console.log('ready to receive cast')
});
/**
* When a data connection between peers is open, get the connecting peer's details
* 接收端
*/
this.peer.on('connection', (connection) => {
console.log('peer connection')
this.conn = connection;
this.connectAnotherId = connection.peer;
this.conn.on('open', () => {
console.log('beconn open')
})
this.conn.on('data', (data) => {
console.log('beconn data', data,this.conn)
const receiveData = JSON.parse(data)
if (receiveData.apply === 1) {
this.anotherName = receiveData.name
// 询问
this.options.callFn && this.options.callFn({
accept: 1,
stream: null
})
}
if (receiveData.apply === 4) {
this._this.$Modal.remove()
this.conn.close()
if (this.waitTimer) {
clearTimeout(this.waitTimer)
this.waitTimer = null
}
this.options.callFn && this.options.callFn({
accept: 4,
stream: null
})
}
})
this.conn.on('close', () => {
this.options.callFn && this.options.callFn({
accept: 4,
stream: null
})
console.log('beconn close')
})
});
// 监听error报错
this.peer.on('error', err => {
this.options.callFn && this.options.callFn({
accept: 5,
stream: null,
text: '无法连接'
})
console.error(err)
});
}
// 未响应 提前挂断 申请方调用
forwardDisconnect() {
this.conn.send(JSON.stringify({
apply: 4,
id: this.options.id,
name: this.options.name
}))
}
connect(anotherId) {
this.connectAnotherId = anotherId
// 连接发起已建立 但还未获得确认的响应
this.conn = this.peer.connect(anotherId)
this.conn.on('open', () => {
console.log('conn open')
this.conn.send(JSON.stringify({
apply: 1,
id: this.options.id,
name: this.options.name
}))
})
this.conn.on("data", (data) => {
console.log("conn data", data);
const receiveData = JSON.parse(data)
if (receiveData.apply === 2) {
// 同意 2 走call
} else {
// 拒绝 3
this.options.callFn && this.options.callFn({
apply: 3,
stream: null
})
}
});
this.conn.on('close', function () {
console.log('conn close')
})
this.conn.on('error', function () {
console.log('conn error')
})
// 发起通话 但还未响应 如未监听到对方返回的流 则未建立连接
this.caller = this.peer.call(this.connectAnotherId, this.localStream);
// 设置监听
this.caller.on('stream', (stream)=> {
// 同意 2 走call
this.options.callFn && this.options.callFn({
apply: 2,
stream
})
console.log('caller stream')
});
this.caller.on('close', function () {
console.log('caller close')
})
this.caller.on('error', function () {
console.log('caller error')
});
}
disconnect() {
this.conn && this.conn.close();
this.caller && this.caller.close();
}
// 销毁
destroy() {
this.conn && this.conn.close();
this.caller && this.caller.close();
this.peer && this.peer.destroy()
}
}
3、调用 (要求写混入,要不会影响之前的代码 所以这部分也是js)
import { AudioTalk } from './audio-talk'
export default {
data() {
return {
activeName: '',
tempClickName: '',
connTimer: null
}
},
mounted() {
const user = localStorage.getItem('logindata')
const parseUser = JSON.parse(user)
console.log(parseUser, 'parseUser')
// 使用pkUser作为peer的id
this.audioTalk = new AudioTalk({ id: parseUser.pkUser,_this: this,callFn:this.handleResult,name:parseUser.userName })
},
methods: {
handleTalk(row) {
console.log(row, 'row')
if (this.tempClickName === row.pk_user && !this.activeName) {
this.audioTalk.forwardDisconnect()
setTimeout(() => {
this.connTimer && clearTimeout(this.connTimer)
// 响应中途要关闭
this.closeTalk()
}, 50);
return
}
if (this.tempClickName&&!this.activeName) {
// 当前正在拨打或正在通话
this.$toast.center('currentHeader.png', `当前正在拨打,请稍后`,null,null,3000)
return
}
if (row.pk_user === this.activeName) {
this.closeTalk()
} else if (this.activeName) {
// 当前正在通话
this.$toast.center('currentHeader.png', `当前正在通话,请先挂断`,null,null,3000)
} else {
this.audioTalk.connect(row.pk_user)
this.tempClickName = row.pk_user
this.$toast.center('currentHeader.png', `正在呼叫${row.studyroom_name}-${row.user_name}`, null, null, 3000)
// 15s没响应的话提示并挂断 如有相应则关闭
this.connTimer = setTimeout(() => {
this.closeTalk('对方没有响应')
clearTimeout(this.connTimer)
},15000)
}
},
handleResult({ accept, apply, stream }) {
this.connTimer && clearTimeout(this.connTimer)
// isAccepted 对方同意true
if (apply === 2) {
this.createAudioNode(stream)
this.activeName = this.tempClickName
this.$toast.center('success.png', '已接通')
} else if (apply === 3) {
this.$toast.center('fail.png', '对方已拒绝')
}
// 作为被接收方 同意2
if (accept === 2) {
this.createAudioNode(stream)
this.$toast.center('success.png', '已接通')
} else if (accept === 3) {
this.$toast.center('fail.png', '已拒绝')
} else if (accept === 4) {
this.deleteAudioNode()
this.closeTalk()
this.$toast.center('fail.png', '对方已挂断')
}
if (accept === 5 || apply === 5) {
this.$toast.center('fail.png', '无法连接')
}
},
closeTalk(text = '已挂断') {
this.activeName = this.tempClickName = ''
this.deleteAudioNode()
this.audioTalk.disconnect()
this.$toast.center('fail.png', text)
},
createAudioNode(stream) {
const audionode = document.createElement('audio')
audionode.id = 'current-audio'
audionode.srcObject = stream
audionode.muted = false
audionode.autoplay = true
audionode.play()
document.body.appendChild(audionode)
},
deleteAudioNode() {
const audionode = document.getElementById('current-audio')
if (audionode) {
audionode.srcObject = null
audionode.pause()
document.body.removeChild(audionode)
}
}
},
beforeDestroy () {
this.audioTalk.destroy();
},
}