下载或者安装 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:02700001@web.domain.com: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 = falseconsole.log(resp, '语音登录失败')})ua.on('unregistered', (response, cause) => {this.showTishi('【' + this.user.number + '】取消语音登录成功')console.log(response, cause, '取消语音登录')})ua.on('invite', (session) => {this.currentSession = sessionthis.inComingNumber = session.remoteIdentity.uri.userthis.isInOut = truethis.$refs.popup.open()this.$nextTick(() => {this.$refs.bell.$refs.audio.play()this.$refs.bell.$refs.audio.currentTime = 0})this.sessionEvent(session)})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 = trueconsole.log(resp, '接受了')})session.on('trackAdded', () => {const pc = session.sessionDescriptionHandler.peerConnectionconst remoteStream = new MediaStream()pc.getReceivers().forEach((receiver) => {if (receiver.track) {remoteStream.addTrack(receiver.track)this.$refs.remoteAudio.$refs.audio.srcObject = remoteStreamthis.$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 = falsethis.$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.outGoingNumberif (this.isRegistered) {this.isInOut = falseconst 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 ua, 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){throw new Error('创建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 = invitationthis.inComingNumber = invitation.remoteIdentity.uri.userthis.dialogVisible = trueinvitation.stateChange.addListener((state)=>{sessionStateEvent(state, invitation)})}}function handleAccept(){let constrainsDefault = {audio: true,video: false,}const options = {sessionDescriptionHandlerOptions: {constraints: constrainsDefault}}this.currentSession.accept(options)this.isConnected = true}function handleReject(){this.currentSession.reject()this.dialogVisible = false}function 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')isConnected.value = truesetupRemoteMedia(session)break;case SessionState.Terminating:console.log('SessionState.Terminating')break;case SessionState.Terminated:console.log('SessionState.Terminated')clearupMedia(session)break; } }function 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 = remoteStreamthis.$refs.remoteAudio.$refs.audio.play()}function clearupMedia(session){if(isCallIn.value){if(session.isCanceled){ElMessage.warning('对方已挂机')}}else{if(!session.isCanceled){// ElMessage.warning('对方已挂机')}}this.$refs.remoteAudio.$refs.audio.srcObject = nullthis.$refs.remoteAudio.$refs.audio.pause()endCall()}function handleCall(){this.isCallIn = falsethis.dialogVisible = trueconst inviterOptions = {sessionDescriptionHandlerOptions: {constraints: { audio: true, video: false }}}if(target){const inviter = new Inviter(userAgent, target, inviterOptions)this.currentSession = inviterinviter.invite({requestDelegate: {onReject: (resp)=>{console.log(resp, 'inviter-onReject')if(resp.statusCode == 500){ElMessage.warning('对方不在线')}else{ElMessage.warning('对方拒接了')}},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)=>{sessionStateEvent(state, inviter)})} }function handleURegister(){if(userAgent){this.isRegistered = falseregisterer.unregister()}}function sendMessage(){if(target && this.isRegistered){const messager = new Messager(userAgent, target, msg.value)messager.message()}}function endCall(){this.isConnected = falsethis.dialogVisible = falseswitch(this.currentSession.state){case SessionState.Initial:case SessionState.Establishing:if(this.currentSession instanceof Inviter){// incoming sessionthis.currentSession.cancel()}else{// outgoing sessionthis.currentSession.reject()}break;case SessionState.Established:this.currentSession.bye()break;case SessionState.Terminating:break;case SessionState.Terminated:console.log(SessionState,'Terminated-endCall')break; }}
}</script>