rtc.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <template>
  2. <div align="center" class="rtc-page cover-bg" :style="{height: pageData.height + 'px'}">
  3. <!-- video 容器 -->
  4. <div class="video-wrapper" id="videoRef" :style="{width: pageData.videoWidth + 'px', height: pageData.videoHeight + 'px'}"></div>
  5. <!-- 三menu键 -->
  6. <div id="foot-menu-wrap" :style="`height: ${footMenuWrapHeight}px`">
  7. <div @click.stop="()=>{sendKey(187); noOperationSetTimeout}"><van-icon name="wap-nav" size="24px"/></div>
  8. <div @click.stop="()=>{sendKey(3); noOperationSetTimeout}"><van-icon name="wap-home-o" size="24px"/></div>
  9. <div @click.stop="()=>{sendKey(4); noOperationSetTimeout}"><van-icon name="arrow-left" size="24px"/></div>
  10. </div>
  11. <!-- 悬浮按钮 -->
  12. <FloatBtn :width="pageData.width" :height="pageData.height" @onClick="levitatedSphereVisible = true"/>
  13. <!-- 右侧popup -->
  14. <RightPopup :engine="engine" :userCardId="this.parametersData.userCardId" :levitatedSphereVisible.sync="levitatedSphereVisible" @shearplate="shearplate" @exit="exit"/>
  15. <!-- 输入并复制到粘贴板 -->
  16. <InputCopy ref="inputCopyRef" @openPasteboard="openPasteboard"/>
  17. <!-- 云机内部的粘贴板内容 -->
  18. <CloudPhoneClipboard ref="cloudPhoneClipboardRef" :doConnectDirectivesWs="doConnectDirectivesWs"/>
  19. </div>
  20. </template>
  21. <script>
  22. import meta from './config/meta.js';
  23. import request from './config/request.js';
  24. import FloatBtn from './components/FloatBtn.vue';
  25. import RightPopup from './components/RightPopup.vue';
  26. import InputCopy from './components/InputCopy.vue';
  27. import CloudPhoneClipboard from './components/CloudPhoneClipboard.vue';
  28. /**
  29. * @description: 判断当前页面运行环境
  30. * @return {Object} 返回当前页面运行环境
  31. */
  32. const isBrowserEnvironment = function() {
  33. // 判断是否在浏览器环境中
  34. const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined';
  35. // 判断是否在微信环境中
  36. const isWechat = /MicroMessenger/i.test(navigator.userAgent);
  37. // 判断是否在微信小程序的 web-view 中
  38. const isMiniProgramWebview = isWechat && /miniProgram/i.test(navigator.userAgent);
  39. // 判断是否是 iPhone 设备
  40. const isIPhone = /iPhone/i.test(navigator.userAgent);
  41. // 判断是否是顶级窗口(不是嵌套在 iframe 中)目前只有ios的浏览器中才会是顶级窗口
  42. const isTopWindow = window.parent === window.self;
  43. return {
  44. isBrowser, // 是否在浏览器环境中
  45. isWechat, // 是否在微信环境中
  46. isMiniProgramWebview, // 是否在微信小程序的 web-view 中
  47. isIPhone, // 是否是 iPhone 设备
  48. isTopWindow, // 是否是顶级窗口
  49. };
  50. }
  51. export default {
  52. auth: false,
  53. name: 'webRTC',
  54. layout: 'cloudPhone',
  55. components: {
  56. FloatBtn,
  57. RightPopup,
  58. InputCopy,
  59. CloudPhoneClipboard,
  60. },
  61. head() {
  62. return {
  63. title: '云手机',
  64. meta: [ ...meta ],
  65. script: [
  66. {
  67. // ./ 路径指向nuxt.config.js同级目录的static文件夹
  68. src: './rtcEngine/config/js/SDK.min.js', // sdk 2.0文件
  69. type: 'text/javascript',
  70. async: false,
  71. defer: false,
  72. onload: '$_script_loadHandler()', // 加载成功回调created生命周期中定义的方法
  73. onerror: '$_script_errHandler()', // 加载失败回调created生命周期中定义的方法
  74. },
  75. {
  76. // ./ 路径指向nuxt.config.js同级目录的static文件夹
  77. src: './static/js/uni.webview.1.5.2.js', // uniapp webview 1.5.2文件
  78. type: 'text/javascript',
  79. async: true,
  80. defer: false,
  81. }
  82. ]
  83. }
  84. },
  85. data() {
  86. return {
  87. // SDK加载状态
  88. sdkLoadStatus: 'loading', // sdk 加载状态 [loading|success|error]
  89. // 页面数据
  90. pageData: {
  91. width: 0, // 页面宽度
  92. height: 0, // 页面高度
  93. footMenuHeight: 40, // 底部菜单高度
  94. videoWidth: 0, // 视频宽度
  95. videoHeight: 0, // 视频高度
  96. },
  97. // 是否支持webRTC
  98. isSupportRtc: !!(
  99. typeof RTCPeerConnection !== 'undefined' &&
  100. typeof RTCIceCandidate !== 'undefined' &&
  101. typeof RTCSessionDescription !== 'undefined'
  102. ),
  103. // url 问号后的参数
  104. parametersData: {},
  105. // 卡的连接信息
  106. connectData: {},
  107. // 云手机引擎 播放器实例
  108. engine: null,
  109. doConnectDirectivesWs: null, // 云手机指令通道
  110. doConnectDirectivesIntervalerPing: null, // 业务通道定时标识 云手机指令通道心跳
  111. doConnectDirectivesRequestNum: 1, // 业务通道重连次数
  112. doConnectDirectivesRequestNumMax: 6, // 业务通道重连次数上限
  113. // 右侧popup显隐
  114. levitatedSphereVisible: false,
  115. }
  116. },
  117. // 页面初始化后触发
  118. async fetch() {
  119. // 预生产环境
  120. // http://192.168.211.37:3000/h5/rtcEngine/rtc/?record=2990744&userCardId=2990744&mealType=VIP&sourceType=0&userCardType=0&validTime=43200&rm=preZJHZ&authPhone=none&username=CGJkh1646034892SZX&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyYW5kb20iOiI5MTY4OCIsImNsaWVudCI6IjciLCJ1c2VyVHlwZSI6IjIiLCJtZXJjaGFudFNpZ24iOiJTWlgiLCJleHAiOjE3NDc1MjExMTIsInVzZXJuYW1lIjoiQ0dKa2gxNjQ2MDM0ODkyU1pYIn0.zqRNm0uW79VX2KV0sv3r9OsXHFOzkIpZP_tem4-lL4M&isTips=1&isWeixin=0&merchantSign=SZX
  121. // 测试环境
  122. // http://192.168.211.37:3000/h5/rtcEngine/rtc/?record=902481&userCardId=902481&mealType=sq&sourceType=0&userCardType=0&validTime=10334&rm=tencent-ap-tianjin-1&authPhone=none&username=0EsH01666175530SZX&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyYW5kb20iOiI0MTg5MyIsImNsaWVudCI6IjciLCJ1c2VyVHlwZSI6IjIiLCJtZXJjaGFudFNpZ24iOiJTWlgiLCJleHAiOjE3NDczODQ2MTgsInVzZXJuYW1lIjoiMEVzSDAxNjY2MTc1NTMwU1pYIn0.fTXawDu4SyEj4mIGr61ZXt0RSXzt3JztPq8-rFe-Zes&isTips=1&isWeixin=0&merchantSign=SZX
  123. // 获取页面传递参数
  124. this.parametersData = this.$route.query;
  125. },
  126. computed: {
  127. // 是否为微信浏览器环境
  128. isWeChatBrowser() {
  129. return this.$userAgent.isWx;
  130. },
  131. // 底部菜单高度
  132. footMenuWrapHeight() {
  133. let num = 40;
  134. this.pageData.footMenuHeight = num;
  135. return num;
  136. }
  137. },
  138. created() {
  139. this.$toast.loading({
  140. duration: 0, // 持续展示 toast
  141. message: '数据加载中...',
  142. });
  143. // 设置html标签的背景为黑色
  144. document.body.style.background = '#000';
  145. // 定义全局变量 用于监听sdk加载状态
  146. window.$_script_loadHandler = async ()=> {
  147. console.log('$_script_loadHandler: SDK加载成功');
  148. this.sdkLoadStatus = 'success';
  149. // 调用接口获取卡连接数据
  150. const cardData = await this.getConnectData(this.parametersData);
  151. // 判断卡的连接方式
  152. const connectData = await this.judgeConnectType(cardData);
  153. // 保存卡连接信息
  154. this.connectData = connectData;
  155. this.initWebRtc();
  156. };
  157. window.$_script_errHandler = ()=> {
  158. console.log('SDK加载失败');
  159. this.sdkLoadStatus = 'error';
  160. }
  161. },
  162. mounted() {
  163. // 获取窗口尺寸
  164. this.getInitSize();
  165. window.onresize = () => {
  166. console.log('窗口尺寸变化');
  167. this.getInitSize()
  168. };
  169. // 初始化页面监听事件
  170. this.initListener();
  171. },
  172. // 页面销毁前触发
  173. beforeDestroy() {
  174. // 销毁页面监听事件
  175. this.destroyListener();
  176. },
  177. methods: {
  178. inii(){
  179. console.log('initi')
  180. },
  181. // 初始化页面监听事件
  182. initListener() {
  183. // 禁止双击缩放
  184. document.addEventListener('dblclick', this.preventDefault);
  185. // 添加监听 页面显示或隐藏 事件
  186. document.addEventListener('visibilitychange', this.visibilitychanged);
  187. },
  188. // 销毁页面监听事件
  189. destroyListener() {
  190. // 允许双击缩放
  191. document.removeEventListener('dblclick', this.preventDefault);
  192. // 移除监听 页面显示或隐藏 事件
  193. document.removeEventListener('visibilitychange', this.visibilitychanged);
  194. },
  195. // 阻止默认事件
  196. preventDefault(e) {
  197. e.preventDefault();
  198. },
  199. // 监听 页面显示或隐藏 执行的函数
  200. visibilitychanged() {
  201. // 获取当前环境
  202. const env = isBrowserEnvironment();
  203. // 获取当前页面的可见性状态
  204. const visibilityState = document.visibilityState;
  205. if (visibilityState === 'visible') {
  206. // 页面显示时的逻辑
  207. // 网页重载
  208. if (env.isBrowser && env.isTopWindow && env.isIPhone) {
  209. location.reload();
  210. }
  211. } else if (visibilityState === 'hidden') {
  212. // 页面隐藏时的逻辑
  213. // video.pause();
  214. } else if (visibilityState === 'prerender') {
  215. // 页面预渲染时的逻辑
  216. console.log('页面处于预渲染状态');
  217. } else if (visibilityState === 'unloaded') {
  218. // 页面即将卸载时的逻辑 移除监听
  219. document.removeEventListener('visibilitychange', this.visibilitychanged);
  220. }
  221. },
  222. // 获取初始化尺寸
  223. getInitSize() {
  224. // 获取窗口尺寸
  225. this.pageData.height = window.innerHeight;
  226. this.pageData.width = window.innerWidth;
  227. // 计算视频尺寸 webRTC需要做成16:9的画面
  228. let videoWidth = this.pageData.width;
  229. let videoHeight = this.pageData.height - this.pageData.footMenuHeight;
  230. // 计算当前视口的宽高比
  231. const currentRatio = videoWidth / videoHeight;
  232. console.log(`当前视口的宽高比: ${currentRatio}`);
  233. // 9:16 的目标比例
  234. const targetRatio = 9 / 16;
  235. // 判断当前视口的宽高比与目标比例的关系
  236. if (currentRatio > targetRatio) {
  237. // 当前视口的宽高比大于目标比例,说明宽度“过宽”,需要以高度为基准
  238. console.log("当前视口宽度过宽,应以高度为基准调整宽度");
  239. this.pageData.videoWidth = videoHeight * targetRatio;
  240. this.pageData.videoHeight = videoHeight;
  241. console.log(`1目标: 宽${this.pageData.videoWidth},高${this.pageData.videoHeight}`);
  242. } else {
  243. // 当前视口的宽高比小于目标比例,说明高度“过高”,需要以宽度为基准
  244. console.log("当前视口高度过高,应以宽度为基准调整高度");
  245. this.pageData.videoWidth = videoWidth / targetRatio;
  246. this.pageData.videoHeight = videoWidth;
  247. console.log(`2目标: 宽${this.pageData.videoWidth},高${this.pageData.videoHeight}`);
  248. }
  249. },
  250. // 获取卡的信息
  251. async getConnectData(params) {
  252. try {
  253. const res = await this.$axios.$post('/resources/user/cloud/connect', { userCardId: params.userCardId}, {
  254. headers: {
  255. merchantSign: params.merchantSign,
  256. },
  257. });
  258. if (!res.success) {
  259. // TODO 提示错误信息 和 日志参数及上报
  260. // res.status状态码枚举值 0: 正常
  261. const statusEnum = {
  262. // 5200:RBD资源挂载中
  263. 5200: '网络异常,请稍后重试',
  264. // 入使用排队9.9,年卡
  265. 5220: '云手机正在一键修复中',
  266. 5203: '正在排队中,请稍等',
  267. // 9.9年卡连接异常,重新进入排队
  268. 5204: '云机异常,正在为你重新分配云机',
  269. 5228: '卡的网络状态为差',
  270. 5229: '接口返回链接信息缺失',
  271. }
  272. // 提示错误信息
  273. this.$toast(statusEnum[res.status] || '网络异常,请稍后重试')
  274. return Promise.reject(new Error(statusEnum[res.status] || '网络异常,请稍后重试'));
  275. }
  276. return res.data;
  277. }catch (error) {
  278. console.log('error connectAxios:', error);
  279. return Promise.reject(error);
  280. }
  281. },
  282. // 判断卡的连接方式
  283. async judgeConnectType(cardData) {
  284. try {
  285. // 不支持webRTC跳转到指定的页面进行拉流
  286. if (!cardData.isWebrtc) {
  287. // 跳转指定页面
  288. location.replace(`${location.origin}/h5/webRtcYJ/WXtrialInterface.html${location.search}`)
  289. return Promise.reject();
  290. }
  291. // 是否支持webRTC
  292. if (!this.isSupportRtc) {
  293. // TODO 提示错误信息 和 日志参数及上报
  294. this.$dialog.alert({
  295. title: '提示',
  296. message: '当前环境不支持使用,可下载谷歌浏览器或客户端进行使用',
  297. confirmButtonText: '确定',
  298. confirmButtonColor: '#3cc51f',
  299. callback: (_, done) => {
  300. done();
  301. this.exit();
  302. }
  303. })
  304. return Promise.reject(new Error('当前浏览器不支持webRTC'));
  305. }
  306. // webRtc连接,需获取连接中转地址
  307. if (cardData.webrtcNetworkAnalysis) {
  308. // 如果有网络分析的请求地址, 则请求,否则失败
  309. const { data: webrtcNetworkAnalysisReq }= await request.get(cardData.webrtcNetworkAnalysis); // 这个接口单独使用axios请求, 因为返回的数据跟封装的数据结构不一统一,是其他平台的接口,所以单独请求
  310. if (webrtcNetworkAnalysisReq !== null && webrtcNetworkAnalysisReq.success && webrtcNetworkAnalysisReq.data) {
  311. // 保存获取的连接地址到connect的请求的响应中, 方便后面使用
  312. cardData.webrtcNetwork = webrtcNetworkAnalysisReq.data;
  313. return cardData;
  314. }else{
  315. // TODO 提示错误信息 和 日志参数及上报
  316. return Promise.reject(new Error('网络分析请求失败'));
  317. }
  318. }else{
  319. // TODO 提示错误信息 和 日志参数及上报
  320. return Promise.reject(new Error('网络分析请求地址不存在'));
  321. }
  322. } catch (error) {
  323. console.log('判断卡的连接方式', error);
  324. return Promise.reject(error);
  325. }
  326. },
  327. // 初始化webRTC及相关配置
  328. initWebRtc() {
  329. try {
  330. // 获取挂载的容器元素
  331. const videoRef = document.getElementById("videoRef");
  332. // 解构connectData中的数据
  333. const { sn: topic, cardToken: authToken, localIp, internetHttps, internetHttp, webrtcNetwork, webrtcTransferCmnet, webrtcTransferTelecom, webrtcTransferUnicom, videoCode } = this.connectData;
  334. // 判断长连接的协议方式
  335. const isWss = location.protocol === 'https:';
  336. // 生成连接地址
  337. const url = `${isWss ? 'wss://' : 'ws://'}${isWss ? internetHttps : internetHttp}/nats`;
  338. // 统一使用三网解析地址
  339. const ICEServerUrl = [
  340. { "CMNET": webrtcNetwork }, // 移动
  341. { 'CHINANET-GD': webrtcNetwork }, // 电信
  342. { 'UNICOM-GD': webrtcNetwork }, // 联通
  343. ];
  344. // 配置连接参数
  345. const connection = {
  346. mount: videoRef,
  347. displaySize: { // 视频在页面显示的尺寸 必填
  348. width: this.pageData.videoWidth,
  349. height: this.pageData.videoHeight,
  350. },
  351. topic, // SN号 必填
  352. url, //信令服务地址 必填
  353. ICEServerUrl,
  354. forwardServerAddress: '', // 转发服务器地址
  355. ip: localIp, // 实例ip
  356. controlToken: '', // 控制token
  357. width: 720, // 推流视频宽度 必填
  358. height: 1080, // 推流视频高度 必填
  359. cardWidth: 0, // 云机系统分辨率 宽 必填
  360. cardHeight: 0, // 云机系统分辨率 高 必填
  361. cardDensity: 0, // 云机系统显示 密度 必填
  362. authToken, //拉流鉴权 token 必填
  363. quality: '高清',// 画质(码率) 超清 | 高清 | 标清 | 流畅
  364. fps: 30, //必填
  365. videoCodec: videoCode, // 视频编码格式 必填
  366. videoCodecMethod: true, // 硬编true | 软编false
  367. isMuted: false, // 是否静音
  368. isAllowedOpenCamera: true, // 是否允许打开摄像头
  369. sendFollow: true, // 是否允许主控转发文本到实例
  370. callback: (event)=> {}
  371. };
  372. // 获取SDK类
  373. const MediaSdk = window.rtc_sdk.MediaSdk;
  374. // 初始化 SDK
  375. this.engine = new MediaSdk();
  376. console.log('RtcEngineConfig==', connection)
  377. // 初始化 SDK 并传入连接参数
  378. this.engine.RtcEngine(connection);
  379. // 监听回调方法
  380. this.eventCallbackFunction();
  381. } catch (error) {
  382. console.log('webRTC初始化失败', error);
  383. }
  384. },
  385. // webRTC状态回调监听回调方法
  386. eventCallbackFunction() {
  387. const engine = this.engine;
  388. // 连接成功
  389. engine.on('CONNECT_SUCCESS', (r) => {
  390. console.log("webrtc连接成功====★★★★★", r);
  391. if (r.code === 1005) { // 1005: 拉流鉴权成功
  392. this.$toast.clear();
  393. // 播放视频
  394. setTimeout(() => {
  395. // engine.mediaElement.node.play();
  396. }, 50)
  397. }
  398. });
  399. // 连接关闭
  400. engine.on('CONNECT_CLOSE', (r) => {
  401. console.log("webrtc关闭====★★★★★", r);
  402. });
  403. // 连接异常
  404. engine.on('CONNECT_ERROR', (r) => {
  405. console.log("webrtc异常状态====★★★★★", r);
  406. });
  407. },
  408. // 业务指令通道初始化
  409. initControlChannel() {
  410. try {
  411. // 初始化业务指令通道
  412. let { internetHttps, internetHttp, localIp, cardToken } = this.connectData;
  413. const isWss = location.protocol === 'https:';
  414. let cUrl = `${isWss ? 'wss' : 'ws'}://${isWss ? internetHttps : internetHttp}/businessChannel?cardIp=${localIp}&token=${cardToken}&type=directives`;
  415. this.doConnectDirectivesWs = new WebSocket(cUrl);
  416. this.doConnectDirectivesWs.binaryType = 'arraybuffer';
  417. // 清除定时器
  418. if (this.doConnectDirectivesIntervalerPing) {
  419. clearInterval(this.doConnectDirectivesIntervalerPing);
  420. }
  421. // 链接成功
  422. this.doConnectDirectivesWs.onopen = (e) => {
  423. this.doConnectDirectivesIntervalerPing = setInterval(() => {
  424. if (this.doConnectDirectivesWs.readyState === 1) {
  425. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'ping' }));
  426. } else {
  427. clearInterval(this.doConnectDirectivesIntervalerPing);
  428. }
  429. }, 3000);
  430. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'getVsStatus' }))
  431. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'bitRate', data: { bitRate: 1243000 } }))
  432. // // 设置日志参数 推流质量
  433. // logReportObj.setParams({ imageQuality: 1243000 });
  434. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'InputMethod', data: { type: 2 } }))
  435. this.doConnectDirectivesWs.send(JSON.stringify({ type: 'getPhoneSize' }))
  436. }
  437. // 接受到的消息
  438. this.doConnectDirectivesWs.onmessage = res => {
  439. const result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
  440. switch (result.type) {
  441. case 'reProduceText':
  442. this.$native.clipboard.writeText(text);
  443. break
  444. case 'downAdnInstallRep':
  445. this.$toast(result.data.msg)
  446. break
  447. // 接受到这个消息就自动退出云机
  448. case 'exitPhone':
  449. this.exit();
  450. break
  451. }
  452. }
  453. // 链接报错的回调
  454. this.doConnectDirectivesWs.onerror = res => {
  455. if (this.doConnectDirectivesRequestNum > this.doConnectDirectivesRequestNumMax) {
  456. return this.exit();
  457. }else{
  458. // 重连次数加1
  459. ++this.doConnectDirectivesRequestNum;
  460. // 重连
  461. this.initControlChannel();
  462. }
  463. }
  464. } catch (error) {
  465. console.log('initControlChannel error', error);
  466. }
  467. },
  468. // 三键
  469. sendKey (keyCode) {
  470. try {
  471. console.log('sendKey', keyCode);
  472. this.engine?.sendKey(keyCode);
  473. } catch (error) {
  474. console.log('sendKey error', error);
  475. }
  476. },
  477. // popup粘贴板按钮点击事件
  478. shearplate(){
  479. // 调用InputCopy组件的pasteText方法读取粘贴板
  480. this.$refs.inputCopyRef.pasteText();
  481. },
  482. // 打开去取云机内的粘贴板内容
  483. openPasteboard(text){
  484. this.$refs.cloudPhoneClipboardRef.init(text);
  485. },
  486. // 超过指定触碰时间是否提示关闭链接
  487. async pushflowPopup() {
  488. try {
  489. const res = await this.$axios.get('/public/v5/pushflow/popup');
  490. if (res.success) {
  491. this.isFiringNoOperationSetTimeout = res.data;
  492. this.noOperationSetTimeout();
  493. }
  494. } catch (error) {
  495. console.log(error);
  496. }
  497. },
  498. // 退出功能
  499. exit() {
  500. // 关闭日志上报
  501. // logReportObj.destroy();
  502. // 关闭webRTC
  503. this.engine.disconnect && this.engine.disconnect();
  504. // 关闭业务指令通道
  505. this.doConnectDirectivesWs && this.doConnectDirectivesWs.close();
  506. // 获取当前环境
  507. const env = isBrowserEnvironment();
  508. // ios环境下 直接使用浏览器api返回上一页
  509. if(env.isBrowser && !env.isMiniProgramWebview && env.isIPhone && env.isTopWindow) {
  510. // 上面的方法执行未生效就走这里
  511. if (window.history.length > 1) {
  512. return window.history.back();
  513. }
  514. }
  515. uni && uni.reLaunch({ url: '/pages/index/index' });
  516. },
  517. }
  518. }
  519. </script>
  520. <style lang="scss" scoped>
  521. html{
  522. background-color: #000;
  523. }
  524. // 动态生成 从 0 到 100px 的样式
  525. @for $i from 0 through 100 {
  526. .mb-#{$i} {
  527. margin-bottom: #{$i}px;
  528. }
  529. .mt-#{$i} {
  530. margin-top: #{$i}px;
  531. }
  532. .ml-#{$i} {
  533. margin-left: #{$i}px;
  534. }
  535. .mr-#{$i} {
  536. margin-right: #{$i}px;
  537. }
  538. }
  539. $-radeus-12: 12px;
  540. $-bg-yellow: rgb(255, 253, 241);
  541. .rtc-page{
  542. position: relative;
  543. font-size: 14px;
  544. .video-wrapper{
  545. position: relative;
  546. }
  547. }
  548. .cover-bg{
  549. background-color: #000;
  550. }
  551. #foot-menu-wrap{
  552. border-width: 0px;
  553. position: absolute;
  554. left: 0px;
  555. bottom: 0px;
  556. width: 100%;
  557. // height: 40px; // 三大功能键高度,通过vue动态添加
  558. background: inherit;
  559. background-color: rgba(0, 12, 23, 1);
  560. border: none;
  561. border-radius: 0px;
  562. -moz-box-shadow: none;
  563. -webkit-box-shadow: none;
  564. box-shadow: none;
  565. z-index: 1;
  566. display: flex;
  567. justify-content: space-evenly;
  568. align-items: center;
  569. color: #fff;
  570. }
  571. </style>