官网:jssip.net/
一、安装和配置
最近接手的vue项目需要使用jssip通话,jssip官网是全英文,所有看起来比较费劲。完成项目后记录一下jssip。 主要记录了jssip的安装、配置以及一些状态信息传递,包括二次拨号函数、来电铃声开关函数、通话添加媒体流函数、接听挂断函数、初始化函数等
-
安装
npm install jssip
-
jssip文件
在src/utils下创建jssip.js文件
import JsSIP from 'jssip'
import store from '@/store'
let ua = null
let currentSession = ''
let peer = null
let stream = null
// 启用信令相关日志
JsSIP.debug.enable('JsSIP:RTCSession:*');
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia || navigator.msGetUserMedia;
// 初始化配置
const initConfig = (data) => {
const socket = new JsSIP.WebSocketInterface(process.env.VUE_APP_CALL_WS_URL)
socket.via_transport = "ws"; // 制定实际传输协议为ws
const configuration = {
sockets: [socket],
uri: `sip:${data.username}@${process.env.VUE_APP_JSSIP_URL}`, // 分机号注册 格式 sip: + 分机号码 + @ + FS注册地址
authorization_user: data.username, // 授权用户
password: data.password, // 密码
register: true, // 自动注册
register_expires: 1800, // 注册有效期
connection_recovery_max_interval: 60, // 链接恢复的最大间隔
connection_recovery_min_interval: 5 // 链接恢复的最小间隔
}
return configuration
};
// 初始化jssip
const jssipInit = (data) => {
JsSIP.C.SESSION_EXPIRES = 120 // FreeSWITCH 默认设置要求 Session Expires 不低于120 低于120打不出电话
JsSIP.C.MIN_SESSION_EXPIRES = 120
const configuration = initConfig(data)
ua = new JsSIP.UA(configuration)
addRegisterMethod(ua)
ua.start()
return ua
}
// jssip监听方法
const addRegisterMethod = (ua) => {
// 接线中
ua.on('connecting', (res) => {
console.log('接线中', res)
})
// 连线中
ua.on('connected', (res) => {
console.log('连线中', res)
})
// 取消连线
ua.on('disconnected', (res) => {
console.log('取消连线', res)
})
// 注册成功
ua.on('registered', (res) => {
console.log('注册成功', res)
store.commit('setSessionData', { state: 'registered' });
})
// 注册失败
ua.on('registrationFailed', (res) => {
console.log('注册失败', res)
})
// 注册即将失效(重新注册)
ua.on('registrationExpiring', (res) => {
console.log('重新注册', res)
})
// 注销回调
ua.on('unregistered', (res) => {
console.log('解除注册', res)
store.commit('setSessionData', { state: 'unregistered' });
})
// 会话邀请
ua.on('invite', (res) => {
console.log('会话邀请', res.remote_identity)
})
// 会话
ua.on('newRTCSession', (data) => {
const { session, request, originator } = data
currentSession = session
// 呼叫中 打电话与接电话的呼叫都走这里
currentSession.on('progress', (res) => {
console.log('process事件', originator)
if (originator === 'remote') {
console.log('电话过来拉~~~~~~~~~··', data)
// console.log('我接听了', res)
store.commit('setSessionData', { state:'progress-remote', data: request });
// todo playRingMedia() // 这里可以加一个判断,如果是监听的时候不需要播放音乐
} else {
console.log('接听事件在progress中触发', res)
store.commit('setSessionData', { state:'progress' });
streamingMediaPlayer() // 添加媒体流 这时候开启音频
}
})
// 对等连接事件触发
currentSession.on('peerconnection', (data) => {
console.log('对等连接事件触发,peerconnection', data)
store.commit('setSessionData', { state:'peerconnection' });
})
// 交换sdp信令事件触发
currentSession.on('sdp', (res) => {
console.log('交换sdp信令事件触发,sdp', res)
store.commit('setSessionData', { state:'sdp' });
})
// 对等连接建立(通话连线时候触发)
currentSession.on('connecting', (data) => {
peer = currentSession._connection
console.log('对等连接建立,connecting', peer, data)
store.commit('setSessionData', { state:'connecting' });
})
// 通话接受时候触发
currentSession.on('accepted', (res) => {
console.log('通话接受时候触发', res)
store.commit('setSessionData', { state:'accepted' });
})
// 通话失败事件触发
currentSession.on('failed', (res) => {
console.log('通话失败事件触发--- failed', res)
stopPlayRingMedia() // 关闭来电振铃
store.commit('setSessionData', { state:'failed' });
})
currentSession.on('reinvite', (res) => {
// openLocalCamera()
console.log('重新协商事件触发', res)
if (currentSession._connection.getLocalStreams().length > 0) {
// 接听后,判断localStream
console.log('localStream存在');
}
if (currentSession._connection.getRemoteStreams().length > 0) {
console.log('remoteStream存在');
}
})
// 呼叫确认
currentSession.on('confirmed', (res) => {
console.log('呼叫确认--设置媒体流到音视频中,confirmed', res)
store.commit('setSessionData', { state:'confirmed' });
if (originator === 'remote') {
streamingMediaPlayer() // 呼入时没有添加媒体流,这时候添加媒体流
}
})
// 通话结束
currentSession.on('ended', (res) => {
console.log('通话结束--- ended', res)
store.commit('setSessionData', { state:'ended' });
})
})
}
// 呼叫
const call = (data) => {
// 这一步也是添加监听,不过已经使用在addRegisterMethod方法中添加了这里为空就行
const eventHandlers = {
// 呼叫中
progress: function (e) {
console.log('call is in progress')
},
// 失败
failed: function (e) {
console.log('call failed: ', e)
},
// 结束
ended: function (e) {
console.log('call ended : ', e)
},
// 呼叫确认
confirmed: function (e) {
console.log('call confirmed')
}
}
const options = {
eventHandlers,
mediaConstraints: {
audio: true,
video: false
}
}
ua.call(`sip:${data.phone}@${data.url}`, options)
}
// 接听
const answer = () => {
const options = {
mediaConstraints: { audio: true, video: false, mandatory: { maxWidth: 640, maxHeight: 360 } }
}
currentSession.answer(options) // 接听来电
console.log('电话接听')
}
// 挂断
const hangUp = () => {
stopPlayRingMedia() // 关闭放来电振铃
ua.terminateSessions()
}
/**
* 二次拨号
* @param number 二次拨号号码
* 注意功能是打通电话后,在按键时对方可接收到
*/
const dtmf = (number) => {
console.log(number)
const options = {
transportType: 'RFC2833' // dtmf类型,默认是info
}
currentSession.sendDTMF(number, options)
}
// 添加媒体流播放
const streamingMediaPlayer = () => {
stopstreamingMediaPlayer() // 先关闭媒体流
stream = new MediaStream()
const receivers = currentSession.connection && currentSession.connection.getReceivers()
if (receivers) {
receivers.forEach((receiver) => stream.addTrack(receiver.track))
}
store.commit('setCurrentSession', currentSession);
// 这里创建一个audio标签隐藏,添加媒体流,添加到界面id为ringMediaAudioId的标签中
let ringAudio = document.getElementById('ringMediaAudioId')
ringAudio = document.createElement('audio')
ringAudio.id = 'ringMediaAudioId'
// ringAudio.hidden = true
ringAudio.srcObject = stream // 添加媒体流
const box = document.getElementById('ringMediaAudioBox')
box.appendChild(ringAudio) // 添加到box中
ringAudio.play() // 播放
}
// 关闭媒体流播放
const stopstreamingMediaPlayer = () => {
const ringAudio = document.getElementById('ringMediaAudioId')
if (ringAudio) {
const box = document.getElementById('ringMediaAudioBox')
box.removeChild(ringAudio)
}
}
// 添加来电振铃播放
const playRingMedia = () => {
stopPlayRingMedia()
const ringAudio = document.getElementById('cheerMusic')
ringAudio && ringAudio.play()
}
// 关闭来电振铃播放
const stopPlayRingMedia = () => {
const cheerMusic = document.getElementById('cheerMusic')
cheerMusic && cheerMusic.load()
}
const sip = {
jssipInit: jssipInit, // 初始化
call: call, // 拨号
answer: answer, // 接听
hangUp: hangUp, // 挂断
dtmf: dtmf // 二次拨号
}
export default sip
二、使用(拨号)
jssip用到的主要是链接sip、断开链接sip、拨打电话、挂断电话、来电监听、来电接听、挂断来电、二次拨号以及电话监听挂和断监听
<script>
import jssip from '@/utils/jssipInit'
export default {
data() {
return {
phone: null, // 电话号
JsSipURL: 'www.abc.com', // JsSipURL 这里是域名,当然本地的话加端口号即可
password: '123', // JsSipPassword 这个密码要与后端约定好
username: '', // 分机号
phoneState: '', // 用来控制界面拨打的状态
incomingCall: false // 来电状态 来电弹窗状态
}
},
created() {
// 监听sip状态()
this.$bus.$on('sessionData', (data) => { // 监听sip
switch (data.state) {
case 'unregistered': // 注销
this.$message.warning('sip已注销,签出成功')
break
case 'registered': // 注册成功
this.$message.success('sip注册成功,签入成功')
break
case 'ended': // 通话结束
// 这里写挂断后逻辑
break
case 'progress': // 呼叫中
this.phoneState = 2
break
case 'confirmed': // 呼叫确认
this.phoneState = 1
break
case 'failed': // 通话失败
this.incomingCall = false
this.phoneState = 0
break
case 'progress-remote': // 来电中
this.incomingCall = true
// 来电后逻辑
this.phoneState = 5
//响铃逻辑:<audio src="../../../assets/kanong.mp3" id="cheerMusic" loop class="hidden-audio"></audio>
const ring = document.getElementById('cheerMusic');
ring && ring.play();
}
break
default:
break
}
})
window.addEventListener('beforeunload', this.onBeforeunload)// 关闭窗口调用事件
},
mounted() {
this.initLink()
},
methods: {
// 初始化链接
initLink() {
// username分机号 password密码 url域名 port端口
jssip.jssipInit({
username: this.username,
password: this.password,
url: this.JsSipURL,
port: '5072'
})
},
// 断开sip
unregistered() {
jssip.unregister()
},
onBeforeunload(e) {
jssip.unregister() // 断开jssip
},
// 二次拨号号码
onDTMFFun(dtmfNumber) {
jssip.dtmf(dtmfNumber)
},
// 呼叫
onClickPhone(phone) {
this.phone = phone
const len = this.phone.length
if (this.phone.length === 11) {
jssip.call({ phone: this.phone, url: this.JsSipURL })
layoutAPI.callStatus()
} else {
if (this.phone.length=== 0) {
this.$message.error('请输入号码!')
} else {
this.$message.error('您输入号码不对,请输入正确的号码!')
}
}
},
// 点击挂断
onClickHangUp() {
this.phoneState = 0
jssip.hangUp()
},
// 点击接听来电
onAnswer() {
// 接听来电逻辑
jssip.answer()
this.incomingCall = false
},
// 点击挂断来电
onHangUp() {
// 挂断逻辑
jssip.hangUp()
this.incomingCall = false
},
// 监听
onListen() {
// 监听逻辑
jssip.answer()
},
// 关闭监听
listenInClose() {
// 关闭监听逻辑
jssip.hangUp()
},
}
}
</script>
-
麦克风静音
// 麦克风静音
handleCallMute(isMute) {
// getSenders():这是 WebRTC 中的一个方法,用于获取当前连接的所有发送者(senders)。每个发送者代表一个音频或视频流的发送通道。
// 返回的 audioSenders 是一个数组,包含所有发送者(即所有音视频流的发送对象)。
const audioSenders = this.currentSession.connection && this.currentSession.connection.getSenders();
//sender.track.kind 代表发送者的流类型,kind 可以是 'audio' 或 'video'。audio即找到音频流的发送者。
//sender.track 是发送者的媒体轨道(audioTrack 或 videoTrack),它代表实际的音视频流。
const audioTrack = audioSenders.find(sender => sender.track.kind === 'audio').track;
if (audioTrack) {
//audioTrack.enabled 用于控制音频是否被启用。enabled 为true 表示音频轨道正常工作,false 表示音频被禁用(即静音)
audioTrack.enabled = !isMute;
}
},
2.2 通话保持
<!-- 通话保持-间奏 -->
<audio src="../../../assets/bridge.mp3" id="bridgeMusic" loop class="hidden-audio"></audio>
// 保持通话
handleCallHold(isKeep) {
const bridge = document.getElementById('bridgeMusic');
if (isKeep) {
this.handleCallMute(true);
bridge.play();
} else {
this.handleCallMute(false);
bridge.load();
}
}