diff --git a/README.md b/README.md index 48ecdafac951b037548204c762c543afeeacb9bb..6df9b13101205e36e3c74a8efeb4e9b588b95f5e 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,13 @@ node package.js | 将产物输出为 tar.gz 包,放在 dist 目录下 (2)执行 npm run dev 命令 #### 3.3 产物下载 ```shell -## 可以直接下载本项目构建好的产物进行尝鲜使用,下载解压后,修改 demo.html 中 ip、port 即可连接 -https://gitee.com/HuaweiCloudDeveloper/huaweicloud-cloudPhoneAccess-web/releases/tag/v23.3.0 +## 可以直接下载本项目构建好的产物进行尝鲜使用 +1、从链接https://gitee.com/HuaweiCloudDeveloper/huaweicloud-cloudPhoneAccess-web/releases/tag/v23.12.3 下载“CloudAppSdk_H5_Release_23.12.3.rar”压缩包并解压 +2、解压后使用文本编辑软件编辑demo.html,写入正确的"aes_key"修改后保存 +3、使用浏览器打开demo.html,输入云手机的ip和端口(可在华为云控制台云手机详情页查看cph_h5_server的公网访问地址),点击“ENTER” +4、连接之后会跳转到新的页面,需要信任一下证书,点击信任后关闭新的页面,回到原来页面刷新一下就可以看到云手机画面 ``` + #### 3.4 FAQ (1)Error in connection establishment: net::ERR_CERT_AUTHORITY_INVALID ``` @@ -102,7 +106,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht - 参数介绍 - @param {string} containerId:渲染游戏视图的 DOM 元素 id,必选。暂时不支持修改,可能导致JS获取不到DOM + @param {string} containerId:渲染游戏视图的 DOM 元素 id,必选。暂时不支持修改或删除,可能导致JS获取不到DOM @param {object} params:启动相关配置,必选 @@ -114,7 +118,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht | ticket | 是 | String | 随机数 | | aes_key | 是 | String | 对称秘钥 | | auth_ts | 是 | String | 验签时间戳 | - | background_timeout | 是 | String | 页面非活跃状态超时时长 (值为0时,无超时判断) | + | background_timeout | 是 | String | 页面非活跃状态超时时长 (取值范围15~3600,单位是s) | | available_playtime | 是 | String | 单次连接可用时长 (值为0时,无超时判断) | | touch_timeout | 否 | String | 无触控时长 (值为0时,无超时判断) | | user_id | 否 | String | 用户id | @@ -142,7 +146,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht let cloudapp = new CloudApp("container", params); ``` -#### 4.3 退出云手机 +#### 4.3 退出云手机(方法) - 接口介绍 @@ -152,7 +156,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 退出云手机。 -#### 4.4 重连云手机 +#### 4.4 重连云手机(方法) - 接口介绍 @@ -162,7 +166,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 重新连接云手机。 -#### 4.5 获取音量值 +#### 4.5 获取音量值(方法) - 接口介绍 @@ -172,7 +176,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 获取当前音量值。 -#### 4.6 设置音量值 +#### 4.6 设置音量值(方法) - 接口介绍 @@ -182,7 +186,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 设置音量值。 -#### 4.7 获取是否全屏 +#### 4.7 获取是否全屏(方法) - 接口介绍 @@ -192,7 +196,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 获取是否全屏状态,仅限PC浏览器。 -#### 4.8 全屏/非全屏切换 +#### 4.8 全屏/非全屏切换(方法) - 接口介绍 @@ -206,7 +210,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht @param {string} fullscreenElementId,全屏元素ID,必须是 containerId 或其对应节点的父级节点的 ID。选填项,若不提供则默认值为 containerId。 -#### 4.9 设置音视频参数 +#### 4.9 设置音视频参数(方法) - 接口介绍 @@ -223,12 +227,12 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht | 参数 | 参数类型 | 描述 | 约束 | | -------------- | -------- | ------ | ---------------------- | | bitrate | String | 码率 | 1Mbit/s到10Mbit/s | - | stream_width | String | 虚拟宽 | 240到4096,且为8的倍数 | - | stream_height | String | 虚拟高 | 240到4096,且为8的倍数 | + | stream_width | String | 虚拟宽 | 240到4096,且为8的倍数,与stream_height乘积不能大于云手机规格宽高乘积 | + | stream_height | String | 虚拟高 | 240到4096,且为8的倍数,与stream_width乘积不能大于云手机规格宽高乘积 | -#### 4.10 应用状态更新 +#### 4.10 应用状态更新(事件) -- 接口介绍 +- 接口介绍(具体状态可见第7条附录) cloudApp.on("appStateChange", function(event, data){ @@ -238,7 +242,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht - 功能描述 - 业务可以根据接入状态自定义交互行为。 + 业务可以根据接入状态自定义交互行为,状态全集可查看端云引擎对接文档 - 参数介绍 @@ -246,7 +250,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht @param {object} data,接入状态数据,data 对象包含 state、message 属性 -#### 4.11 网络时延更新 +#### 4.11 网络时延更新(事件) - 接口介绍 @@ -271,7 +275,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht | delay | number | 网络时延,单位是ms | | bitrate | number | 码率,单位是kbps | -#### 4.12 云机剪切板回调 +#### 4.12 云机剪切板(事件) - 接口介绍 @@ -292,7 +296,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht @param {string} data,云手机剪切板数据。 -#### 4.13 发送剪切板数据 +#### 4.13 发送剪切板数据(方法) - 接口介绍 @@ -307,7 +311,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht @param {string} data,端侧剪切板数据。 -#### 4.13 云手机桌面功能 +#### 4.13 云手机桌面功能(方法) - 接口介绍 @@ -317,7 +321,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 进入云手机桌面。 -#### 4.14 云手机回退功能 +#### 4.14 云手机回退功能(方法) - 接口介绍 @@ -327,7 +331,7 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 应用退出、返回上一步。 -#### 4.15 云手机多任务功能 +#### 4.15 云手机多任务功能(方法) - 接口介绍 @@ -337,8 +341,37 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht 进入云手机多任务页面。 -### 5. 开发指导 -### 6. FAQ +#### 4.16 云手机设置默认相机功能(方法) + +- 接口介绍 + + cloudApp.setDefaultCamera(devId); + +- 功能描述 + + 设置需要调用的相机,参数为相机的设备Id(deviceId)。 + +#### 4.17 云手机获取可用相机功能(方法) + +- 接口介绍 + + cloudApp.getCameras(); + +- 功能描述 + + 返回所有可用的相机,可根据返回值设置默认相机。 + +#### 4.18 云手机恢复默认相机功能(方法) + +- 接口介绍 + + cloudApp.resetDefaultCamera(); + +- 功能描述 + + 重置指定相机,调用默认相机。 + +### 5. FAQ - 部署后页面访问地址中需要携带目标html的完整名称。如:https://110.123.23.34:1001/enter.html - 出现AESGCMCrypto undefined的相关错误,是由于浏览器安全策略限制。需要使用HTTPS协议访问项目, 或使用localhost:XXXX、127.0.0.1:XXXX访问项目。 @@ -347,8 +380,47 @@ Chrome restricts the usage of WebCryptographyApi to secure origins. It means 'ht - 项目报错:Failed to execute 'importScripts' on 'WorkerGlobalScope': The URL '/xxx/xxx/lib/libffmpeg_264_265.js' is invalid。请检查demo.html中libPath(编解码库文件的地址)参数的值是否正确。libPath可以不用修改。如果用户需要修改,要写完整的目录路径,如:'https://110.123.23.34:1001/lib/'。验证目录地址配置是否正确,可在浏览器中输入'https://110.123.23.34:1001/lib/libffmpeg_264_265.js'访问,可成功访问文件内容,则配置正确。 - 部署后页面提示样式找不到。如:style is null | undefined -- 请检查是否修改了demo.html中DOM元素的ID,当前ID与逻辑绑定,正在优化中。若要修改,请确保JS操作DOM时,对应的ID参数也一并进行了修改。 +- 请检查是否修改了demo.html中DOM元素的ID,当前ID与逻辑绑定。若要修改,请确保JS操作DOM时,对应的ID参数也一并进行了修改。 -### 7. license +### 6. license Apache License 2.0。 +### 7. 附录 + +# CAE状态码 +| 十进制消息码 | 消息内容 | 说明 | +| ------------ | --------------------------- | ----------------------------- | +| 256 | Connecting | Socket连接中 | +| 512 | Connect success | Socket连接成功 | +| 769 | Server unreachable | 服务器不可达 | +| 770 | Resource in using | 资源正在使用中 | +| 1024 | Verifying | 接入信息认证中 | +| 1280 | Verify success | 接入信息认证通过 | +| 1537 | Verify parameter missing | 缺少认证参数 | +| 1538 | Verify parameter invalid | 认证参数非法 | +| 1540 | Server inner error | 系统内部错误 | +| 1541 | Server inner error | 系统内部错误 | +| 1542 | Server inner error | 接入信息认证失败 | +| 1543 | / | SESSION_ID校验失败 | +| 2048 | Start success | Start指令执行成功 | +| 2308 | Start parameter missing | Start指令缺少参数 | +| 2560 | Connect lost | 连接丢失 | +| 2816 | Reconnecting | 重新连接中 | +| 3072 | Reconnect success | 重连成功 | +| 3329 | Reconnect parameter invalid | 重连参数非法 | +| 3331 | Server inner error | 系统内部错误 | +| 3584 | Available time usedup | 可用时间已用完 | +| 3840 | Notouch timeout | 未触屏时间超时 | +| 4096 | Switch background timeout | 播流软件切换到后台超时 | +| 4353 | Engine Start failed | 接入引擎开启失败 | +| 4354 | / | 云手机不支持H265编码 | +| 4608 | Switch backgroud | 播流软件切换到后台 | +| 4865 | Server inner error | 播流软件切换后台失败 | +| 5120 | Switch foregroud | 播流软件切换到前台 | +| 5377 | / | 播流软件切换前台失败 | +| 6400 | Back home | 退出播流软件 | +| 8448 | Set media config success | 媒体配置设置成功 | +| 8705 | Set media config error | 媒体配置设置失败 | +| 8960 | / | CAE被抢占,当前客户端被迫下线 | +| 65535 | Invalid Operation | 非法操作 | + diff --git a/sdk/src/AppController.js b/sdk/src/AppController.js index 73c2174a2fe259c4970ab723525e6028fb1cb70b..658a0f4d5d3bee9553dbba29e6df8dd3d3d7b4d2 100644 --- a/sdk/src/AppController.js +++ b/sdk/src/AppController.js @@ -149,7 +149,7 @@ class AppController { // 网络时延/码率 this.networkInfo = { heartBeatSendTimes: [], - sendTimesMaxCount: 50, + sendTimesMaxCount: 4, delay: 0, bitRate: 0, kbitCount: 0, @@ -797,7 +797,16 @@ class AppController { } recordHeartbeat(time) { - this.networkInfo.heartBeatSendTimes.length <= this.networkInfo.sendTimesMaxCount && (this.networkInfo.heartBeatSendTimes.push(time)); + if (this.networkInfo.heartBeatSendTimes.length <= this.networkInfo.sendTimesMaxCount){ + this.networkInfo.heartBeatSendTimes.push(time); + } else { + this.networkInfo.heartBeatSendTimes = [] + this.terminateSocketWorker(); + setTimeout(() => { + this.createSocket(); + this.reconnect(); + }, 0); + } } tryReconnect(trigger) { @@ -1483,6 +1492,32 @@ class AppController { } } + setDefaultCamera(devId) { + this.deviceHardwareHandler.setDefaultCamera(devId); + } + + getCameras() { + let videoDevices = []; + navigator.mediaDevices.enumerateDevices() + .then(devices => { + devices.forEach(device => { + if (device.kind === "videoinput") { + videoDevices.push(device); + } + }); + console.log(videoDevices) + // 输出视频摄像头列表 + return videoDevices; + }) + .catch(error => { + console.log("获取设备列表失败:", error); + }); + } + + resetDefaultCamera() { + this.deviceHardwareHandler.defaultCameraId = ''; + } + /** * 销毁 * @param {boolean}} reserveSocketWorker 是否需要销毁socket,exit场景,延迟close socket,close前需保留socket diff --git a/sdk/src/CPHCloudApp.js b/sdk/src/CPHCloudApp.js index cfdefcc64cbc9209f816bd4c3641d2a937daac94..587219cb8dc21454fbdc79fda0a7ff4c224c4c39 100644 --- a/sdk/src/CPHCloudApp.js +++ b/sdk/src/CPHCloudApp.js @@ -15,6 +15,7 @@ import AppController from './AppController'; import PROTOCOL_CONFIG from './config/protocolConfig'; import Logger from './Logger'; +import Util from './Util'; import NoDebugger from './NoDebugger'; const WEBSOCKET_PREFIX = 'wss://'; @@ -42,6 +43,7 @@ const INIT_VIRTUAL_DEVICE_STATE = 2048; */ class CPHCloudApp { constructor(containerId, options) { + this.util = new Util(); let defaults = {}; // 参数校验 let msg = this._verify(options); @@ -117,6 +119,13 @@ class CPHCloudApp { window.addEventListener('unload', () => { this.exit(); }); + this.util.bind(window, 'online', () => { + this.appController.createSocket(); + this.reconnect(); + }); + this.util.bind(window, 'offline', () => { + this.appController.terminateSocketWorker(); + }); } fullscreenToggle(fullscreenElementId) { @@ -322,6 +331,8 @@ class CPHCloudApp { } exit() { + this.util.unbind(window, 'online'); + this.util.unbind(window, 'offline'); this.appController.exit(); this.appController = null; } @@ -382,6 +393,18 @@ class CPHCloudApp { sendClipboardData(data) { this.appController.sendClipboardData(data); } + + setDefaultCamera(devId) { + this.appController.setDefaultCamera(devId); + } + + getCameras() { + this.appController.getCameras(); + } + + resetDefaultCamera() { + this.appController.resetDefaultCamera(); + } } CPHCloudApp.RESOLUTIONS = {...PROTOCOL_CONFIG.RESOLUTIONS}; diff --git a/sdk/src/CloudApp.js b/sdk/src/CloudApp.js index f72fe0cefc6e03d08b9f5046ae863efbfe1ea6f9..fd59fe854e70e5d5b993310ff25dfe5cd5956d70 100644 --- a/sdk/src/CloudApp.js +++ b/sdk/src/CloudApp.js @@ -103,6 +103,18 @@ class CloudApp { this.channel.updateResolutionAndTouch(); } + setDefaultCamera(devId) { + this.channel.setDefaultCamera(devId); + } + + getCameras() { + this.channel.getCameras(); + } + + resetDefaultCamera() { + this.channel.resetDefaultCamera(); + } + _getChannel(containerId, options) { const channelType = options.channelType || 'WebSocket'; if (channelType === 'WebSocket') { diff --git a/sdk/src/DeviceHardwareHandler.js b/sdk/src/DeviceHardwareHandler.js index 4f01fa5abf5c11798efe2655e3c2b21a573164a8..46db2681039685777a9f08ac9eb4529db93ca355 100644 --- a/sdk/src/DeviceHardwareHandler.js +++ b/sdk/src/DeviceHardwareHandler.js @@ -35,6 +35,7 @@ class DeviceHardwareHandler { this.util = new Util(); this.sensorMsgType = PROTOCOL_CONFIG.SENSOR_MESSAGE_TYPE; this.gpsLocationMsgType = PROTOCOL_CONFIG.GPS_LOCATION_MESSAGE_TYPE; + this.defaultCameraId = ""; } handleStartPreviewReq(cameraMsgBody, cameraMsgHeader) { @@ -56,10 +57,15 @@ class DeviceHardwareHandler { videoOptions.width.ideal = cameraWidth; videoOptions.height.ideal = PC_CAMERA_MAX_HEIGHT; } - if (this.options.isMobile) { - videoOptions.facingMode = { - exact: MOBILE_CAMERA_MODE_MAP[cameraMsgHeader.devId] - }; + + if (this.defaultCameraId) { + videoOptions.deviceId = { + exact: this.defaultCameraId + } + } else if (this.options.isMobile){ + videoOptions.facingMode = { + exact: MOBILE_CAMERA_MODE_MAP[cameraMsgHeader.devId] + }; } this.cameraPlayer = new CameraPlayer({ videoOptions, @@ -71,6 +77,10 @@ class DeviceHardwareHandler { }); } + setDefaultCamera(devId) { + this.defaultCameraId = devId; + } + handleStopPreviewReq(cameraMsgHeader) { this.cameraMsgHeader = cameraMsgHeader; this.sendMediaMsgCmdData(CAMERA_RSP_LEN, this.options.cameraMsgType.OPT_CAMERA_STOP_PREVIEW_RSP, 'CAMERA'); diff --git a/sdk/src/DirectionHandler.js b/sdk/src/DirectionHandler.js index 324c5e9db6920eb3a345a119074df9cd98d557cc..b87c713c6cea96f3c9811229e07927f20771c898 100644 --- a/sdk/src/DirectionHandler.js +++ b/sdk/src/DirectionHandler.js @@ -569,7 +569,7 @@ class DirectionHandler { // 旋转前需要先取消旋转,避免叠加旋转 this.cancelTransform(); if (this.options.isMobile) { - const deviceOrientation = window.screen.orientation.angle || window.orientation; + const deviceOrientation = window?.screen?.orientation?.angle || window.orientation; if ([DEVICE_ORIENTATION_ANGLES.PORTRAIT,DEVICE_ORIENTATION_ANGLES.REVERSE_PORTRAIT].includes(deviceOrientation)) { this.portraitMobile(); } else if ([DEVICE_ORIENTATION_ANGLES.LANDSCAPE, DEVICE_ORIENTATION_ANGLES.REVERSE_LANDSCAPE].includes(deviceOrientation)) { @@ -692,7 +692,7 @@ class DirectionHandler { setTimeout(() => { let width = document.documentElement.clientWidth; let height = document.documentElement.clientHeight; - const deviceOrientation = window.screen.orientation.angle || window.orientation; + const deviceOrientation = window?.screen?.orientation?.angle || window.orientation; if ([DEVICE_ORIENTATION_ANGLES.PORTRAIT,DEVICE_ORIENTATION_ANGLES.REVERSE_PORTRAIT].includes(deviceOrientation)) { if ([PROTOCOL_CONFIG.ORIENTATION[0], PROTOCOL_CONFIG.ORIENTATION[16]].includes(this.orientation)) { playerWidth = width;