培训网站推广杭州公司网站设计
news/
2025/9/23 2:43:15/
文章来源:
培训网站推广,杭州公司网站设计,花生壳建设网站,怎样创建个人购物网站FreeSWITCH 简单图形化界面38 - 在uniapp中使用JsSIP进行音视频呼叫 0、测试环境1、学习uniapp2、测试代码main.jsutils/render.jsstore/data.jspages/index/index.vuepages.json 3、效果4、难点 0、测试环境
http://myfs.f3322.net:8020/ 用户名#xff1a;admin#xff0c… FreeSWITCH 简单图形化界面38 - 在uniapp中使用JsSIP进行音视频呼叫 0、测试环境1、学习uniapp2、测试代码main.jsutils/render.jsstore/data.jspages/index/index.vuepages.json 3、效果4、难点 0、测试环境
http://myfs.f3322.net:8020/ 用户名admin密码admin
FreeSWITCH界面安装参考https://blog.csdn.net/jia198810/article/details/137820796
1、学习uniapp
在学习JsSIP的时候之前写过几个 demo 并试图将其拓展到手机端应用。采用纯 Web 页面在手机浏览器环境下借助 WSS 协议能够顺利达成通信效果。但考虑到实际的使用场景需要将其封装为独立的 APP 更好。
JsSIP 的音视频功能必须依赖 WSS 协议才能实现否则浏览器会限制音频或视频设备的调用并且需要有效的证书支持。即便证书不受浏览器信任用户还可以手动选择 “信任该证书继续访问” 来维持功能的正常使用。 鉴于 Uniapp 本质上也是基于网页技术之前认为在 Uniapp 中同样需要可信任的证书尤其是对于自签名证书而言由于 APP 中不存在 “信任该证书继续访问” 这样的手动操作选项就放弃了在 Uniapp 上的测试。
后来看下了Uniapp 框架的教程经过测试发现 Uniapp 不仅能够成功运行 JsSIP 库而且在使用 WSS 协议时证书似乎被默认信任了我不知道为什么但实际效果是可以正常使用。
值得注意的是Uniapp 本身并不直接具备调用 JsSIP 的能力但通过其提供的 renderjs 可以实现调用JsSIP库。调用JsSIP后基本就是复制之前的demo代码。
2、测试代码
基本流程就是index.vue界面变量发生变化后–触发render.js里的jssip逻辑。 就写了一个页面代码结构
main.js
// #ifndef VUE3
import Vue from vue
import { reactive } from vue
import App from ./App
Vue.config.productionTip falseApp.mpType appconst app new Vue({...App,
})
app.$mount()
// #endif//用了个pinia
//#ifdef VUE3
import { createSSRApp } from vue
import App from ./App.vue
import { reactive } from vue
import * as Pinia from pinia;export function createApp() {const app createSSRApp(App)app.use(Pinia.createPinia());return {app,Pinia}
}
// #endifutils/render.js
import JsSIP from jssip;
import {toRaw,inject
} from vue;export default {data() {return {// 是否有音频设备hasAudioDevice: false,// 初始化 uaMyCaller: null,// 当前会话currentCall: null,//当前呼叫类型currentCallMediaType: null,// 连接服务器失败次数connectCount: 5,ua: null,//分机设置setting: {username: 1020,password: 1020,wssServer: 210.51.10.231,wssPort: 7443,userAgent: MyWssPhone,},//当前分机状态status: {isRegistered: false,isUnregistered: false,isConnecting: false,isDisconnected: false,isNewMessage: false,isIncomingCall: false,isOutgoingCall: false,isIceCandidate: false,isProgress: false,isAccepted: false,},//jssip的socketsocket: null,//被叫号码callee: ,//呼叫放行originator: ,//呼叫媒体mediaType: audio,//音频控件ringtone: null,ringtoneSound: ./static/sounds/ringin.wav,//本地视频控件localVideoElement: null,//本地视频父控件localVideoElementId: local-video,//本地视频控件媒体流localMediaStream: null,//播放状态localVideoIsPlaying: false,//远程视频控件remoteVideoElement: null,//远程视频父控件remoteVideoElementId: remote-video,//远端媒体流remoteMediaStream: null,//日志enableLog: true,log: };},mounted() {},methods: {// 日志showLog(...data) {this.log data.join();if (this.enableLog) {//JsSIP.debug.enable(JsSIP:*);console.log(this.log);}},//接收vue页面username值updateUsername(newValue, oldValue) {this.showLog(用户名变化:, newValue, oldValue);this.setting.username newValue;},// 接收vue页面password值updatePassword(newValue, oldValue) {this.showLog(密码变化:, newValue, oldValue);this.setting.password newValue;},// 接收vue页面wssServer值updateWssServer(newValue, oldValue) {this.showLog(服务器地址变化:, newValue, oldValue);this.setting.wssServer newValue;},// 接收vue页面wssPort值updateWssPort(newValue, oldValue) {this.showLog(Wss端口变化:, newValue, oldValue);this.setting.wssPort newValue;},// 接收vue页面callee值updateCallee(newValue, oldValue) {this.showLog(被叫号码变化:, newValue, oldValue);this.callee newValue;},// 停止一切handleStop(newValue, oldValue) {if (!newValue) {return}//停止if (this.ua) {this.showLog(注销并停止ua);this.ua.unregister()this.ua.stop();}if (this.socket) {this.socket.disconnect()this.showLog(断开连接);}},//监听vue页面login 数据变化处理注册handleRegister(newValue, oldValue) {if (!newValue) {this.showLog(注销或者不注册)this.handleStop()return}if (!this.setting.username || !this.setting.password || !this.setting.wssServer || !this.setting.wssPort) {this.showLog(数据为空);return;}const jssip_uri new JsSIP.URI(sip,this.setting.username,this.setting.wssServer,this.setting.wssPort);this.showLog(uri, jssip_uri);const wss_uri wss://${this.setting.wssServer}:${this.setting.wssPort};this.socket new JsSIP.WebSocketInterface(wss_uri);const configuration {sockets: [this.socket],uri: jssip_uri.toAor(),authorization_user: this.setting.username,display_name: this.setting.username,register: true,password: this.setting.password,realm: this.setting.wssServer,register_expires: 300,user_agent: this.setting.userAgent,contact_uri: sip:${this.setting.username}${this.setting.wssServer};transportwss};JsSIP.C.SESSION_EXPIRES 180, JsSIP.C.MIN_SESSION_EXPIRES 180;this.showLog(配置参数, configuration);if (this.ua) {this.ua.stop();}this.ua new JsSIP.UA(configuration);this.ua.on(registrationFailed, (e) {this.onRegistrationFailed(e);});this.ua.on(registered, (e) {this.onRegistered(e);});this.ua.on(unregistered, (e) {this.onUnregistered(e);});this.ua.on(connecting, (e) {this.onConnecting(e);});this.ua.on(disconnected, (response, cause) {this.onDisconnected(response, cause);});this.ua.on(newMessage, (e) {this.onNewMessage(e);});this.ua.on(newRTCSession, (e) {this.showLog(新呼叫:, e)this.showLog(当前呼叫:主叫号码, e.request.from.uri.user);this.showLog(当前呼叫:被叫号码, e.request.to.uri.user);this.currentCall e.session;this.originator e.originator;if (this.originator remote) {//来电this.onInComingCall(e);} else {//去电this.onOutGoingCall(e);}this.currentCall.on(connecting, (e) {this.showLog(呼叫连接中)});this.currentCall.on(icecandidate, (e) {this.onIceCandidate(e);});this.currentCall.on(progress, (e) {this.onProgress(e);});this.currentCall.on(accepted, (e) {this.onAccepted(e);});this.currentCall.on(peerconnection, (e) {this.onPeerConnection(e);});this.currentCall.on(confirmed, (e) {this.onConfirmed(e);});this.currentCall.on(sdp, (e) {this.onSDP(e);});this.currentCall.on(getusermediafailed, (e) {this.onGetUserMediaFailed(e);});this.currentCall.on(ended, (e) {this.onEnded(e);});this.currentCall.on(failed, (e) {this.onFailed(e);});});this.ua.start();},// 呼叫async handleCall(newValue, oldValue) {console.log(newValue, oldValue)if (String(newValue).indexOf(audio) ! -1) {this.mediaType audio;} else {this.mediaType video;}if (this.ua null) {this.showLog(发起呼叫:没有ua)return false}if (this.ua.isRegistered() false) {this.showLog(发起呼叫:未注册)return false}if (this.callee ) {this.showLog(发起呼叫:被叫号码不能为空)return false;}if (this.callee this.setting.username) {this.showLog(发起呼叫:不能呼叫自己)return false;}if (this.currentCall) {this.showLog(发起呼叫:已经在通话中)return false;}let options {eventHandlers: {progress: (e) {},failed: (e) {},ended: (e) {},confirmed: (e) {},},mediaConstraints: this.mediaType video ? {audio: true,video: true} : {audio: true,video: false},mediaStream: await this.getLocalMediaStream(this.mediaType),pcConfig: {}};console.log(呼出OPTION:, options)try {this.currentCall toRaw(this.ua).call(sip:${this.callee}${this.setting.wssServer}, options);} catch (error) {console.debug(error)}},//接听电话async handleAnswerCall(newValue, oldValue) {if (!newValue) {return}if (this.currentCall null) {this.showLog(应答通话,没有通话);}//停止播放来电铃声if (this.ringtone) {this.showLog(应答通话,停止播放来电铃声)this.ringtone.pause();}//开始应答呼let options {mediaConstraints: this.currentCallMediaType video ? {audio: true,video: true} : {audio: true,video: false},mediaStream: await this.getLocalMediaStream(this.currentCallMediaType),pcConfig: {},};this.showLog(应答通话,options,, options);//应答来电this.currentCall.answer(options);},// 挂断通话handleHangupCall(newValue, oldValue) {console.log(this.currentCall)if (this.currentCall null) {this.showLog(挂断呼叫:没有通话);}if (this.currentCall) {this.currentCall.terminate();}},// 注册失败时回调函数onRegistrationFailed(e) {this.showLog(注册失败:, e);this.status.isRegistered false;},// 注册成功时回调函数onRegistered(e) {this.showLog(注册成功:, e);this.status.isRegistered true;//注册成功跳转到vue home页面this.handleToHome();},// 注销成功时回调函数onUnregistered(e) {this.showLog(注销成功:, e);this.status.isRegistered false;},// 连接中时回调函数onConnecting(e) {this.showLog(正在连接:, e);if (e.attempts this.connectCount) {this.showLog(连接失败次数超过5次停止连接, e);this.handleStop()}},// 服务器断开时回调函数onDisconnected(e) {this.showLog(断开连接, e);this.status.isRegistered false;},// 新短信时回调函数onNewMessage(e) {this.showLog(新短信:, e.originator, e.message, e.request);},// 来电时回调函数onInComingCall(e) {this.showLog(来电:, e);//获取主叫号码this.ringtone new Audio(this.ringtoneSound);this.ringtone.loop true;let play this.ringtone.play();if (play) {play.then(() {// 视频频加载成功// 视频频的播放需要耗时setTimeout(() {// 后续操作this.showLog(来电呼叫,播放来电铃声, this.ringtoneSound);}, this.ringtone.duration * 1000);}).catch((e) {this.showLog(来电呼叫,呼叫:播放来电铃声失败,, e);})}//判断媒体里是否有视频编码this.currentCallMediaType this.parseSdp(e.request.body);},// 去电时回调函数onOutGoingCall(e) {this.showLog(去电:, e);},// ice候选时回调函数onIceCandidate(e) {this.showLog(ice候选:, e);},// 呼叫中时回调函数onProgress(e) {this.showLog(呼叫中:, e);this.showLog(当前呼叫:progress-1,, e);this.showLog(当前呼叫:pregress-2:, this.currentCall.connection);if (this.originator local) {//去电this.showLog(当前呼叫:progress-3,去电播放被叫回铃音......)//播放180回铃音或者183sdp彩铃(音频彩铃)if (this.currentCall.connection) {let receivers this.currentCall.connection.getReceivers();this.showLog(当前呼叫:progress-4, receivers)let stream new MediaStream();stream.addTrack(receivers[0].track);let ringback new Audio();ringback.srcObject stream;ringback.play();}} else {//来电this.showLog(当前呼叫:progress-5,来电等待接听......)}},// 呼叫接受时回调函数onAccepted(e) {this.showLog(呼叫接受:, e);},// PeerConnection时回调函数onPeerConnection(e) {this.showLog(PeerConnection:, e);},// 呼叫确认时回调函数onConfirmed(e) {this.showLog(呼叫确认:, e);this.showLog(当前呼叫:confirmed,, this.originator);this.showLog(当前呼叫:confirmed,, this.currentCall.connection);let receivers this.currentCall.connection.getReceivers();let audioReceiver null;let videoReceiver null;// 区分音频和视频接收器receivers.forEach((receiver) {if (receiver.track.kind audio) {audioReceiver receiver;} else if (receiver.track.kind video) {videoReceiver receiver;}});// 播放音频if (audioReceiver) {this.showLog(播放远端音频)let audioElement new Audio();let stream new MediaStream();stream.addTrack(audioReceiver.track);this.audioStream stream;// 延时播放音频setTimeout(() {audioElement.srcObject stream;audioElement.play();}, 500); // 设置音频延时播放 1 秒}// 播放视频if (videoReceiver) {this.showLog(播放远端视频)this.remoteVideoElement document.createElement(video)// 直接设置内联样式this.remoteVideoElement.style.width 60%;this.remoteVideoElement.style.height 60%;this.remoteVideoElement.style.objectFit fill; // 视频将拉伸以填满容器this.remoteVideoElement.autoplay truethis.remoteVideoElement.playsinline truedocument.getElementById(this.remoteVideoElementId).appendChild(this.remoteVideoElement)this.remoteMediaStream new MediaStream();this.remoteMediaStream.addTrack(videoReceiver.track);// 延时播放视频setTimeout(() {this.remoteVideoElement.srcObject this.remoteMediaStream;this.remoteVideoElement.play();}, 500); // 设置视频延时播放 1 秒} else {this.showLog(没有视频流,可能是音频呼叫)}},// 获取sdp时回调函数onSDP(e) {this.showLog(获取sdp:, e);},// 获取媒体失败时回调函数onGetUserMediaFailed(e) {this.showLog(获取媒体失败:, e);},// 呼叫结束时回调函数onEnded(e) {this.releaseMediaStreams();this.showLog(呼叫结束:, e);},// 呼叫失败时回调函数onFailed(e) {this.releaseMediaStreams();this.showLog(呼叫失败:, e);},// 释放媒体流的方法releaseMediaStreams() {//释放本地视频if (this.localMediaStream) {this.localMediaStream.getTracks().forEach(track {this.showLog(停止本地媒体流:, track.kind); // 显示是音频还是视频轨道track.stop(); // 停止轨道});this.localMediaStream null;}// 移除远程video元素if (this.remoteVideoElement) {this.showLog(移除远程video元素);this.remoteVideoElement.remove();this.remoteVideoElement null;}// 清理本地视频元素如果有if (this.localVideoElement) {this.showLog(移除本地video元素);this.localVideoElement.srcObject null; // 确保不再引用本地流this.localVideoElement null;}//停止播放铃声if (this.ringtone) {this.showLog(停止播放来电铃声)this.ringtone.pause();}this.currentCall null;},//解析sdp获取媒体类型呼入时使用parseSdp(sdp) {this.showLog(解析SDP:sdp是, sdp)let sb {};let bs sdp.split(\r\n);bs.forEach((value, index) {let a value.split();sb[a[0]] a[1];});let mediaType sb.m.split( )[0]// mediaType audio or mediaType video// 根据不通的类型弹窗this.showLog(解析SDP:媒体类型是, mediaType)return mediaType;},//获取本地媒体 //stream.getTracks() [0]音频 [1]视频.//stream.getAudioTracks() stream.getVideoTracks()// 获取本地流async getLocalMediaStream(mediaType) {try {this.showLog(尝试获取本地媒体流:, mediaType);let constraints mediaType video ? {audio: true,video: true} : {audio: true,video: false};let stream await navigator.mediaDevices.getUserMedia(constraints);this.localMediaStream stream; // 保存本地媒体流关闭时使用它。this.showLog(获取本地媒体流成功, stream);return stream;} catch (error) {this.showLog(获取本地媒体流失败, 设置虚拟摄像头:, error.name, error.message);// 获取音频流let audioConstraints {audio: true};let audioStream;try {audioStream await navigator.mediaDevices.getUserMedia(audioConstraints);} catch (audioError) {this.showLog(获取音频流也失败仅使用虚拟摄像头:, audioError.name, audioError.message);return this.createVirtualStream(); // 如果音频也获取失败直接返回虚拟摄像头流}// 获取虚拟摄像头视频流let videoStream this.createVirtualStream();// 合并音频和视频流let combinedStream new MediaStream([...audioStream.getTracks(), ...videoStream.getTracks()]);return combinedStream;}},// 创建虚拟摄像头createVirtualStream() {const text 未找到摄像头设备;const canvas document.createElement(canvas);canvas.width 800;canvas.height 600;const ctx canvas.getContext(2d);ctx.fillStyle black;ctx.fillRect(0, 0, canvas.width, canvas.height);ctx.fillStyle white;ctx.font 64px Arial;ctx.textAlign center;ctx.fillText(text, canvas.width / 2, canvas.height / 2);// 将画布内容转换为MediaStreamconst stream canvas.captureStream();return stream;},// 播放本地媒体流async handleOpenLocalVideo(newValue, oldValue) {if (newValue false) {this.showLog(初始化数据,跳过);return}this.showLog(打开本地媒体);// 首先获取本地视频if (this.localVideoElementId) {this.showLog(播放视频的控件id:, this.localVideoElementId);if (this.localVideoIsPlaying) {// 已经打开了摄像头就无需再次打开了this.showLog(本地音频/视频已经打开无需再次打开);return;} else {try {const stream await this.getLocalMediaStream(video);this.showLog(视频流为:, stream)if (stream) {this.localVideoElement document.createElement(video)// 直接设置内联样式this.localVideoElement.style.width 60%;this.localVideoElement.style.height 60%;this.localVideoElement.style.objectFit fill; // 视频将拉伸以填满容器this.localVideoElement.autoplay truethis.localVideoElement.playsinline truethis.localVideoElement.srcObject stream;document.getElementById(this.localVideoElementId).appendChild(this.localVideoElement)this.localVideoIsPlaying true;} else {this.showLog(无法获取本地视频流获取到的流为空);}} catch (error) {this.showLog(获取本地视频流失败:, error);if (error.name NotAllowedError) {this.showLog(用户拒绝授予摄像头权限);} else if (error.name NotFoundError) {this.showLog(未找到摄像头设备);} else {this.showLog(其他错误:, error.name, error.message);}}}} else {this.showLog(没有本地视频控件id);}},//关闭本地摄像头handleCloseLocalVideo(newValue, oldValue) {if (newValue false) {this.showLog(初始化数据,跳过);return}this.showLog(关闭本地视频流);if (this.localMediaStream) {this.localMediaStream.getTracks().forEach((track) {track.stop();});this.localMediaStream null;this.localVideoIsPlaying false; // 更新本地视频播放状态//移除本地video元素if (this.localVideoElement) {this.localVideoElement.remove();this.localVideoElement null;}} else {this.showLog(没有本地流可以关闭);}},/*** Vue界面相关的操作*///跳转vue界面到home页面,未用到handleToHome() {this.$ownerInstance.callMethod(handleToHome);},//测试piniatest(newValue,oldValue){console.log(我在测试pinia数据是:,newValue,oldValue);console.log(当前ua的状态:,this.ua?.isRegistered());}},// //监听status,如果发送变化,则更新状态,向vue页面发送状态数据watch: {status: {handler(newValue, oldValue) {console.log(监听status变化,向vue页面发送:, newValue, oldValue);this.$ownerInstance?.callMethod(handleUpdateStatus, newValue);},deep: true},log: {handler(newValue, oldValue) {//console.log(监听log变化,向vue页面发送, newValue, oldValue);this.$ownerInstance?.callMethod(handleUpdateLog, newValue);},deep: true}}
}store/data.js
import {defineStore
} from pinia;export const useSettingStore defineStore(data, {state: () {return {formData: {username: 1020,password: 1020,wssServer: 210.51.10.231,wssPort: 7443,mediaType: audio,callee: ,},action: {register: false,openRemoteVideo: false,closeRemoteVideo: false,openLocalVideo: false,closeLocalVideo: false,audioCall: false,videoCall: false,answerCall: false,hangUpCall: false,stop: false,},status: {data:}};},
});pages/index/index.vue
templateview classlogin-container :registeraction.register :change:registerWebPhone.handleRegister:usernameformData.username :change:usernameWebPhone.updateUsername :passwordformData.password:change:passwordWebPhone.updatePassword :wssServerformData.wssServer:change:wssServerWebPhone.updateWssServer :wssPortformData.wssPort:change:wssPortWebPhone.updateWssPort :calleeformData.callee :change:calleeWebPhone.updateCallee:audioCallaction.audioCall :change:audioCallWebPhone.handleCall :videoCallaction.videoCall:change:videoCallWebPhone.handleCall :answerCallaction.answerCall:change:answerCallWebPhone.handleAnswerCall :hangupCallaction.hangupCall:change:hangupCallWebPhone.handleHangupCall :openLocalVideoaction.openLocalVideo:change:openLocalVideoWebPhone.handleOpenLocalVideo :closeLocalVideoaction.closeLocalVideo:change:closeLocalVideoWebPhone.handleCloseLocalVideo :stopaction.stop :change:stopWebPhone.handleStop!-- 厂商Logo或者软件名称 --text classsoftware-name{{ softwareName }}/text!-- 登录表单 --uni-forms refform :modelformData!-- 分机号 --uni-forms-itemuni-easyinput v-modelformData.username placeholder请输入分机号 //uni-forms-item!-- 分机密码 --uni-forms-itemuni-easyinput v-modelformData.password placeholder请输入分机密码 //uni-forms-item!-- 服务器地址 --uni-forms-itemuni-easyinput v-modelformData.wssServer placeholder请输入服务器地址 //uni-forms-itemuni-forms-itemuni-easyinput v-model.numberformData.wssPort placeholder请输入服务器端口 //uni-forms-item!-- 提交按钮 --button form-typesubmit classsubmit-btn :disabledstatus.data?.isRegistered clickhandleRegister登录/buttonview{{ status.data?.isRegistered }}/viewview{{ log.data }}/view/uni-formsview classcontainer!-- 远端视频 --view classremote-video-containerviewtext classdescription远端视频/text/viewview idremote-video/view/view!-- 本地视频 --view classlocal-video-containerviewtext classdescription本地视频/text/viewview idlocal-video/view/view!-- 控制按钮开启、关闭、切换前后摄像头 --view classcontrol-buttonsuni-rowuni-col :span12button clickopenLocalVideo开启本地视频/button/uni-coluni-col :span12button clickcloseLocalVideo关闭本地视频/button/uni-col/uni-row/view!-- 号码输入框 --view classinput-containeruni-easyinput v-model.numberformData.callee placeholder请输入被叫号码 //view!-- 通话控制按钮音频、视频通话、挂断 --view classcall-control-buttonsuni-rowuni-col :span6button clickhandleAudioCall音频/button/uni-coluni-col :span6button clickhandleVideoCall视频/button/uni-coluni-col :span6button clickhandleAnswerCall接听/button/uni-coluni-col :span6button clickhandleHangupCall挂断/button/uni-col/uni-row/view!-- 退出按钮 --view classexit-buttonbutton clickhandleStop注销/button/view/view/view
/templatescript moduleWebPhone langrenderjsimport render from ../../utils/render.js;export default render;
/scriptscriptimport {ref,reactive,} from vue;import {useSettingStore} from ../../store/data;import { storeToRefs } from pinia;export default {setup() {const setting useSettingStore();const formData setting.formData;const action setting.action;const status setting.status;const form ref(null);//日志const log reactive({data: })//软件名称const softwareName MyWssPhone;//修改register值传给renderjsconst handleRegister () {console.log(提交的数据:, formData);action.register new Date().getTime();};const openLocalVideo () {//不变化不触发renderjs里的方法,所以用date作为每次变化的值console.log(开启本地视频);action.openLocalVideo new Date().getTime();};const closeLocalVideo () {console.log(关闭本地视频);action.closeLocalVideo new Date().getTime();};const handleAudioCall () {console.log(开始音频通话);action.audioCall audio new Date().getTime();};const handleVideoCall () {console.log(开始视频通话);action.videoCall video new Date().getTime();};const handleAnswerCall () {console.log(接听电话)action.answerCall answer new Date().getTime();}const handleHangupCall () {console.log(挂断通话);action.hangupCall new Date().getTime();};const handleStop () {console.log(注销);action.stop new Date().getTime();};//跳转到首页const handleToHome () {console.log(vue页面跳转到home);// uni.navigateTo({// url: /pages/home/index,// });};//监听renderjs传回的状态const handleUpdateStatus (data) {//console.log(vue页面接收到的状态:, data);status.data data;};//监听renderjs传回的日志const handleUpdateLog (data) {//console.log(vue页面接收到的日志:, data);log.data data;};return {formData,action,status,log,form,softwareName,handleRegister,handleUpdateStatus,handleUpdateLog,handleToHome,openLocalVideo,closeLocalVideo,handleAudioCall,handleVideoCall,handleAnswerCall,handleHangupCall,handleStop,};},};
/scriptstyle scoped.login-container {padding: 20px;display: flex;flex-direction: column;align-items: center;}.logo,.software-name {margin-bottom: 20px;text-align: center;}.submit-btn {width: 100%;margin-top: 20px;background-color: royalblue;color: white;}.container {display: flex;flex-direction: column;align-items: center;padding: 20px;}.remote-video-container,.local-video-container {text-align: center;}.local-video {width: 350px;/* 容器宽度 */height: 270px;/* 容器高度 */display: flex;justify-content: center;align-items: center;}.remote-video {width: 350px;/* 容器宽度 */height: 270px;/* 容器高度 */display: flex;justify-content: center;align-items: center;}.description {font-size: 12px;}.control-buttons,.call-control-buttons {flex: auto;}.input-container {width: 50%;max-width: 300px;}button {margin: 10px;font-size: 12px;border: none;border-radius: 4px;background-color: #007bff;color: white;cursor: pointer;width: 98%;}button:hover {background-color: #0056b3;}.exit-button button {font-size: 20px;background-color: red;}.exit-button button:hover {background-color: darkred;}
/stylepages.json
{pages: [{path: pages/index/index,style: {navigationBarTitleText: 登录}},{path: pages/home/index,style: {navigationBarTitleText: 首页}}],globalStyle: {navigationBarTextStyle: black,navigationBarTitleText: uni-app,navigationBarBackgroundColor: #F8F8F8,backgroundColor: #F8F8F8,app-plus: {background: #efeff4}},condition: {current: 1,list: [{name: 登录,path: pages/index/index,query: }]}
}
3、效果
配置下测试环境在手机打开app的所有权限网络、使用音频设备等看下效果 Screenrecorder-2024-12- 4、难点
在uniapp上使用JsSIP可以正常进行音视频通信但是 APP保活是问题本人并不了解安卓底层开发APP运行一段时间后程序被自动杀掉了我也沙雕了。 兴趣使然 仅用于参考祝君好运
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/911146.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!