参考Sonic平台
WebSocket封装
/*** WebSocket 封装* 主要功能:* 1. 自动重连;*/
export default class Socket {// WebSocket 实例ws// 相关配置项options// 错误消息队列errorStack = []// 是否正在重连isReconnectLoading = false// 定时器超时重连 IDtimeId = nullconstructor(options) {/*** options 支持传入参数* url websocket请求地址* node: 'player',* mode: 'audio',* debug: true,* flushingTime: 0,* reconnectDelay: 3000,* onopen,* onmessage,* onerror,* onclose*/this.options = {isErrorReconnect: true,...options}this.init()}/*** 初始化 WebSocket*/init() {if ('WebSocket' in window) {this.ws = new WebSocket(this.options.url);this.ws.binaryType = this.options.binaryTypethis.onOpen(this.options.onopen)this.onMessage(this.options.onmessage)this.onError(this.options.onerror)this.onClose(this.options.onclose)} else {console.error('该浏览器不支持WebSocket!');}}// 获取 WebSocket 实例get getWebSocket() {return this.ws}/*** 设置连接成功后的回调函数* @param {*} cb*/onOpen(cb) {this.ws.onopen = () => {console.log('websocket >>>> onOpen 连接成功!')// 发送成功连接之前所发送失败的消息this.errorStack.forEach(message => {this.send(message)})cb && cb()this.errorStack = []this.isReconnectLoading = false}}/*** 设置接收 WebSocket 消息的回调函数* @param {*} cb */onMessage(cb) {try {this.ws.onmessage = cb} catch (e) {console.error('error: ', e)}}/*** 设置连接失败后的回调函数* @param {*} cb */onError(cb) {this.ws.onerror = (err) => {console.error(err, 'websocket >>>> onError 连接异常!')cb && cb(err)if (!this.options.isErrorReconnect) returnthis.onReconnection()this.isReconnectLoading = false}}/*** 设置连接关闭后的回调函数* @param {*} cb */onClose(cb) {this.ws.onclose = () => {console.log('websocket >>>> onClose 关闭连接!')// 用户手动关闭的不重连if (this.isCustomClose) returncb && cb()this.onReconnection()this.isReconnectionLoading = false}}/*** 请求连接异常重连* @returns */onReconnection() {// 重连请求延时const delay = this.options.reconnectDelay || 3000// 防止重复请求if (this.isReconnectionLoading) {console.log('websocket >>>> onReconnection 请勿重复请求连接!')return}console.log('websocket >>>> onReconnection 正在重连!')this.isReconnectionLoading = trueclearTimeout(this.timeId)this.timeId = setTimeout(() => {this.init()}, delay)}/*** 手动发送请求* @param {*} message * @returns */handleSend(message) {// 连接失败时的处理if (this.ws.readyState !== WebSocket.OPEN) {console.error('websocket >>>> handleSend 请求发送失败!')this.errorStack.push(message)return}this.ws.send(message)}/*** 手动关闭连接*/handleClose() {this.isCustomClose = truethis.ws.close()}/*** 手动开启连接*/handleStart() {this.isCustomClose = falsethis.onReconnection()}/*** 手动销毁连接实例*/handleDestroy() {this.handleClose()this.ws = nullthis.errorStack = nullconsole.log('websocket >>>> handleDestroy 实例已销毁!')}
}
音视频接受处理器
/** Copyright (C) [SonicCloudOrg] Sonic Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.**/
/*** 音频接受处理器*/
import JMuxer from 'jmuxer';
import Socket from './socket';const DEFAULT_WS_URL = 'ws://localhost:8080';export default class AudioProcessor {constructor(options) {const wsUrl = options.wsUrl || DEFAULT_WS_URL/*** node: 'player',* mode: 'audio',* debug: true,* flushingTime: 0,* wsUrl*/this.jmuxer = new JMuxer({mode: 'audio',flushingTime: 0,// onReady() {// console.log('Jmuxer audio init onReady!');// },// onError(data) {// console.error('Buffer error encountered', data);// },...options});this.audioDom = document.getElementById(options.node)this.initWebSocket(wsUrl)}initWebSocket(url) {const that = thisthis.ws = new Socket({url,binaryType: 'arraybuffer',isErrorReconnect: false,onmessage: function(event) {var data = that.parse(event.data);data && that.jmuxer.feed(data);}});}/*** 音频解析* @param {*} data AAC Buffer 视频流* @returns */parse(data) {let input = new Uint8Array(data)return {audio: input};}onPlay() {this.audioDom.load()const playPromise = this.audioDom.play()if (playPromise !== undefined) {playPromise.then(() => {this.audioDom.play()})}}onPause() {this.audioDom.pause()}onReload() {this.audioDom.load()}onDestroy() {this.ws.handleClose()this.audioDom.pause()this.jmuxer.destroy()}
}
保存为图片元素 {{ device.name }} {{ device['model'] }} {{ device['udId'] }} {{ device['version'] }} {{ device['size'] }} {{ device['cpu'] }} 输入文本发送 0" style="margin-top: 20px;margin-bottom: 20px">adb connect {{ remoteAdbUrl }} 所在Agent未开启该功能! http://{{ agent['host'] }}:{{ remoteAppiumPort }}/wd/hub AppiumDriver未初始化! 其他初始化AppiumDriver 扫描二维码将二维码图片拖到此处,或点击上传只能上传jpg/png文件 文件互传将文件拖到此处,或点击上传Push Pull 下载文件 上传安装将APK文件拖到此处,或点击上传只能上传apk文件 URL安装发送 刷新 {{ scope.row.packageName }} 打开 卸载 开始抓包 下载证书 取消全局代理 代理连接:{{agent['host'] + ':' + proxyConnPort}}刷新 {{(currentWifi.length > 0 && isConnectWifi) ? currentWifi.replaceAll('"', '') : ' '}}使用教学 截图 清空 保存图片{{ cmdUser + ':/ $' }} Send Stop Clear | grep Search Stop Clear 用例详情取消关联 {{ testCase['id'] }} {{ testCase.name }} 0?project['projectImg']:defaultLogo"shape="square">{{ project['projectName'] }} {{ testCase['platform'] === 1 ? '安卓' : 'iOS' }} {{ testCase['module'] }} {{ testCase['version'] }} {{ testCase['designer'] }} {{ testCase['editTime'] }} {{ testCase['des'] }} 关联项目0?item['projectImg']:defaultLogo"shape="square">{{ item['projectName'] }} 新增用例 重新获取控件元素 0">当前Activity: {{ activity }} 0"style="margin-top: 10px">{{ scope.row }} {{ node.label.substring(0, node.label.indexOf('>')) + ' ' }}resource-id={{'"' + data.detail['resource-id'] + '">'}}{{ node.label }} 添加控件控件快照 {{ elementDetail['class'] }}{{ elementDetail['resource-id'] }}{{ scope.row }} {{ elementDetail['xpath'] }} {{ elementDetail['text'] }}{{ elementDetail['content-desc'] }}{{ elementDetail['package'] }}{{ computedCenter(elementDetail['bStart'], elementDetail['bEnd']) }} {{ elementDetail['index'] }} {{ elementDetail['bounds'] }}