开发指导
该开发场景主要面向普通云手机客户端开发者,旨在帮助开发者通过集成和调用KooPhone端SDK的部分基本功能,初步理解和实现一个面向云手机用户的功能完善的H5客户端。
本章节所有开发示例均为展示基本功能的实现,仅供参考。
- 准备样式。
绘制H5串流页面时,首先需要配置页面样式。主要分如下三类:
- 配置基础样式。
<head> <style> html, body { width: 100%; /* 宽度占满整个浏览器窗口 */ height: 100%; /* 高度占满整个浏览器窗口 */ } body { margin: 0; /* 清除body默认的外边距(避免页面出现空白边) */ } </style> </head> - 配置容器布局样式。
请参考以下配置div部分的CSS格式,铺满父元素并水平对齐子元素,两端各占30%,中间container固定为600px。以满足左侧(#left-box)输入鉴权信息及操作按钮,中间(#container)进行串流演示,右侧(#right-box)展示串流过程的信息。
<head> <style> #box { width: 100%; /* 宽度占满父元素(这里是body,即全屏) */ height: 100%; /* 高度占满父元素(全屏) */ display: flex; /* 开启弹性布局,子元素会按flex规则排列 */ justify-content: space-between; /* 子元素沿主轴(水平)两端对齐,中间自动填充空白 */ } #left-box { width:30%; /* 宽度为父元素(#box)的30%(随窗口缩放变化) */ margin: 30px; /* 外边距30px(和其他元素拉开距离) */ } #container { width: 600px; /* 固定宽度600像素(不会随窗口缩放变化) */ height: 100%; /* 高度占满父元素(#box,即全屏) */ } #right-box { width: 30%; /* 宽度为父元素(#box)的30%(随窗口缩放变化) */ height: 90%; /* 高度为父元素的90%(留10%空白) */ overflow: auto; /* 内容超出容器时,自动显示滚动条(只滚动内容,容器不撑大) */ border: solid 1px #000; /* 黑色实线边框,宽度1px */ margin: 30px; /* 外边距30px(和其他元素拉开距离) */ border-radius: 4px; /* 边框圆角4px(让边角更圆润) */ } </style> </head>
- 配置基础样式。
- 准备Html页面元素。
- 准备输入框及按钮元素。
请参考以下布局,设计IAM信息输入框,以及进入串流和结束串流按钮。
<div id="box"> <div id="left-box"> <p style="color:red">输入以下参数,单击 初始化player并启动串流 按钮发起串流</p> <label for="">domain:</label> <input id="domain" type="text" value=""/><br> <label for="">user:</label> <input id="user" type="text" value=""/><br> <label for="">password:</label> <input id="password" type="password" value=""/><br> <label for="">instance_id:</label> <input id="instance_id" type="text" value=""/><br> <br> <button onclick="enter()">初始化player并启动串流</button> <br> <button onclick="closeMediaStream()">退出串流</button> <br> </div> <div id="container"></div> <div id="right-box"></div> </div> - 引入SDK。
- 纯浏览器环境,适用于不使用构建工具(如Webpack/Vite),仅通过浏览器原生支持的ES8模块开发场景。
<script type="text/javascript" src="./js/kp-client-xx.js"></script> <script type="text/javascript"> var client = new KPClient(); </script> - 工程化项目,适用于使用构建工具的项目,模块化语法会被构建工具处理,无需依赖浏览器原生支持。
import { KPClient } from './js/kp-client-xx.js'; const client = new KPClient();
- 纯浏览器环境,适用于不使用构建工具(如Webpack/Vite),仅通过浏览器原生支持的ES8模块开发场景。
- 准备输入框及按钮元素。
- 自动获取鉴权信息。
在快速开始中提供了手动获取鉴权信息进入串流的方案。这里给出自动获取鉴权并进入串流的使用指导,方便用户直接进行工程化部署。
- 部署本地Proxy。
- 在串流时,由于浏览器跨域问题,无法在js中直接请求鉴权接口,因此需要在本地启动代理服务。将以下代码保存到本地proxy.js文件中。
const http = require('http'); const { URL } = require('url'); const { createServer: createProxyServer } = require('http-proxy'); // 读取命令行参数:node proxy.js url1 url2 const args = process.argv.slice(2); if (args.length < 2) { process.exit(1); } const TARGET_AUTH = args[0]; const TARGET_TOKEN = args[1]; const PORT = 8080; // 创建代理服务 const proxyAuth = createProxyServer({ target: TARGET_AUTH, changeOrigin: true }); const proxyToken = createProxyServer({ target: TARGET_TOKEN, changeOrigin: true }); // 创建本地HTTP服务 const server = http.createServer((req, res) => { const path = req.url; res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Headers", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader('Access-Control-Expose-Headers', 'x-subject-token'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } if (path === '/v3/auth/tokens') { proxyToken.web(req, res); } else { proxyAuth.web(req, res); } }); // 启动服务 server.listen(PORT, () => { console.log('跨域代理服务已启动'); console.log(`本地地址: http://localhost:${PORT}`); }); - 准备请求串流鉴权的URL地址和请求IAM Token的URL地址。
- 请求串流鉴权的URL地址,在API Explorer中获取。举例:如果是北京4区域,地址为https://koophone.myhuaweicloud.com。
- 请求IAM Token的URL地址,在构造IAM请求中获取。举例:如果是北京4区域,地址为https://iam.cn-north-4.myhuaweicloud.com
- 启动本地代理。
- 在串流时,由于浏览器跨域问题,无法在js中直接请求鉴权接口,因此需要在本地启动代理服务。将以下代码保存到本地proxy.js文件中。
- 获取IAM Token。
在获取KooPhone鉴权信息之前,需要准备本账号的IAM相关信息:
- IAM用户所属账号名称(domain)
- IAM用户名(user)
- IAM用户登录密码(password)
- KooPhone云手机实例ID(instance_id)
将IAM信息和实例信息替换到如下代码中的参数定义部分,然后使用如下refreshIamToken函数即可获取到IAM Token。
// IAM信息填充 const domain = document.getElementById("domain").value; const user = document.getElementById("user").value; const password = document.getElementById("password").value; const instanceId = document.getElementById("instance_id").value; async function refreshIamToken() { return fetch("http://localhost:8080/v3/auth/tokens", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "auth": { "identity": { "methods": [ "password" ], "password": { "user": { "domain": { "name": domain }, "name": user, "password": password } } }, "scope": { "domain": { "name": domain } } } }), mode: "cors" }) .then(response => { const headers = response.headers; for (let [key, value] of headers.entries()) { console.log(`${key}: ${value}`); } return headers.get('x-subject-token'); }); } - 获取串流鉴权信息。
获取IAM Token后,即可请求KooPhone实例串流鉴权信息。准备KooPhone云手机实例ID(instance_id),将实例ID替换如下instanceId参数。
// 串流实例信息填充 const instanceId = document.getElementById("instance_id").value; async function refreshDeviceToken() { const token = await refreshIamToken(); return fetch(`http://localhost:8080/v1/instances/${instanceId}/auth`, { method: "POST", headers: { "Content-Type": "application/json", "x-auth-token": token, "x-user-id": "123456", "x-request-id": "0000123456" }, mode: "cors" }) .then(resp => resp.text()) .then(data => { let json = JSON.parse(data); if (json["error_code"] === "0") { let deviceToken = json["data"]["device_token"]; let deviceId = json["data"]["resource"]["device_id"]; let signalUrl = json["data"]["resource"]["rtc"]["ice_signaling"]["signaling_url"] return {deviceToken, deviceId, signalUrl}; } else { // 请求异常 alert(json["error_msg"]); } }); }
- 部署本地Proxy。
- 初始化SDK并启动串流。
在完成步骤1~3后,已经准备好初始化所需的元素mountPoint(串流云机所需的挂载点,String或HTMLElement类型,且tagName必须是div)。
如果对串流有其他要求(比如音量,视频宽高等),可以按照JS SDK参考准备其他参数。
- 初始化player。
// 创建时,配置选项 let player = CloudappClient.createCloudappPlayer({ mountPoint: 'container' muted: false, volume: 0.5, frameAspect: '16:9', noOperationThreshold: 120, countdownTime: 60, sessionKeepingTime: 60, enableClipboard: true }); if (!player) { console.log('Player创建失败!'); } - 启动串流。
在获取到实例串流信息,并初始化player之后,即可开始串流。
- 检查浏览器支持情况。
player.isBrowserSupport()
目前暂不支持UC浏览器和MacOS/iPad原生浏览器。如果浏览器无法使用,建议使用Chrome浏览器。
- 设置所需回调。
// 举例1:设置状态变化回调 player.on("onStateChange", function (event) { console.log("onStateChange:" + event.state); }); // 举例2:设置用户动作回调 player.on("onUserAction", function (event) { console.log("useraction:" + event.action); }); - 启动串流。
// 获取输入框参数 const {deviceToken, deviceId, signalUrl} = await refreshDeviceToken(); // 启动串流 player.initMediaStream({ boxid: deviceId, // 设备ID token: deviceToken, // 鉴权Token uuid: 'test-user-id', // 串流用户ID signaling_url: signalUrl // 信令服务器地址 });在浏览器中单击“初始化player并启动串流”,可以看到云机画面。图2 初始化player并启动串流
- 退出串流。
player.closeMediaStream();
在浏览器中单击“退出串流”,关闭云机画面。
图3 退出串流
- 检查浏览器支持情况。
- 初始化player。
(可选)设置音频、视频
音频
- 设置音量
<input id="volume" type="text" name="name"/><br> <button onclick="setVolume()">设置音量</button><br>
实现音量设置函数。
function setVolume() { let volume = Number(document.getElementById("volume").value); player.setVolume(volume); } - 设置静音
<div> <label for="">是</label> <input type="radio" name="muted" style="width:20px;height:20px;" value="1" checked="true"/> <label for="">否</label> <input type="radio" name="muted" style="width:20px;height:20px;" value="0"/> </div> <button onclick="setMuted()">设置player静音开关</button><br>实现静音设置函数。
function setMuted() { let radios = document.getElementsByName('muted'); let value; for(let i = 0; i < radios.length; i++) { if(radios[i].checked) { value = radios[i].value; break; } } if (value === '1') { player.setMuted(true); } else { player.setMuted(false); } }
视频
- 设置视频清晰度
绘制视频质量输入框,设置视频质量按钮。输入值参考SDK中的setVideoQuality()函数说明。
<input id="quality" type="text" name="name"/><br> <button onclick="setQuality()">设置画质</button><br>
实现视频清晰度设置按钮。
function setQuality() { let quality = Number(document.getElementById("quality").value); player.setVideoQuality(quality); } - 全屏及退出播放
<button onclick="setMediaFullScreen()">设置player全屏播放</button><br> <button onclick="setMediaExitFullScreen()">退出player全屏播放</button><br>
实现全屏及退出播放按钮对应功能函数。
function setMediaFullScreen() { player.setMediaFullScreen(); } function setMediaExitFullScreen() { player.setMediaExitFullScreen(); }
