WXtrialInterface.js 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. import request from './request.js'
  2. import { clickCopyText, pasteText } from './common.js'
  3. import logReport from './logReport.js'
  4. // 禁止双击缩放
  5. document.addEventListener('dblclick', function (e) {
  6. e.preventDefault();
  7. });
  8. const { Dialog, Toast } = vant
  9. Toast.setDefaultOptions({ duration: 2000 });
  10. // 从 CLOUD_GAME_SDK 结构中解构必要的函数和常量
  11. const RtcEngineSDK = window.rtc_sdk.CloudGameSdk
  12. // 业务通道定时标识
  13. let doConnectDirectivesIntervalerPing = null
  14. // 获取云机数据定时标识
  15. let getUserCardInfoTimerInterval = null
  16. let getUserCardInfoRequestNum = 1
  17. let doConnectDirectivesRequestNum = 1
  18. let doConnectDirectivesTimerInterval = null
  19. // 触碰间隔定时标识
  20. let noOperationSetTimeoutTimeInterval = null
  21. let noOperationSetIntervalTimeInterval = null
  22. // 倒计时定时标识
  23. let countdownTimeInterval = null
  24. // 日志上报实例
  25. let logReportObj = null;
  26. const app = new Vue({
  27. el: '#app',
  28. data: {
  29. // 底部按钮
  30. footerBtn: [{
  31. key: 'task',
  32. img: '../static/img/wx/gengduo_icon.png'
  33. }, {
  34. key: 'home',
  35. img: '../static/img/wx/home_icon.png'
  36. }, {
  37. key: 'onBack',
  38. img: '../static/img/wx/fanhui_icon.png'
  39. }],
  40. // 宽高
  41. width: 0,
  42. height: 0,
  43. // webRtc实例
  44. engine: {},
  45. // 横竖屏幕 false是竖
  46. isLandscape: false,
  47. // 悬浮球位置
  48. levitatedSpherePositionData: {},
  49. // 右侧弹窗
  50. levitatedSphereVisible: false,
  51. // 清晰度数据
  52. definitionList: [{
  53. name: '高清',
  54. value: 2800
  55. }, {
  56. name: '标清',
  57. value: 2200,
  58. }, {
  59. name: '极速',
  60. value: 1800,
  61. }],
  62. // 选中的清晰度
  63. definitionValue: '',
  64. // 分辨率
  65. resolutionRatioVisible: false,
  66. resolutionRatioList: [],
  67. // 需要用到的参数
  68. parametersData: {},
  69. // 屏幕分辨率
  70. phoneSize: {},
  71. // 业务指令通道实例
  72. doConnectDirectivesWs: null,
  73. // 粘贴版
  74. pasteVersionVisible: false,
  75. pasteVersionList: [],
  76. // 复制内容
  77. copyTextValue: '',
  78. copyTextVisible: false,
  79. // 卡数据
  80. userCardInfoData: {},
  81. // 是否显示计时
  82. timingVisible: false,
  83. // 计费规则
  84. billingRulesVisible: false,
  85. applyRecommendVisible: false,
  86. // 是否是启动不操作自动退出云机功能
  87. isFiringNoOperationSetTimeout: false,
  88. // 超过指定触碰时间的弹窗
  89. noOperationSetTimeoutTimeVisible: false,
  90. // 超过指定触碰时间的弹窗文案
  91. confirmButtonText: '',
  92. // 云机剩余时长
  93. countdownTime: 0,
  94. // 是否支持webRTC
  95. isSupportRtc: !!(
  96. typeof RTCPeerConnection !== 'undefined' &&
  97. typeof RTCIceCandidate !== 'undefined' &&
  98. typeof RTCSessionDescription !== 'undefined'
  99. ),
  100. // 推荐列表
  101. recommendList: [],
  102. layoutViewWidth: null,
  103. layoutViewHeight: null,
  104. // 是否显示video
  105. isShowVideo: false,
  106. plugFlowStartTime: null,
  107. obtainCardInfoStartTime: null
  108. },
  109. created() {
  110. this.initConfig()
  111. },
  112. mounted() {
  113. // 初始化日志上报
  114. this.initLogReport();
  115. this.getUserCardInfo();
  116. },
  117. computed: {
  118. // 右侧弹框退出相关按钮
  119. exitList() {
  120. let arr = [{
  121. name: '剪贴版',
  122. key: "shearplate",
  123. img: '../static/img/wx/jianqieban_icon.png'
  124. }, {
  125. name: '退出',
  126. key: 'signout',
  127. img: '../static/img/wx/tuichu_icon.png'
  128. }]
  129. if ([1, 2, 3].includes(+this.parametersData.userCardType)) {
  130. arr.push({
  131. name: '退出并下机',
  132. key: 'dormant',
  133. img: '../static/img/wx/tuichu_icon.png'
  134. })
  135. }
  136. return arr
  137. },
  138. rtcMediaPlayerStyle() {
  139. let obj = {
  140. objectFit: "fill",
  141. width: `${this.layoutViewWidth}px`,
  142. height: `${this.layoutViewHeight}px`,
  143. }
  144. if (this.isLandscape) {
  145. obj = {
  146. width: `${this.layoutViewHeight}px`,
  147. height: `${this.layoutViewWidth}px`,
  148. // left: '50%',
  149. // top: '50%',
  150. // transform: 'translate(-50%, -50%) rotate(90deg)'
  151. transform: 'rotate(90deg)'
  152. }
  153. }
  154. return obj
  155. }
  156. },
  157. methods: {
  158. // 初始化
  159. initConfig() {
  160. // 获取窗口尺寸
  161. this.getInitSize()
  162. let levitatedSpherePositionData = localStorage.getItem('levitatedSpherePositionData')
  163. let definitionValue = localStorage.getItem('definitionValue')
  164. // 悬浮球位置
  165. this.levitatedSpherePositionData = levitatedSpherePositionData ? JSON.parse(levitatedSpherePositionData) : { right: '15px', top: '15px' }
  166. // 清晰度
  167. this.definitionValue = definitionValue ? +definitionValue : 2200
  168. // 获取参数
  169. this.parametersData = getParameters()
  170. let { token, validTime, merchantSign } = this.parametersData
  171. // 给api增加需要的参数
  172. request.defaults.headers.Authorization = token
  173. request.defaults.headers.versionname = '5.9.1'
  174. // 获取商户标识, 打开云机时必传, 用于日志上报
  175. request.defaults.headers.merchantSign = merchantSign;
  176. window.onresize = () => {
  177. this.getInitSize()
  178. }
  179. },
  180. // 连接webRTC
  181. connectWebRtc() {
  182. // console.time('获取推流响应消耗时间:')
  183. this.plugFlowStartTime = +new Date()
  184. const { sn: topic, cardToken: authToken, internetHttps, internetHttp, webrtcNetwork, webrtcTransferCmnet, webrtcTransferTelecom, webrtcTransferUnicom } = this.userCardInfoData;
  185. const isWss = location.protocol === 'https:'
  186. const url = `${isWss ? 'wss://' : 'ws://'}${isWss ? internetHttps : internetHttp}/nats`;
  187. const ICEServerUrl = [
  188. // { "CMNET": webrtcTransferCmnet || '' }, // 移动
  189. // { 'CHINANET-GD': webrtcTransferTelecom || '' }, // 电信
  190. // { 'UNICOM-GD': webrtcTransferUnicom || '' }, // 联通
  191. // webrtc三网地址遗弃, 统一使用三网解析地址
  192. { "CMNET": webrtcNetwork }, // 移动
  193. { 'CHINANET-GD': webrtcNetwork }, // 电信
  194. { 'UNICOM-GD': webrtcNetwork }, // 联通
  195. ];
  196. const connection = {
  197. name: "猪猪令是猪",
  198. topic, // SN号 必填
  199. url, //信令服务地址 必填
  200. ICEServerUrl,
  201. width: 720, // 推流视频宽度 必填
  202. height: 1280, // 推流视频高度 必填
  203. // cardWidth: this.phoneSize.width || 1080, // 云机系统分辨率 宽 必填
  204. // cardHeight: this.phoneSize.height || 1920, // 云机系统分辨率 高 必填
  205. // cardDensity: this.phoneSize.dpi || 480, // 云机系统显示 密度 必填
  206. cardWidth: 0, // 云机系统分辨率 宽 必填
  207. cardHeight: 0, // 云机系统分辨率 高 必填
  208. cardDensity: 0, // 云机系统显示 密度 必填
  209. authToken, //拉流鉴权 token 必填
  210. bitrate: 6000, //码率 必填
  211. fps: 30, //必填
  212. isAllowedOpenCamera: true, // 是否允许打开摄像头
  213. callback: this.statusCallBack,//回调函数 必填
  214. };
  215. // 设置日志参数 推流质量
  216. logReportObj.setParams({imageQuality: 6000});
  217. // 初始化 SDK
  218. this.engine = new RtcEngineSDK(connection);
  219. this.engine.RtcEngine()
  220. },
  221. // webRTC状态回调
  222. statusCallBack(event) {
  223. if (event.type !== 'StreamStates') {
  224. // console.log("链接的状态", event, event.val);
  225. }
  226. switch (event.type) {
  227. case "screenChange":
  228. // 0:横屏 1:竖屏
  229. console.log("屏幕方向变化事件:" + event.val);
  230. this.isLandscape = event.val === 0;
  231. break;
  232. case "wsState":
  233. // “TIMEOUT”:nats链接超时
  234. // success 链接成功
  235. const status = ["TIMEOUT", "failed"]
  236. if (status.includes(event.val)) {
  237. // 设置日志 推流状态为失败
  238. logReportObj.setParams({plugFowStatus: 2, linkWay: 0, linkEndTime: logReportObj.formatDate(new Date())});
  239. // 日志上报
  240. logReportObj.collectLog(
  241. `信令服务地址nats失败:
  242. url: ${this.engine.options.url}
  243. 消息: ${JSON.stringify(event)}`
  244. );
  245. Dialog.alert({
  246. title: '提示',
  247. message: '链接超时',
  248. confirmButtonText: '确定',
  249. confirmButtonColor: '#3cc51f',
  250. beforeClose: (action, done) => {
  251. this.exit()
  252. done()
  253. }
  254. })
  255. }
  256. break;
  257. case "rtcState":
  258. // “connected”:rtc链接成功 “failed”:rtc链接失败 “closed”:rtc链接关闭 “disconnected”:rtc链接超时
  259. // console.log(event.val, 'rtc链接成功状态')
  260. if (event.val === "connected") {
  261. // Toast.clear();
  262. // this.doConnectDirectives()
  263. // this.isShowVideo = true
  264. // this.$refs.rtcMediaPlayer.play()
  265. // let ms = +new Date - this.plugFlowStartTime
  266. // console.timeEnd('获取推流响应消耗时间:')
  267. // console.log(`获取推流响应消耗时间:${ms / 1000}秒`)
  268. // this.definitionFun(this.definitionValue)
  269. // this.pushflowPopup()
  270. // this.getResidueTime()
  271. // 设置日志 推流状态为成功
  272. let now = new Date();
  273. logReportObj.setParams({plugFowStatus: 1, linkWay: 1, timeConsuming: now.getTime() - logReportObj.timeStartTime, linkEndTime: logReportObj.formatDate(now)});
  274. // 日志上报
  275. logReportObj.collectLog(`推流成功`);
  276. playOnBtn.bind(this)()
  277. return
  278. }
  279. if (event.val === "connecting") return
  280. if (event.val === 'failed') {
  281. // 设置日志 推流状态为失败
  282. logReportObj.setParams({plugFowStatus: 2, linkWay: 1, linkEndTime: logReportObj.formatDate(new Date())});
  283. // 日志上报
  284. logReportObj.collectLog(
  285. `rtc链接失败:
  286. 消息: ${JSON.stringify(event)}`
  287. );
  288. }
  289. if(event.val === 'disconnected') {
  290. // 设置日志 推流状态为失败
  291. logReportObj.setParams({plugFowStatus: 2, linkWay: 1, linkEndTime: logReportObj.formatDate(new Date())});
  292. // 日志上报
  293. logReportObj.collectLog(
  294. `rtc链接超时:
  295. 消息: ${JSON.stringify(event)}`
  296. );
  297. }
  298. this.exit()
  299. break;
  300. case "AuthenticationStatus":
  301. // console.log(`鉴权${event.val === "success" ? '成功' : '失败'}`);
  302. if (event.val !== "success") {
  303. // 日志上报
  304. logReportObj.collectLog(
  305. `rtc鉴权失败:
  306. 消息: ${JSON.stringify(event)}`
  307. );
  308. }
  309. break;
  310. case "StreamStates":
  311. // “currentRoundTripTime”:延迟 “lostRate”:丢包率 “seconds_KBytes”:带宽 “framesPerSecond”:帧率
  312. // ballPosition.value = event.val
  313. // console.log(event.val)
  314. break;
  315. case 'videoResolution': // 云机视频分辨率
  316. // console.log(event.val, '分辨率')
  317. break
  318. case 'networkService': // 获取三网信息
  319. // console.log("三网信息:", event.val);
  320. break
  321. case 'networkServiceURL': // 获取三网信息
  322. // console.log("三网IP地址:", event.val);
  323. break
  324. }
  325. },
  326. // 悬浮球移动
  327. touchmoveLevitatedSphere(e) {
  328. let { pageX, pageY } = e.targetTouches[0]
  329. let min = 20
  330. let MaxPageX = this.width - 20
  331. let MaxPageY = this.height - 20
  332. pageX = pageX <= min ? min : (pageX >= MaxPageX ? MaxPageX : pageX)
  333. pageY = pageY <= min ? min : (pageY >= MaxPageY ? MaxPageY : pageY)
  334. this.levitatedSpherePositionData = {
  335. left: `${pageX}px`,
  336. top: `${pageY}px`,
  337. transform: 'translate(-50%, -50%)'
  338. }
  339. },
  340. touchendLevitatedSphere(e) {
  341. localStorage.setItem('levitatedSpherePositionData', JSON.stringify(this.levitatedSpherePositionData))
  342. },
  343. // 清晰度
  344. definitionFun(value) {
  345. this.definitionValue = value
  346. this.engine.makeBitrate && this.engine.makeBitrate(value)
  347. // 设置日志参数 推流质量
  348. logReportObj.setParams({imageQuality: value});
  349. localStorage.setItem('definitionValue', value)
  350. this.levitatedSphereVisible = false
  351. },
  352. // 修改分辨率
  353. resolutionRatio() {
  354. request.get('/api/resources/v5/machine/resolution/getResolvingPower', { params: { userCardId: this.parametersData.userCardId } }).then(res => {
  355. if (res.success) {
  356. this.resolutionRatioList = res.data.map(item => {
  357. item.height = item.high
  358. return item
  359. })
  360. this.levitatedSphereVisible = false
  361. this.resolutionRatioVisible = true
  362. }
  363. })
  364. },
  365. // 确定修改分辨率
  366. confirmResolution() {
  367. let { width, height, dpi: density } = this.phoneSize
  368. this.engine.makeResolution && this.engine.makeResolution({ width, height, density })
  369. this.resolutionRatioVisible = false
  370. },
  371. // 退出相关按钮操作
  372. exitFun(key) {
  373. switch (key) {
  374. case 'dormant':
  375. Dialog.alert({
  376. title: '提示',
  377. message: '确定退出云手机并下机',
  378. confirmButtonText: '确定',
  379. confirmButtonColor: '#3cc51f',
  380. showCancelButton: true,
  381. beforeClose: (action, done) => {
  382. if (action === 'cancel') done()
  383. if (action === 'confirm') {
  384. this.downline(done)
  385. }
  386. }
  387. })
  388. break;
  389. case 'shearplate':
  390. this.copyTextValue = ''
  391. pasteText().then(content => {
  392. typeof content !== 'boolean' ? this.openPasteboard(content) : this.copyTextVisible = true
  393. }, err => {
  394. this.copyTextVisible = true
  395. })
  396. break;
  397. case 'signout':
  398. this.exit()
  399. break;
  400. }
  401. },
  402. // 业务指令
  403. doConnectDirectives() {
  404. let { internetHttps, internetHttp, localIp, cardToken } = this.userCardInfoData
  405. const isWss = location.protocol === 'https:'
  406. let cUrl = `${isWss ? 'wss' : 'ws'}://${isWss ? internetHttps : internetHttp}/businessChannel?cardIp=${localIp}&token=${cardToken}&type=directives`
  407. this.doConnectDirectivesWs = new WebSocket(cUrl);
  408. this.doConnectDirectivesWs.binaryType = 'arraybuffer'
  409. clearInterval(doConnectDirectivesIntervalerPing)
  410. // 链接成功
  411. this.doConnectDirectivesWs.onopen = (e) => {
  412. doConnectDirectivesIntervalerPing = setInterval(() => {
  413. if (this.doConnectDirectivesWs.readyState === 1) {
  414. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'ping' }));
  415. } else {
  416. clearInterval(doConnectDirectivesIntervalerPing);
  417. }
  418. }, 3000)
  419. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'getVsStatus' }))
  420. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'bitRate', data: { bitRate: 1243000 } }))
  421. // 设置日志参数 推流质量
  422. logReportObj.setParams({imageQuality: 1243000});
  423. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'InputMethod', data: { type: 2 } }))
  424. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'getPhoneSize' }))
  425. }
  426. // 接受到的消息
  427. this.doConnectDirectivesWs.onmessage = res => {
  428. const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
  429. switch (result.type) {
  430. // 分辨率
  431. case 'getPhoneSize':
  432. case 'setPhoneSize':
  433. let data = JSON.parse(JSON.stringify(result.data))
  434. let { width, height } = data
  435. if (width > height) {
  436. data.width = height
  437. data.height = width
  438. }
  439. this.phoneSize = data
  440. break
  441. // 云机复制过来的文本
  442. case 'reProduceText':
  443. if (navigator.clipboard) {
  444. navigator.clipboard.writeText(result.data.text);
  445. }
  446. break
  447. case 'downAdnInstallRep':
  448. Toast(result.data.msg)
  449. break
  450. // 接受到这个消息就自动退出云机
  451. case 'exitPhone':
  452. this.exit()
  453. break
  454. }
  455. }
  456. // 链接报错的回调
  457. this.doConnectDirectivesWs.onerror = res => {
  458. // 设置日志 推流状态为失败
  459. logReportObj.setParams({plugFowStatus: 1, linkWay: 0, linkEndTime: logReportObj.formatDate(new Date())});
  460. // 日志上报
  461. logReportObj.collectLog(
  462. `业务指令通道报错:
  463. url: ${res.target.url}
  464. type: ${res.type}
  465. 消息: ${JSON.stringify(res)}`
  466. );
  467. clearInterval(doConnectDirectivesTimerInterval)
  468. if (doConnectDirectivesRequestNum > 6) {
  469. this.exit()
  470. return
  471. }
  472. doConnectDirectivesRequestNum++
  473. this.doConnectDirectives()
  474. }
  475. },
  476. // 粘贴版相关接口
  477. shearContent({ type, params, queryStr }) {
  478. let url = '/api/public/v5/shear/content'
  479. if (queryStr) url += queryStr
  480. return request[type](url, params)
  481. },
  482. // 清空全部、清除某条
  483. deletePasteVersion(ids) {
  484. if (!ids) {
  485. Dialog.alert({
  486. title: '提示',
  487. message: '确定清空剪贴板?',
  488. confirmButtonText: '确定',
  489. confirmButtonColor: '#3cc51f',
  490. showCancelButton: true,
  491. beforeClose: (action, done) => {
  492. if (action === 'cancel') done()
  493. if (action === 'confirm') {
  494. fun.bind(this)(done)
  495. }
  496. }
  497. })
  498. return
  499. }
  500. fun.bind(this)()
  501. function fun(callBack = () => { }) {
  502. this.shearContent({
  503. type: 'delete', queryStr: Qs.stringify(
  504. {
  505. ids: ids ? [ids] : this.pasteVersionList.map((v) => v.id),
  506. },
  507. { arrayFormat: 'repeat', addQueryPrefix: true },
  508. )
  509. }).then(res => {
  510. if (res.status === 0) {
  511. this.getPasteVersion()
  512. callBack(true)
  513. } else {
  514. callBack(false)
  515. Toast(res.msg)
  516. }
  517. }).catch(() => {
  518. callBack(false)
  519. })
  520. }
  521. },
  522. // 获取粘贴版数据
  523. getPasteVersion(callBack = () => { }) {
  524. this.shearContent({ type: 'get' }).then(res => {
  525. this.pasteVersionList = res.data
  526. callBack(true)
  527. }).catch(() => {
  528. callBack(false)
  529. }).finally(() => { })
  530. },
  531. // 复制弹窗是否关闭
  532. beforeCloseCopy(action, done) {
  533. if (action !== 'confirm') {
  534. // 获取剪切板
  535. this.getPasteVersion(() => {
  536. this.pasteVersionVisible = true
  537. this.levitatedSphereVisible = false
  538. done()
  539. })
  540. return
  541. }
  542. if (!this.copyTextValue) {
  543. Toast('请输入复制到剪切板的内容')
  544. done(false)
  545. return
  546. }
  547. this.openPasteboard(this.copyTextValue, done)
  548. },
  549. // 打开粘贴板
  550. async openPasteboard(content, callBack = () => { }) {
  551. this.shearContent({ type: 'post', params: { content } }).then().finally(() => {
  552. callBack()
  553. // 获取剪切板
  554. this.getPasteVersion(() => {
  555. this.pasteVersionVisible = true
  556. this.levitatedSphereVisible = false
  557. })
  558. })
  559. },
  560. // 复制粘贴某条数据
  561. copyPasteVersiontext(e) {
  562. clickCopyText(e, (event) => {
  563. this.doConnectDirectivesWs.send(JSON.stringify({
  564. type: 'cutting',
  565. data: {
  566. str: event.text,
  567. },
  568. }))
  569. Toast('复制成功')
  570. }, () => {
  571. Toast('复制失败')
  572. })
  573. },
  574. // 获取卡信息
  575. getUserCardInfo() {
  576. Toast.loading({
  577. duration: 0, // 持续展示 toast
  578. message: '数据加载中...',
  579. });
  580. this.obtainCardInfoStartTime = + new Date()
  581. let { userCardId } = this.parametersData
  582. userCardId = +userCardId
  583. const statusTips = {
  584. // 5200:RBD资源挂载中
  585. 5200: '网络异常,请稍后重试',
  586. // 入使用排队9.9,年卡
  587. 5220: '云手机正在一键修复中',
  588. 5203: '正在排队中,请稍等',
  589. // 9.9年卡连接异常,重新进入排队
  590. 5204: '云机异常,正在为你重新分配云机',
  591. 5228: '卡的网络状态为差',
  592. 5229: '接口返回链接信息缺失',
  593. }
  594. let { isWeixin } = this.parametersData;
  595. let clientType = +isWeixin ? 'wx' : undefined;
  596. // 设置上报参数
  597. logReportObj.setParams({userCardId});
  598. clientType && logReportObj.setParams({clientType});
  599. request.post('/api/resources/user/cloud/connect', { userCardId }).then(async res => {
  600. try {
  601. if (!res.success) {
  602. // 设置日志 推流状态为失败,和链接状态
  603. logReportObj.setParams({plugFowStatus: 2, linkWay: logReportObj.RESPONSE_CODE[res.status] || 0, linkEndTime: logReportObj.formatDate(new Date())});
  604. // 日志上报
  605. logReportObj.collectLog(
  606. `接口获取数据失败:
  607. url: /api/resources/user/cloud/connect
  608. method: post
  609. 参数: ${JSON.stringify({ userCardId })}
  610. 响应: ${JSON.stringify(res)}`
  611. );
  612. return;
  613. }
  614. const data = res.data;
  615. // 设置上报参数
  616. logReportObj.setParams({videoType: data.videoCode.toLowerCase(), resourceId: data.resourceId});
  617. switch (res.status) {
  618. case 0:
  619. getUserCardInfoRequestNum = 1
  620. // 不支持webRTC跳转到指定的页面
  621. if (!res.data.isWebrtc) {
  622. // 关闭日志上报
  623. logReportObj.destroy();
  624. // 跳转指定页面
  625. location.replace(`${location.origin}/h5/webRtcYJ/WXtrialInterface.html${location.search}`)
  626. return
  627. }
  628. if (!this.isSupportRtc) {
  629. // 设置日志 推流状态为失败
  630. logReportObj.setParams({plugFowStatus: 2, linkEndTime: logReportObj.formatDate(new Date())});
  631. // 日志上报
  632. logReportObj.collectLog(`${+isWeixin ? '微信小程序' : ''}当前版本暂不支持使用`);
  633. Dialog.alert({
  634. title: '提示',
  635. message: `${+isWeixin ? '微信小程序' : ''}当前版本暂不支持使用,可下载谷歌浏览器或客户端进行使用`,
  636. confirmButtonText: '确定',
  637. confirmButtonColor: '#3cc51f',
  638. beforeClose: (action, done) => {
  639. this.exit()
  640. done()
  641. }
  642. })
  643. return
  644. }
  645. // webRtc连接,需获取连接中转地址
  646. if (res.data.webrtcNetworkAnalysis) {
  647. // 如果有网络分析的请求地址, 则请求,否则失败
  648. const webrtcNetworkAnalysisReq = await request.get(res.data.webrtcNetworkAnalysis);
  649. if (webrtcNetworkAnalysisReq !== null && webrtcNetworkAnalysisReq.success) {
  650. if (webrtcNetworkAnalysisReq.data) {
  651. // 保存获取的连接地址到上个请求的响应中, 方便后面使用
  652. res.data.webrtcNetwork = webrtcNetworkAnalysisReq.data;
  653. // 设置上报参数
  654. logReportObj.setParams({transferServerIp: webrtcNetworkAnalysisReq.data});
  655. }else{
  656. // 设置上报参数
  657. logReportObj.setParams({linkWay: 4, plugFowStatus: 2});
  658. // 日志上报
  659. logReportObj.collectLog(
  660. `webRtc连接,获取中转地址成功,但返回数据为空:
  661. url: ${res.data.webrtcNetworkAnalysis}
  662. method: get
  663. 参数: 无
  664. 响应: ${JSON.stringify(webrtcNetworkAnalysisReq)}`
  665. );
  666. }
  667. }else{
  668. // 设置上报参数
  669. logReportObj.setParams({linkWay: 4, plugFowStatus: 2, linkEndTime: logReportObj.formatDate(new Date())});
  670. // 日志上报
  671. logReportObj.collectLog(
  672. `webRtc连接,获取中转地址失败:
  673. url: ${res.data.webrtcNetworkAnalysis}
  674. method: get
  675. 参数: 无
  676. 响应: ${JSON.stringify(webrtcNetworkAnalysisReq)}`
  677. );
  678. }
  679. }else{
  680. // 设置上报参数
  681. logReportObj.setParams({linkWay: 4, plugFowStatus: 2});
  682. // 日志上报
  683. logReportObj.collectLog(
  684. `webRtc连接,获取请求中转地址为空:
  685. url: /api/resources/user/cloud/connect
  686. method: post
  687. 参数: ${JSON.stringify({ userCardId })}
  688. 响应: ${JSON.stringify(res)}`
  689. );
  690. }
  691. // 如果没有获取到连接地址, 则失败
  692. if(!res.data.webrtcNetwork) {
  693. Dialog.alert({
  694. title: '提示',
  695. message: '访问失败,请稍后重试',
  696. confirmButtonText: '确定',
  697. confirmButtonColor: '#3cc51f',
  698. beforeClose: (action, done) => {
  699. this.exit()
  700. done()
  701. }
  702. })
  703. return;
  704. }
  705. this.userCardInfoData = res.data
  706. // let ms = + new Date() - this.obtainCardInfoStartTime
  707. this.connectWebRtc()
  708. return
  709. case 5200:
  710. case 5220:
  711. case 5203:
  712. case 5204:
  713. if (res.status === 5200) {
  714. reconnect.bind(this)()
  715. return
  716. }
  717. Toast(statusTips[res.status])
  718. break
  719. default:
  720. // 设置上报参数
  721. logReportObj.setParams({linkWay: logReportObj.RESPONSE_CODE[res.status] || 0, linkEndTime: logReportObj.formatDate(new Date())});
  722. // 日志上报
  723. logReportObj.collectLog(statusTips[res.status] ||
  724. `webRtc连接,获取请求中转地址返回异常:
  725. url: /api/resources/user/cloud/connect
  726. method: post
  727. 参数: ${JSON.stringify({ userCardId })}
  728. 响应: ${JSON.stringify(res)}`
  729. );
  730. Toast('画面异常,请重新进入')
  731. break
  732. }
  733. setTimeout(() => {
  734. this.exit()
  735. }, 3000)
  736. } catch (error) {
  737. }
  738. }).catch((error) => {
  739. // 设置上报参数
  740. logReportObj.setParams({linkWay: 4, plugFowStatus: 2, linkEndTime: logReportObj.formatDate(new Date())});
  741. // 日志上报
  742. logReportObj.collectLog(
  743. `接口获取数据失败:
  744. url: /api/resources/user/cloud/connect
  745. method: post
  746. 参数: ${JSON.stringify({ userCardId })}
  747. 响应: ${JSON.stringify(error)}`
  748. );
  749. })
  750. // 重连
  751. function reconnect() {
  752. // 重连6次结束
  753. if (getUserCardInfoRequestNum > 6) {
  754. Toast('网络异常,请稍后重试')
  755. clearTimeout(getUserCardInfoTimerInterval)
  756. setTimeout(() => {
  757. this.exit()
  758. }, 3000)
  759. return
  760. }
  761. // 重连
  762. getUserCardInfoTimerInterval = setTimeout(() => {
  763. this.getUserCardInfo()
  764. getUserCardInfoRequestNum++
  765. }, 3000)
  766. }
  767. },
  768. // 超过指定触碰时间是否提示关闭链接
  769. pushflowPopup() {
  770. request.get('/api/public/v5/pushflow/popup').then(res => {
  771. if (res.success) {
  772. this.isFiringNoOperationSetTimeout = res.data
  773. this.noOperationSetTimeout()
  774. }
  775. })
  776. },
  777. // 退出功能
  778. exit() {
  779. // 关闭日志上报
  780. logReportObj.destroy();
  781. this.engine.disconnect && this.engine.disconnect();
  782. this.doConnectDirectivesWs && this.doConnectDirectivesWs.close()
  783. uni.reLaunch({
  784. url: '/pages/index/index'
  785. });
  786. },
  787. // 不触碰屏幕显示退出链接弹窗
  788. noOperationSetTimeout(key) {
  789. if (!this.isFiringNoOperationSetTimeout) return
  790. clearTimeout(noOperationSetTimeoutTimeInterval)
  791. if (key === 'cancel') {
  792. clearInterval(noOperationSetIntervalTimeInterval)
  793. this.noOperationSetTimeoutTimeVisible = false
  794. this.noOperationSetTimeout()
  795. return
  796. }
  797. noOperationSetTimeoutTimeInterval = setTimeout(() => {
  798. let index = 9
  799. this.confirmButtonText = '退出(10秒)'
  800. this.noOperationSetTimeoutTimeVisible = true
  801. noOperationSetIntervalTimeInterval = setInterval(() => {
  802. this.confirmButtonText = `退出${index ? `(${index}秒)` : ''}`
  803. index--
  804. if (index < 0) {
  805. this.noOperationSetTimeout('cancel')
  806. this.exit()
  807. }
  808. }, 1000)
  809. }, 300000)
  810. },
  811. // 获取云机剩余时长
  812. async getResidueTime() {
  813. clearInterval(countdownTimeInterval)
  814. const { userCardType, isShowCountdown, isShowRule } = this.parametersData
  815. const { userCardId } = this.userCardInfoData
  816. if (![1, 2, 3].includes(+userCardType)) return
  817. const res = await request.get(`/api/resources/yearMember/getResidueTime?userCardId=${userCardId}`)
  818. let time = res.data;
  819. if (!res.status) {
  820. this.countdownTime = residueTimeStamp(time)
  821. await request.get(`/api/resources/yearMember/startTime?userCardId=${userCardId}`)
  822. if (+isShowCountdown) this.timingVisible = true
  823. if (+isShowRule) this.billingRulesVisible = true
  824. countdownTimeInterval = setInterval(() => {
  825. if (time <= 0) {
  826. clearInterval(countdownTimeInterval)
  827. this.downline()
  828. return
  829. }
  830. time--
  831. this.countdownTime = residueTimeStamp(time)
  832. }, 1000)
  833. }
  834. },
  835. // 关闭倒计时弹窗
  836. handlecountdownTimeClose() {
  837. const { userCardId } = this.userCardInfoData
  838. request.get(`/api/resources/yearMember/closeRemind?userCardId=${userCardId}`).then(res => {
  839. if (!res.status) {
  840. clearInterval(countdownTimeInterval)
  841. this.timingVisible = false
  842. return
  843. }
  844. Toast(res.msg);
  845. })
  846. },
  847. // 退出并下机
  848. downline(fun = () => { }) {
  849. const { userCardId } = this.userCardInfoData
  850. request.get(`/api/resources/yearMember/downline?userCardId=${userCardId}`).then(res => {
  851. if (!res.status) {
  852. fun(true)
  853. // 通信给h5项目告知是退出并下机
  854. parent.postMessage(
  855. {
  856. type: 'exit',
  857. },
  858. '*',
  859. );
  860. uni.postMessage({
  861. data: {
  862. type: 'exit'
  863. }
  864. });
  865. this.exit()
  866. return
  867. }
  868. fun(false)
  869. Toast(res.msg);
  870. })
  871. },
  872. // 获取推荐列表
  873. getRecommend() {
  874. const { userCardId } = this.userCardInfoData
  875. request.get(`/api/public/v1/market/get/recommend?userCardId=${userCardId}`).then(res => {
  876. if (!res.status) {
  877. this.billingRulesVisible = false
  878. this.recommendList = res.data
  879. this.recommendList.length && (this.applyRecommendVisible = true)
  880. }
  881. })
  882. },
  883. // 下载apk
  884. downAndInstallApk({ downloadUrl: apkUrl, id: taskUid }) {
  885. this.doConnectDirectivesWs.send(JSON.stringify({
  886. type: 'downAndInstallApk',
  887. data: {
  888. apkUrl,
  889. taskUid
  890. },
  891. }))
  892. },
  893. // 移开手指时会发生的回调
  894. touchendRtcMediaPlayer(event) {
  895. this.engine.touchClick && this.engine.touchClick(event, 1, true)
  896. this.noOperationSetTimeout()
  897. },
  898. // 返回、主页、任务器
  899. footerBtnFun(key) {
  900. this.engine[key] && this.engine[key]()
  901. this.noOperationSetTimeout()
  902. },
  903. // 获取初始化尺寸
  904. getInitSize() {
  905. // 高度、悬浮球相关配置
  906. this.height = window.innerHeight
  907. this.width = window.innerWidth
  908. this.$nextTick(() => {
  909. // 云机画面宽高
  910. let layoutView = document.querySelector('.layout-view')
  911. // 获取视口宽度,webRTC需要做成16:9的画面
  912. let currentWidth = layoutView.clientWidth;
  913. let currentHeight = layoutView.clientHeight;
  914. // this.layoutViewWidth = layoutView.offsetWidth;
  915. // this.layoutViewHeight = layoutView.offsetHeight;
  916. // 计算当前视口的宽高比
  917. const currentRatio = currentWidth / currentHeight;
  918. // 9:16 的目标比例
  919. const targetRatio = 9 / 16;
  920. console.log(`当前视口的宽高比: ${currentRatio}`);
  921. // 判断当前视口的宽高比与目标比例的关系
  922. if (currentRatio > targetRatio) {
  923. // 当前视口的宽高比大于目标比例,说明宽度“过宽”,需要以高度为基准
  924. console.log("当前视口宽度过宽,应以高度为基准调整宽度");
  925. this.layoutViewWidth = currentHeight * targetRatio;
  926. this.layoutViewHeight = currentHeight;
  927. console.log(`1目标: 宽${this.layoutViewWidth},高${this.layoutViewHeight}`);
  928. } else {
  929. // 当前视口的宽高比小于目标比例,说明高度“过高”,需要以宽度为基准
  930. console.log("当前视口高度过高,应以宽度为基准调整高度");
  931. this.layoutViewHeight = currentWidth / targetRatio;
  932. this.layoutViewWidth = currentWidth;
  933. console.log(`2目标: 宽${this.layoutViewWidth},高${this.layoutViewHeight}`);
  934. }
  935. })
  936. },
  937. // 音量
  938. volumeControl(value) {
  939. this.engine.ExexuteKeyBoard && this.engine.ExexuteKeyBoard(value)
  940. this.$refs.rtcMediaPlayer && (this.$refs.rtcMediaPlayer.muted = false)
  941. },
  942. // 初始化日志上报实例
  943. initLogReport() {
  944. // 初始化日志上报实例
  945. logReportObj = new logReport({ request });
  946. uni.getEnv((res) => {
  947. // 设置上报参数
  948. logReportObj.setParams({clientType: Object.keys(res)[0]});
  949. })
  950. }
  951. }
  952. })
  953. function playOnBtn() {
  954. const { isTips } = this.parametersData;
  955. Dialog.alert({
  956. title: '提示',
  957. message: `${+isTips ? '开始' : '继续'}使用云手机`,
  958. confirmButtonText: '确定',
  959. confirmButtonColor: '#3cc51f',
  960. beforeClose: (action, done) => {
  961. if (action === 'confirm') {
  962. this.isShowVideo = true
  963. Toast.clear();
  964. this.$refs.rtcMediaPlayer.play()
  965. setTimeout(() => {
  966. this.doConnectDirectives()
  967. this.definitionFun(this.definitionValue)
  968. this.pushflowPopup()
  969. this.getResidueTime()
  970. done()
  971. })
  972. }
  973. }
  974. });
  975. }
  976. // 获取URL参数
  977. function getParameters() {
  978. let arr = location.search.split('?')
  979. let obj = {}
  980. if (arr[1]) {
  981. arr = arr[1].split('&')
  982. arr.forEach(item => {
  983. let [key, value = ''] = item.split('=')
  984. obj[key] = value
  985. })
  986. }
  987. return obj
  988. }
  989. // 倒计时处理的时间
  990. function residueTimeStamp(value) {
  991. let theTime = value;//秒
  992. let middle = 0;//分
  993. let hour = 0;//小时
  994. if (theTime > 59) {
  995. middle = parseInt(theTime / 60);
  996. theTime = parseInt(theTime % 60);
  997. }
  998. if (middle > 59) {
  999. hour = parseInt(middle / 60);
  1000. middle = parseInt(middle % 60);
  1001. }
  1002. theTime < 10 ? theTime = '0' + theTime : theTime = theTime
  1003. middle < 10 ? middle = '0' + middle : middle = middle
  1004. hour < 10 ? hour = '0' + hour : hour = hour
  1005. return hour + ':' + middle + ':' + theTime
  1006. }