WXtrialInterface.js 41 KB

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