uniapp Vue 使用 sip.js进行语音通话视频通话

news/2024/11/17 4:27:12/

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

http://www.ppmy.cn/news/1032985.html

相关文章

HTML和JavaScript实现一个简单的计算器

使用HTML和JavaScript实现一个简单的计算器。 一、绘制键盘 <!DOCTYPE html> <html> <head><title>Simple Calculator</title><style>.calculator {display: grid;grid-template-columns: repeat(4, 1fr);grid-gap: 5px;padding: 10px;}.…

day-21 代码随想录算法训练营(19)二叉树part07

530.二叉搜索树的最小绝对差 思路一&#xff1a;二叉搜索树的中序遍历必为升序数组&#xff0c;加入数组后计算相邻两个数差值&#xff0c;即可求出最小绝对差 思路二&#xff1a;同样的思路&#xff0c;中序遍历&#xff0c;直接使用指针记录上一个节点&#xff0c;同时更新…

从public static void main(String[] args)看如何构造数据

java语言中public static void main(String[] args)里面的ages有什么作用&#xff1f; 在Java语言中&#xff0c;public static void main(String[] args) 是一个特殊的方法&#xff0c;它是Java程序的入口点。当你运行一个Java程序时&#xff0c;程序会从这个方法开始执行。这…

86. 分隔链表

86. 分隔链表 题目-中等难度示例1. 新建两链表&#xff0c;根据x值分类存放&#xff0c;最后合并 题目-中等难度 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保…

4WRAP6W7-08-30=G24K4/M=00比例先导阀控制放大器

先导控制阀是直动式比例阀。控制边的尺寸经过优化&#xff0c;可用作比例方向阀型号 4WRKE 的先导控制阀。 比例电磁铁为带可拆卸线圈的耐压密闭型湿式插脚交流线圈。 它们可将电流按比例转换为机械力。电流强度的增加会导致磁力相应增加。设定的磁力会在整个控制行程中保持不…

理解jvm之对象已死怎么判断?

目录 引用计数算法 什么是引用 可达性分析算法&#xff08;用的最多的&#xff09; 引用计数算法 定义&#xff1a;在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1…

Redis_缓存2_缓存删除和淘汰策略

14.5 缓存数据的删除和替换 14.5.1 过期数据 可以使用ttl查看key的状态。已过期的数据&#xff0c;redis并未马上删除。优先去执行读写数据操作&#xff0c;删除操作延后执行。 14.5.2 删除策略 redis中每一个value对应一个内存地址&#xff0c;在expires&#xff0c;一个内…

Gof23设计模式之模板方法模式

1.定义 定义一个操作中的算法骨架&#xff0c;而将算法的一些步骤延迟到子类中&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 2.结构 模板方法&#xff08;Template Method&#xff09;模式包含以下主要角色&#xff1a; 抽象类&#xff0…