培训网站推广杭州公司网站设计

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,一经查实,立即删除!

相关文章

西安网站建设易网宣杭州推广公司排名

描述 电压跟随电路 电压跟随器是共集电极电路,信号从基极输入,射极输出,故又称射极输出器。基极电压与集电极电压相位相同,即输入电压与输出电压同相。这一电路的主要特点是:高输入电阻、低输出电阻、电压增益近似为…

电子商务网站建设方面的论文市场营销策略模板

文章目录 前言ClassLoaderJAVA SPI机制Spring SPI机制示例原理 如何加载jar包里的class 前言 Java的SPI机制与Spring中的SPI机制是如何实现的? ClassLoader 这里涉及到了class Loader的机制,有些复杂,jdk中提供默认3个class Loader&#x…

英文网站建设方案模板高校seo优化的常用手法

登入的角色本身属于领导级别(集团权限),没有下级的不同权限: 切换不同身份(公司),以获得相应部门的不同导航菜单及权限 这里实现:更改角色权限后,实现页面 不刷新 更改…

新泰网站定制郑州专业的建网站

对象指针 实验介绍 本节实验专门介绍对象指针相关内容。指针是一把双刃剑,用好了非常顺手使用,但同时也好要小心使用指针。指针是 C/C++ 语言的特点,只有掌握好指针才能学习到精髓。 知识点 对象指针对象成员指针this 指针对象指针 在前面的实验中已经使用过对象指针,本…

昆山商城网站建设公司名高端大气不重名

APache Dubbo简介 Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式…

网站建设数据库实训体会定制化软件

绪论 1)信息,消息,信号通信:利用电(或者光)信号传输消息中所包含的信息。信息:消息的内涵。 消息:信息的物理表现形式。(可分为两类连续消息语音,音乐&#x…

网页建站总结报告不一样的婚恋网站怎么做

题目:有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。 输入:8 输出:7 用list实现循环遍历的过程 import copy a[1,2,3,4…

中国免费建设网站网址做棋牌网站违法吗

说明 在 Synopsys* VCS* 和 VCS* MX 仿真器中模拟由以下位置生成的 Nios V 处理器系统时,可能会出现该问题: 英特尔 Quartus Prime Pro Edition 软件版本 23.1 至 23.4,或 英特尔 Quartus Prime Standard Edition 软件版本 23.1std 这是由…

网站建设中的英文单词如何用手机制作网页链接

在Python中,浮点数是一种用于表示带有小数部分的数值类型。浮点数可以用来进行各种数学运算,包括加法、减法、乘法和除法等。 以下是Python中使用浮点数的语法示例: # 声明一个浮点数变量number 3.14# 进行浮点数之间的运算a 2.5b 1.3c a…

如何建 网站邵阳经开区网站

在JavaScript中,数据类型是编程中非常重要的概念,它决定了数据的性质、如何存储以及如何操作这些数据。以下是JavaScript中的主要数据类型、它们的区别以及数据类型检测的方式的详细介绍。 JavaScript的主要数据类型 1. 原始数据类型(Primi…

企业网站优化报价怎么给网站 做排名

XSLVGL2.0 开发手册 【XSLVGL2.0】如何在其它线程内更新UI 1、概述2、UI资源锁锁死怎么办?1、概述 项目常常会在其它线程更新UI的状态,但如果直接更新UI会导致UI数据异常从而使得UI挂死。这时只需要使用UI资源锁即可保证UI数据不会异常了。 参考XSLVGL2.0 User Manual 页面…

房地产市场发展趋势成都百度推广账户优化

介绍:在简单工厂模式中,我们提到,工厂方法模式是简单工厂模式的一个延伸,它属于Gof23中设计模式的创建型设计模式。它解决的仍然是软件设计中与创建对象有关的问题。它可以更好的处理客户的需求变化。引入我们继续来说"new&q…

网站开发需要的资料微信开发在哪能看

中午好,我的网工朋友。 都放假了没?龙年将至,都有啥新年计划? 过年,讲究的就是一个热闹,可以暂时告别辛苦的一年,重新整装出发。 热闹可少不了春联啊,红红火火又一年,…

福州整站优化企业网站页面设计

目录 前言引言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Jupyter Notebook环境Pycharm 环境 相关其它博客工程源代码下载其它资料下载 前言 博主前段时间发布了一篇有关方言识别和分类模型训练的博客,在读者的反馈中发现许多小伙伴对方言…

西安咪豆网站建设公司php网站开发实例视频教程

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考,主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等,本系列文章篇数较多,不定期更新,上半部分介绍无约束优化,…

网站运营经验分享ppt模板个人网页设计与制作教程

intersect组件是解决纵向联邦学习中的隐私求交问题 fate隐私求交的方式有三种:raw,rsa,dh。raw方式不安全,rsa和dh方式是安全的,dh是基于对称加密的安全交集 rsa是基于RSA(非对称加密)的安全交集,,dh方法也用于安全的…

建个网站有什么用营销渠道有哪些

Renderer2 类 Renderer2 类是 Angular 提供的一个抽象服务,允许在不直接操作 DOM 的情况下操纵应用程序的元素。这是推荐的方法,因为它使得更容易开发可以在没有 DOM 访问权限的环境中渲染的应用程序,比如在服务器上、在 Web Worker 中或在原…

最好的网站设计公司源码 php桂林市区好玩的地方

随着信息化时代的到来,煤矿行业也迎来了前所未有的机遇与挑战。在这个充满活力和竞争的领域,技术的革新对于提高生产效率、保障安全生产至关重要。而随着时间敏感网络(TSN)技术的不断发展,TSN工业交换机作为其关键组成…

无锡制作网站价格黄页88企业名录

使用社区版,您可以在本地服务器上安装 ONLYOFFICE 文档,并将在线编辑器与 ONLYOFFICE 协作平台或其他热门系统集成在一起。 ONLYOFFICE 文档是什么 ONLYOFFICE 文档是一个功能强大的文档编辑器,支持处理文本文档、电子表格、演示文稿、可填写…

免费试用网站有哪些网上网络推广

系列文章目录 文章目录 系列文章目录前言一、UR5 URDF 代码二、base_link三、shoulder_pan_joint四、shoulder_pan_trans五、ROS base_link 坐标系到 UR 机器人 Base 坐标系的转换六、与全零TCP&#xff08;工具坐标系&#xff09;重合的工具链接 前言 一、UR5 URDF 代码 <…