下载或者安装 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 = 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.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>

APP模式下检测麦克风权限,permission.js可从插件市场直接下载,使用页面import引入。

getPermission(){
	let env = uni.getSystemInfoSync().platform
	if (env === 'android') {permission.requestAndroidPermission('android.permission.RECORD_AUDIO').then((e) => {
			if (e === -1) {
				uni.showToast({
					title: '您已经拒绝录音权限,请在应用设置中手动打开',
					icon: 'none',
				})
			} else if (e === 0) {
				uni.showToast({
					title: '您拒绝了录音授权',
					icon: 'none',
				})
			} else if (e === 1) {
				console.log('已授权')
			} else {
				uni.showToast({
					title: '授权返回值错误',
					icon: 'none',
				})
			}
		}).catch((err) => {
			uni.showToast({
				title: '拉起录音授权失败',
				icon: 'none',
			})
		})
	} else if (env === 'ios') {
		if (permission.judgeIosPermission("record")){
			console.log('已授权')
		}else{
			uni.showToast({
				title: '您拒绝了录音授权,请在应用设置中手动打开',
				icon: 'none',
			})
		}	
	}
}
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐