WXtrialInterface.js 41 KB

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