下载或者安装 sip.js 到 uniapp 项目,APP 端在 menifest.json 中配置麦克风权限
menifest.json 中 app 权限配置选中:
android.permission.RECORD_AUDIO
android.permission.MODIFY_AUDIO_SETTINGS
sip.js 低版本 如 V0.13.0 版本的写法
<template>
<view class="container">
<view class="top-box">
<button type="primary" @click="handleRegister">注册</button>
<button type="primary" plain @click="handleUnRegister">取消注册</button>
</view>
<view class="top-box">
<uni-easyinput class="margr" v-model="outGoingNumber" placeholder="SIP URL"></uni-easyinput>
<button type="primary" @click="handleCall">呼叫</button>
</view>
<view class="top-box">
<audio ref="remoteAudio" id="remoteAudio"></audio>
<audio ref="localAudio" id="localAudio"></audio>
<audio ref="bell" id="bell" src="~/static/music.mp3"></audio>
</view>
<uni-popup ref="popup" type="center">
<view class="popup-box">
<view class="flex-box fs-36 fw-600">{{isInOut?inComingNumber:outGoingNumber}}</view>
<template v-if="isConnected">
<view class="flex-box">通话中...</view>
<view class="flex-box">
<uni-tag text="挂断" type="error" @click="handleCacel"></uni-tag>
</view>
</template>
<template v-else>
<view v-if="isInOut">
<view class="flex-box">呼入...</view>
<view class="flex-box">
<uni-tag text="接听" type="success" @click="handleAccept"></uni-tag>
<uni-tag text="拒绝" type="error" @click="handleTerminate"></uni-tag>
</view>
</view>
<view v-else>
<view class="flex-box">呼出...</view>
<view class="flex-box">
<uni-tag text="挂断" type="error" @click="handleTerminate"></uni-tag>
</view>
</view>
</template>
</view>
</uni-popup>
</view>
</template>
<script>
import * as sip from "@/common/js/sip-0.13.0.min.js"
let ua;
export default {
name: "VoiceIntercom",
props: {},
data() {
return {
configuration: {},
outGoingNumber: '02700002',
baseUrl: 'web.domain.com', // sipurl格式: "sip:[email protected]:7065",
port: '7065',
user: {
number: '02700001',
name: 'test',
password: '123456'
},
server: 'wss://web.domain.com:7067',
currentSession: null,
inComingNumber: null,
isRegistered: false,
isConnected: false, // 是否接通
isInOut: true, // true 被呼, false 呼出
}
},
onLoad() {
this.handleRegister()
},
beforeDestroy() {
this.handleUnRegister()
},
methods: {
// 登录
handleRegister() {
this.configuration = {
uri: `sip:${this.user.number}@${this.baseUrl}:${this.port}`,
displayName: this.user.name,
password: this.user.password,
transportOptions: {
wsServers: [this.server],
traceSip: true
},
}
ua = new sip.UA(this.configuration)
ua.on('registered', (resp) => {
this.showTishi('【' + this.user.number + '】语音登录成功')
this.isRegistered = true
})
ua.on('registrationFailed', (resp) => {
if (resp.statusCode == 503) {
this.showTishi('【' + this.user.number + '】服务不可用')
} else {
this.showTishi('【' + this.user.number + '】语音登录失败:' + resp.reasonPhrase)
}
this.isRegistered = false
console.log(resp, '语音登录失败')
})
ua.on('unregistered', (response, cause) => {
this.showTishi('【' + this.user.number + '】取消语音登录成功')
console.log(response, cause, '取消语音登录')
})
ua.on('invite', (session) => {
this.currentSession = session
this.inComingNumber = session.remoteIdentity.uri.user
this.isInOut = true
this.$refs.popup.open()
this.$nextTick(() => {
this.$refs.bell.$refs.audio.play()
this.$refs.bell.$refs.audio.currentTime = 0
})
this.sessionEvent(session)
})
ua.on('message', (message)=>{
console.log(message,'ua-message')
})
ua.start()
},
// 退出登录
handleUnRegister() {
if (ua) {
ua.unregister()
this.isRegistered = false
}
},
// session 处理
sessionEvent(session) {
session.on('rejected', () => {
console.log('inComing挂断')
})
session.on('cancel', () => {
console.log('outgoing挂断')
})
session.on('terminated', (message, cause) => {
console.log(message, cause, 'session-terminated')
if (cause == 'Rejected') {
if (message.reasonPhrase == 'Decline') {
this.showTishi('您的拨号暂时无人接听!')
} else {
this.showTishi('对方拒接了!')
}
} else if (cause == 'BYE') {
this.showTishi('对方已挂机!')
} else if (cause == 'Canceled') {
this.showTishi('对方已取消!')
}
this.$refs.bell.$refs.audio.pause()
this.$refs.bell.$refs.audio.pause()
this.$refs.popup.close()
})
session.on('accepted', (resp) => {
this.$refs.bell.$refs.audio.pause()
this.isConnected = true
console.log(resp, '接受了')
})
session.on('trackAdded', () => {
const pc = session.sessionDescriptionHandler.peerConnection
const remoteStream = new MediaStream()
pc.getReceivers().forEach((receiver) => {
if (receiver.track) {
remoteStream.addTrack(receiver.track)
this.$refs.remoteAudio.$refs.audio.srcObject = remoteStream
this.$refs.remoteAudio.$refs.audio.play()
}
})
})
session.on('bye', (resp, cause) => {
console.log(resp, cause, 'session-bye')
if ((resp && resp.method == 'BYE') || cause == 'BYE') {
this.isConnected = false
this.$refs.popup.close()
this.$refs.remoteAudio.$refs.audio.pause()
this.showTishi('【' + this.user.number + '】通话已结束!')
}
})
session.on('failed', () => {
console.log('session-failed')
})
},
// 接听
handleAccept() {
const option = {
sessionDescriptionHandlerOptions: {
constraints: {
audio: true,
video: false
}
}
}
this.currentSession.accept(option)
},
// 拒接
handleTerminate() {
this.currentSession.terminate()
this.$refs.popup.close()
this.isConnected = false
},
// 挂断
handleCacel() {
if (this.isInOut) {
this.currentSession.reject()
} else {
this.currentSession.terminate()
}
this.$refs.popup.close()
this.isConnected = false
},
// 拨打
handleCall(number) {
number = this.outGoingNumber
if (this.isRegistered) {
this.isInOut = false
const sipUrl = `sip:${number}@${this.baseUrl}:${this.port}`
this.currentSession = ua.invite(sipUrl, {
sessionDescriptionHandlerOptions: {
constraints: {
audio: true,
video: false
}
}
})
this.$refs.popup.open()
this.sessionEvent(this.currentSession)
} else {
this.showTishi('请先登录语音用户')
}
},
showTishi(title){
uni.showToast({
title: title,
icon: 'none'
})
},
}
}
</script>
<style scoped lang="scss">
.container {
font-size: 30rpx;
}
.top-box {
padding: 30rpx;
display: flex;
}
.popup-box {
background: #ffffff;
width: 80vw;
padding: 40rpx;
border-radius: 20rpx;
line-height: 80rpx;
}
.flex-box {
display: flex;
justify-content: space-around;
.uni-tag {
font-size: 30rpx;
padding: 16rpx 28rpx;
}
}
.fs-36{
font-size: 36rpx;
font-weight: bold;
}
</style>
sip.js 高版本如 V0.21.2 用法(参数同上,只列出 methods 里的部分)
<script>
import { UserAgentOptions, UserAgent, Registerer, Invitation, Inviter, Session, SessionState, InvitationAcceptOptions, InviterOptions, Messager, URI, RegistererState, } from '@/common/js/sip-0.21.2.min.js'
let userAgent, registerer;
const target = UserAgent.makeURI(`sip:${this.outGoingNumber}@${this.baseUrl}:${this.port}`);
methods: {
handleRegister() {
const uri = UserAgent.makeURI(`sip:${this.user.number}@${this.baseUrl}:${this.port}`)
if(!uri){
console.log('创建URI失败')
}
const transportOptions = {
server: this.server
}
const userAgentOptions = {
authorizationUsername: this.user.number,
authorizationPassword: this.user.password,
displayName: this.user.name,
transportOptions,
uri,
delegate: {
onInvite
}
}
userAgent = new UserAgent(userAgentOptions)
registerer = new Registerer(userAgent)
userAgent.start().then(()=>{
registerer.register({
requestDelegate: {
onReject: (resp)=>{
console.log(resp, 'onReject')
},
onAccept: (resp)=>{
console.log(resp, 'onAccept')
},
onProgress: (resp) => {
console.log(resp, 'onProgress')
},
onRedirect: (resp) => {
console.log(resp, 'onRedirect')
},
onTrying: (resp) => {
console.log(resp, 'onTrying')
},
}
}).catch((e: Error) => {
console.log(e, "Register failed")
})
}).catch((err:any)=>{
console.log(err,'start-err')
})
registerer.stateChange.addListener((newState)=>{
switch (newState) {
case RegistererState.Unregistered:
console.log('退出登录')
break;
case RegistererState.Registered :
break;
case RegistererState.Initial :
console.log('语音用户登录Initial')
break;
case RegistererState.Terminated :
console.log('语音用户登录Terminated')
break;
}
})
function onInvite(invitation){
this.currentSession = invitation
this.inComingNumber = invitation.remoteIdentity.uri.user
this.$refs.popup.open()
invitation.stateChange.addListener((state)=>{
this.sessionStateEvent(state, invitation)
})
}
}
handleAccept(){
let constrainsDefault = {
audio: true,
video: false,
}
const options = {
sessionDescriptionHandlerOptions: {
constraints: constrainsDefault
}
}
this.currentSession.accept(options)
this.isConnected = true
}
handleReject(){
this.currentSession.reject()
this.$refs.popup.close()
}
sessionStateEvent(state, session){
switch(state){
case SessionState.Initial:
console.log('SessionState.Initial')
case SessionState.Establishing:
console.log('SessionState.Establishing')
break;
case SessionState.Established:
console.log('SessionState.Established')
this.isConnected = true
this.setupRemoteMedia(session)
break;
case SessionState.Terminating:
console.log('SessionState.Terminating')
break;
case SessionState.Terminated:
console.log('SessionState.Terminated')
this.clearupMedia(session)
break;
}
}
setupRemoteMedia(session){
const remoteStream = new MediaStream()
// console.log(session.sessionDescriptionHandler, 'sessionDescriptionHandler')
session.sessionDescriptionHandler.peerConnection.getReceiver().forEach((receiver)=>{
if(receiver.track){
remoteStream.addTrack(receiver.track)
}
})
this.$refs.remoteAudio.$refs.audio.srcObject = remoteStream
this.$refs.remoteAudio.$refs.audio.play()
}
clearupMedia(session){
if(this.isCallIn){
if(session.isCanceled){
console.log('对方已挂机')
}
}else{
if(!session.isCanceled){
// console.log('对方已挂机')
}
}
this.$refs.remoteAudio.$refs.audio.srcObject = null
this.$refs.remoteAudio.$refs.audio.pause()
this.endCall()
}
handleCall(){
this.isCallIn = false
this.$refs.popup.open()
const inviterOptions = {
sessionDescriptionHandlerOptions: {
constraints: { audio: true, video: false }
}
}
if(target){
const inviter = new Inviter(userAgent, target, inviterOptions)
this.currentSession = inviter
inviter.invite({
requestDelegate: {
onReject: (resp)=>{
console.log(resp, 'inviter-onReject')
if(resp.statusCode == 500){
console.log('对方不在线')
}else{
console.log('对方拒接了')
}
},
onAccept: (resp)=>{
console.log(resp, 'inviter-onAccept')
},
onProgress: (resp) => {
console.log(resp, 'inviter-onProgress')
},
onRedirect: (resp) => {
console.log(resp, 'inviter-onRedirect')
},
onTrying: (resp) => {
console.log(resp, 'inviter-onTrying')
},
}
})
inviter.stateChange.addListener((state)=>{
this.sessionStateEvent(state, inviter)
})
}
}
handleURegister(){
if(userAgent){
this.isRegistered = false
registerer.unregister()
}
}
sendMessage(){
if(target && this.isRegistered){
const messager = new Messager(userAgent, target, '你好')
messager.message()
}
}
endCall(){
this.isConnected = false
this.$refs.popup.close()
switch(this.currentSession.state){
case SessionState.Initial:
case SessionState.Establishing:
if(this.currentSession instanceof Inviter){
// incoming session
this.currentSession.cancel()
}else{
// outgoing session
this.currentSession.reject()
}
break;
case SessionState.Established:
this.currentSession.bye()
break;
case SessionState.Terminating:
break;
case SessionState.Terminated:
console.log(SessionState,'Terminated-endCall')
break;
}
}
}
</script>