import request from './request.js' import { clickCopyText, pasteText } from './common.js' // 禁止双击缩放 document.addEventListener('dblclick', function (e) { e.preventDefault(); }); const { Dialog, Toast } = vant Toast.setDefaultOptions({ duration: 2000 }); // 从 CLOUD_GAME_SDK 结构中解构必要的函数和常量 const RtcEngineSDK = window.rtc_sdk.CloudGameSdk // 业务通道定时标识 let doConnectDirectivesIntervalerPing = null // 获取云机数据定时标识 let getUserCardInfoTimerInterval = null let getUserCardInfoRequestNum = 1 let doConnectDirectivesRequestNum = 1 let doConnectDirectivesTimerInterval = null // 触碰间隔定时标识 let noOperationSetTimeoutTimeInterval = null let noOperationSetIntervalTimeInterval = null // 倒计时定时标识 let countdownTimeInterval = null // let vc = new VConsole() const app = new Vue({ el: '#app', data: { // 底部按钮 footerBtn: [{ key: 'task', img: '../static/img/wx/gengduo_icon.png' }, { key: 'home', img: '../static/img/wx/home_icon.png' }, { key: 'onBack', img: '../static/img/wx/fanhui_icon.png' }], // 宽高 width: 0, height: 0, // webRtc实例 engine: {}, // 横竖屏幕 false是竖 isLandscape: false, // 悬浮球位置 levitatedSpherePositionData: {}, // 右侧弹窗 levitatedSphereVisible: false, // 清晰度数据 definitionList: [{ name: '高清', value: 2800 }, { name: '标清', value: 2200, }, { name: '极速', value: 1800, }], // 选中的清晰度 definitionValue: '', // 分辨率 resolutionRatioVisible: false, resolutionRatioList: [], // 需要用到的参数 parametersData: {}, // 屏幕分辨率 phoneSize: {}, // 业务指令通道实例 doConnectDirectivesWs: null, // 粘贴版 pasteVersionVisible: false, pasteVersionList: [], // 复制内容 copyTextValue: '', copyTextVisible: false, // 卡数据 userCardInfoData: {}, // 是否显示计时 timingVisible: false, // 计费规则 billingRulesVisible: false, applyRecommendVisible: false, // 是否是启动不操作自动退出云机功能 isFiringNoOperationSetTimeout: false, // 超过指定触碰时间的弹窗 noOperationSetTimeoutTimeVisible: false, // 超过指定触碰时间的弹窗文案 confirmButtonText: '', // 云机剩余时长 countdownTime: 0, // 是否支持webRTC isSupportRtc: !!( typeof RTCPeerConnection !== 'undefined' && typeof RTCIceCandidate !== 'undefined' && typeof RTCSessionDescription !== 'undefined' ), // 推荐列表 recommendList: [], layoutViewWidth: null, layoutViewHeight: null, // 是否显示video isShowVideo: false, plugFlowStartTime: null, obtainCardInfoStartTime: null }, created() { this.initConfig() }, mounted() { this.getUserCardInfo() }, computed: { // 右侧弹框退出相关按钮 exitList() { let arr = [{ name: '剪贴版', key: "shearplate", img: '../static/img/wx/jianqieban_icon.png' }, { name: '退出', key: 'signout', img: '../static/img/wx/tuichu_icon.png' }] if ([1, 2, 3].includes(+this.parametersData.userCardType)) { arr.push({ name: '退出并下机', key: 'dormant', img: '../static/img/wx/tuichu_icon.png' }) } return arr }, rtcMediaPlayerStyle() { let obj = { objectFit: "fill" } if (this.isLandscape) { obj = { width: `${this.layoutViewHeight}px`, height: `${this.layoutViewWidth}px`, left: '50%', top: '50%', transform: 'translate(-50%, -50%) rotate(90deg)' } } return obj } }, methods: { // 初始化 initConfig() { // 获取窗口尺寸 this.getInitSize() let levitatedSpherePositionData = localStorage.getItem('levitatedSpherePositionData') let definitionValue = localStorage.getItem('definitionValue') // 悬浮球位置 this.levitatedSpherePositionData = levitatedSpherePositionData ? JSON.parse(levitatedSpherePositionData) : { right: '15px', top: '15px' } // 清晰度 this.definitionValue = definitionValue ? +definitionValue : 2200 // 获取参数 this.parametersData = getParameters() let { token, validTime } = this.parametersData // 给api增加需要的参数 request.defaults.headers.Authorization = token request.defaults.headers.versionname = '5.8.7' window.onresize = () => { this.getInitSize() } }, // 连接webRTC connectWebRtc() { console.time('获取推流响应消耗时间:') this.plugFlowStartTime = +new Date() const { sn: topic, cardToken: authToken, internetHttps, internetHttp, webrtcTransferCmnet, webrtcTransferTelecom, webrtcTransferUnicom } = this.userCardInfoData; const isWss = location.protocol === 'https:' const url = `${isWss ? 'wss://' : 'ws://'}${isWss ? internetHttps : internetHttp}/nats`; const ICEServerUrl = [ { "CMNET": webrtcTransferCmnet || '' }, // 移动 { 'CHINANET-GD': webrtcTransferTelecom || '' }, // 电信 { 'UNICOM-GD': webrtcTransferUnicom || '' }, // 联通 ]; const connection = { name: "猪猪令是猪", topic, // 云机ID 必填 url, //信令服务地址 必填 ICEServerUrl, width: 720, // 推流视频宽度 必填 height: 1280, // 推流视频高度 必填 // cardWidth: this.phoneSize.width || 1080, // 云机系统分辨率 宽 必填 // cardHeight: this.phoneSize.height || 1920, // 云机系统分辨率 高 必填 // cardDensity: this.phoneSize.dpi || 480, // 云机系统显示 密度 必填 cardWidth: 0, // 云机系统分辨率 宽 必填 cardHeight: 0, // 云机系统分辨率 高 必填 cardDensity: 0, // 云机系统显示 密度 必填 authToken, //拉流鉴权 token 必填 bitrate: 6000, //码率 必填 fps: 30, //必填 callback: this.statusCallBack,//回调函数 必填 }; // 初始化 SDK this.engine = new RtcEngineSDK(connection); this.engine.RtcEngine() }, // webRTC状态回调 statusCallBack(event) { if (event.type !== 'StreamStates') { // console.log("链接的状态", event, event.val); } switch (event.type) { case "screenChange": // 0:横屏 1:竖屏 console.log("屏幕方向变化事件:" + event.val); this.isLandscape = event.val === 0; break; case "wsState": // “TIMEOUT”:nats链接超时 // success 链接成功 const status = ["TIMEOUT", "failed"] if (status.includes(event.val)) { Dialog.alert({ title: '提示', message: '链接超时', confirmButtonText: '确定', confirmButtonColor: '#3cc51f', beforeClose: (action, done) => { this.exit() done() } }) } break; case "rtcState": // “connected”:rtc链接成功 “failed”:rtc链接失败 “closed”:rtc链接关闭 “disconnected”:rtc链接超时 if (event.val === "connected") { Toast.clear(); this.doConnectDirectives() this.isShowVideo = true this.$refs.rtcMediaPlayer.muted = true this.$refs.rtcMediaPlayer.play() let ms = +new Date - this.plugFlowStartTime console.timeEnd('获取推流响应消耗时间:') console.log(`获取推流响应消耗时间:${ms / 1000}秒`) this.definitionFun(this.definitionValue) this.pushflowPopup() this.getResidueTime() return } if (event.val === "connecting") return this.exit() break; case "AuthenticationStatus": console.log(`鉴权${event.val === "success" ? '成功' : '失败'}`); break; case "StreamStates": // “currentRoundTripTime”:延迟 “lostRate”:丢包率 “seconds_KBytes”:带宽 “framesPerSecond”:帧率 // ballPosition.value = event.val // console.log(event.val) break; case 'videoResolution': // 云机视频分辨率 // console.log(event.val, '分辨率') break case 'networkService': // 获取三网信息 console.log("三网信息:", event.val); break case 'networkServiceURL': // 获取三网信息 console.log("三网IP地址:", event.val); break } }, // 悬浮球移动 touchmoveLevitatedSphere(e) { let { pageX, pageY } = e.targetTouches[0] let min = 20 let MaxPageX = this.width - 20 let MaxPageY = this.height - 20 pageX = pageX <= min ? min : (pageX >= MaxPageX ? MaxPageX : pageX) pageY = pageY <= min ? min : (pageY >= MaxPageY ? MaxPageY : pageY) this.levitatedSpherePositionData = { left: `${pageX}px`, top: `${pageY}px`, transform: 'translate(-50%, -50%)' } }, touchendLevitatedSphere(e) { localStorage.setItem('levitatedSpherePositionData', JSON.stringify(this.levitatedSpherePositionData)) }, // 清晰度 definitionFun(value) { this.definitionValue = value this.engine.makeBitrate && this.engine.makeBitrate(value) localStorage.setItem('definitionValue', value) this.levitatedSphereVisible = false }, // 修改分辨率 resolutionRatio() { request.get('/api/resources/v5/machine/resolution/getResolvingPower', { params: { userCardId: this.parametersData.userCardId } }).then(res => { if (res.success) { this.resolutionRatioList = res.data.map(item => { item.height = item.high return item }) this.levitatedSphereVisible = false this.resolutionRatioVisible = true } }) }, // 确定修改分辨率 confirmResolution() { let { width, height, dpi: density } = this.phoneSize this.engine.makeResolution && this.engine.makeResolution({ width, height, density }) this.resolutionRatioVisible = false }, // 退出相关按钮操作 exitFun(key) { switch (key) { case 'dormant': Dialog.alert({ title: '提示', message: '确定退出云手机并下机', confirmButtonText: '确定', confirmButtonColor: '#3cc51f', showCancelButton: true, beforeClose: (action, done) => { if (action === 'cancel') done() if (action === 'confirm') { this.downline(done) } } }) break; case 'shearplate': this.copyTextValue = '' pasteText().then(content => { typeof content !== 'boolean' ? this.openPasteboard(content) : this.copyTextVisible = true }, err => { this.copyTextVisible = true }) break; case 'signout': this.exit() break; } }, // 业务指令 doConnectDirectives() { let { internetHttps, localIp, cardToken } = this.userCardInfoData let cUrl = `wss://${internetHttps}/businessChannel?cardIp=${localIp}&token=${cardToken}&type=directives` this.doConnectDirectivesWs = new WebSocket(cUrl); this.doConnectDirectivesWs.binaryType = 'arraybuffer' clearInterval(doConnectDirectivesIntervalerPing) // 链接成功 this.doConnectDirectivesWs.onopen = (e) => { doConnectDirectivesIntervalerPing = setInterval(() => { if (this.doConnectDirectivesWs.readyState === 1) { this.doConnectDirectivesWs.send(JSON.stringify({ type: 'ping' })); } else { clearInterval(doConnectDirectivesIntervalerPing); } }, 3000) this.doConnectDirectivesWs.send(JSON.stringify({ type: 'getVsStatus' })) this.doConnectDirectivesWs.send(JSON.stringify({ type: 'bitRate', data: { bitRate: 1243000 } })) this.doConnectDirectivesWs.send(JSON.stringify({ type: 'InputMethod', data: { type: 2 } })) this.doConnectDirectivesWs.send(JSON.stringify({ type: 'getPhoneSize' })) } // 接受到的消息 this.doConnectDirectivesWs.onmessage = res => { const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; switch (result.type) { // 分辨率 case 'getPhoneSize': case 'setPhoneSize': let data = JSON.parse(JSON.stringify(result.data)) let { width, height } = data if (width > height) { data.width = height data.height = width } this.phoneSize = data break // 云机复制过来的文本 case 'reProduceText': if (navigator.clipboard) { navigator.clipboard.writeText(result.data.text); } break case 'downAdnInstallRep': Toast(result.data.msg) break // 接受到这个消息就自动退出云机 case 'exitPhone': this.exit() break } } // 链接报错的回调 this.doConnectDirectivesWs.onerror = res => { clearInterval(doConnectDirectivesTimerInterval) if (doConnectDirectivesRequestNum > 6) { this.exit() return } doConnectDirectivesRequestNum++ this.doConnectDirectives() } }, // 粘贴版相关接口 shearContent({ type, params, queryStr }) { let url = '/api/public/v5/shear/content' if (queryStr) url += queryStr return request[type](url, params) }, // 清空全部、清除某条 deletePasteVersion(ids) { if (!ids) { Dialog.alert({ title: '提示', message: '确定清空剪贴板?', confirmButtonText: '确定', confirmButtonColor: '#3cc51f', showCancelButton: true, beforeClose: (action, done) => { if (action === 'cancel') done() if (action === 'confirm') { fun.bind(this)(done) } } }) return } fun.bind(this)() function fun(callBack = () => { }) { this.shearContent({ type: 'delete', queryStr: Qs.stringify( { ids: ids ? [ids] : this.pasteVersionList.map((v) => v.id), }, { arrayFormat: 'repeat', addQueryPrefix: true }, ) }).then(res => { if (res.status === 0) { this.getPasteVersion() callBack(true) } else { callBack(false) Toast(res.msg) } }).catch(() => { callBack(false) }) } }, // 获取粘贴版数据 getPasteVersion(callBack = () => { }) { this.shearContent({ type: 'get' }).then(res => { this.pasteVersionList = res.data callBack(true) }).catch(() => { callBack(false) }).finally(() => { }) }, // 复制弹窗是否关闭 beforeCloseCopy(action, done) { if (action !== 'confirm') { // 获取剪切板 this.getPasteVersion(() => { this.pasteVersionVisible = true this.levitatedSphereVisible = false done() }) return } if (!this.copyTextValue) { Toast('请输入复制到剪切板的内容') done(false) return } this.openPasteboard(this.copyTextValue, done) }, // 打开粘贴板 async openPasteboard(content, callBack = () => { }) { this.shearContent({ type: 'post', params: { content } }).then().finally(() => { callBack() // 获取剪切板 this.getPasteVersion(() => { this.pasteVersionVisible = true this.levitatedSphereVisible = false }) }) }, // 复制粘贴某条数据 copyPasteVersiontext(e) { clickCopyText(e, (event) => { this.doConnectDirectivesWs.send(JSON.stringify({ type: 'cutting', data: { str: event.text, }, })) Toast('复制成功') }, () => { Toast('复制失败') }) }, // 获取卡信息 getUserCardInfo() { Toast.loading({ duration: 0, // 持续展示 toast message: '数据加载中...', }); this.obtainCardInfoStartTime = + new Date() console.time('获取卡信息响应消耗时间:') let { userCardId } = this.parametersData userCardId = +userCardId const statusTips = { 5200: '网络异常,请稍后重试', 5220: '云手机正在一键修复中', 5203: '正在排队中,请稍等', 5204: '云机异常,正在为你重新分配云机' } request.post('/api/resources/user/cloud/connect', { userCardId }).then(async res => { const { isWeixin } = this.parametersData; switch (res.status) { case 0: getUserCardInfoRequestNum = 1 // 不支持webRTC跳转到指定的页面 if (!res.data.isWebrtc) { location.replace(`${location.origin}/h5/webRtcYJ/WXtrialInterface.html${location.search}`) return } if (!this.isSupportRtc) { Dialog.alert({ title: '提示', message: `${+isWeixin ? '微信小程序' : ''}当前版本暂不支持使用,可下载谷歌浏览器或双子星客户端进行使用`, confirmButtonText: '确定', confirmButtonColor: '#3cc51f', beforeClose: (action, done) => { this.exit() done() } }) return } this.userCardInfoData = res.data let ms = + new Date() - this.obtainCardInfoStartTime console.timeEnd('获取卡信息响应消耗时间:') console.log(`获取卡信息响应消耗时间:${ms / 1000}秒`) this.connectWebRtc() return case 5200: case 5220: case 5203: case 5204: if (res.status === 5200) { reconnect.bind(this)() return } Toast(statusTips[res.status]) break default: Toast('画面异常,请重新进入') break } setTimeout(() => { this.exit() }, 3000) }) function reconnect() { if (getUserCardInfoRequestNum > 6) { Toast('网络异常,请稍后重试') clearTimeout(getUserCardInfoTimerInterval) setTimeout(() => { this.exit() }, 3000) return } getUserCardInfoTimerInterval = setTimeout(() => { this.getUserCardInfo() getUserCardInfoRequestNum++ }, 3000) } }, // 超过指定触碰时间是否提示关闭链接 pushflowPopup() { request.get('/api/public/v5/pushflow/popup').then(res => { if (res.success) { this.isFiringNoOperationSetTimeout = res.data this.noOperationSetTimeout() } }) }, // 退出功能 exit() { this.engine.disconnect && this.engine.disconnect(); this.doConnectDirectivesWs && this.doConnectDirectivesWs.close() uni.reLaunch({ url: '/pages/index/index' }); }, // 不触碰屏幕显示退出链接弹窗 noOperationSetTimeout(key) { if (!this.isFiringNoOperationSetTimeout) return clearTimeout(noOperationSetTimeoutTimeInterval) if (key === 'cancel') { clearInterval(noOperationSetIntervalTimeInterval) this.noOperationSetTimeoutTimeVisible = false this.noOperationSetTimeout() return } noOperationSetTimeoutTimeInterval = setTimeout(() => { let index = 9 this.confirmButtonText = '退出(10秒)' this.noOperationSetTimeoutTimeVisible = true noOperationSetIntervalTimeInterval = setInterval(() => { this.confirmButtonText = `退出${index ? `(${index}秒)` : ''}` index-- if (index < 0) { this.noOperationSetTimeout('cancel') this.exit() } }, 1000) }, 300000) }, // 获取云机剩余时长 async getResidueTime() { clearInterval(countdownTimeInterval) const { userCardType, isShowCountdown, isShowRule } = this.parametersData const { userCardId } = this.userCardInfoData if (![1, 2, 3].includes(+userCardType)) return const res = await request.get(`/api/resources/yearMember/getResidueTime?userCardId=${userCardId}`) let time = res.data; if (!res.status) { this.countdownTime = residueTimeStamp(time) await request.get(`/api/resources/yearMember/startTime?userCardId=${userCardId}`) if (+isShowCountdown) this.timingVisible = true if (+isShowRule) this.billingRulesVisible = true countdownTimeInterval = setInterval(() => { if (time <= 0) { clearInterval(countdownTimeInterval) this.downline() return } time-- this.countdownTime = residueTimeStamp(time) }, 1000) } }, // 关闭倒计时弹窗 handlecountdownTimeClose() { const { userCardId } = this.userCardInfoData request.get(`/api/resources/yearMember/closeRemind?userCardId=${userCardId}`).then(res => { if (!res.status) { clearInterval(countdownTimeInterval) this.timingVisible = false return } Toast(res.msg); }) }, // 退出并下机 downline(fun = () => { }) { const { userCardId } = this.userCardInfoData request.get(`/api/resources/yearMember/downline?userCardId=${userCardId}`).then(res => { if (!res.status) { fun(true) // 通信给h5项目告知是退出并下机 parent.postMessage( { type: 'exit', }, '*', ); uni.postMessage({ data: { type: 'exit' } }); this.exit() return } fun(false) Toast(res.msg); }) }, // 获取推荐列表 getRecommend() { const { userCardId } = this.userCardInfoData request.get(`/api/public/v1/market/get/recommend?userCardId=${userCardId}`).then(res => { if (!res.status) { this.billingRulesVisible = false this.recommendList = res.data this.recommendList.length && (this.applyRecommendVisible = true) } }) }, // 下载apk downAndInstallApk({ downloadUrl: apkUrl, id: taskUid }) { this.doConnectDirectivesWs.send(JSON.stringify({ type: 'downAndInstallApk', data: { apkUrl, taskUid }, })) }, // 移开手指时会发生的回调 touchendRtcMediaPlayer(event) { this.engine.touchClick && this.engine.touchClick(event, 1, true) this.noOperationSetTimeout() }, // 返回、主页、任务器 footerBtnFun(key) { this.engine[key] && this.engine[key]() this.noOperationSetTimeout() }, // 获取初始化尺寸 getInitSize() { // 高度、悬浮球相关配置 this.height = window.innerHeight this.width = window.innerWidth this.$nextTick(() => { // 云机画面宽高 let layoutView = document.querySelector('.layout-view') this.layoutViewWidth = layoutView.offsetWidth this.layoutViewHeight = layoutView.offsetHeight }) }, // 音量 volumeControl(value) { this.engine.ExexuteKeyBoard && this.engine.ExexuteKeyBoard(value) this.$refs.rtcMediaPlayer && (this.$refs.rtcMediaPlayer.muted = false) } } }) // 获取URL参数 function getParameters() { let arr = location.search.split('?') let obj = {} if (arr[1]) { arr = arr[1].split('&') arr.forEach(item => { let [key, value = ''] = item.split('=') obj[key] = value }) } return obj } // 倒计时处理的时间 function residueTimeStamp(value) { let theTime = value;//秒 let middle = 0;//分 let hour = 0;//小时 if (theTime > 59) { middle = parseInt(theTime / 60); theTime = parseInt(theTime % 60); } if (middle > 59) { hour = parseInt(middle / 60); middle = parseInt(middle % 60); } theTime < 10 ? theTime = '0' + theTime : theTime = theTime middle < 10 ? middle = '0' + middle : middle = middle hour < 10 ? hour = '0' + hour : hour = hour return hour + ':' + middle + ':' + theTime }