
# 开发指导
该开发场景主要面向普通云手机客户端开发者，旨在帮助开发者通过集成和调用KooPhone端SDK的部分基本功能，初步理解和实现一个面向云手机用户的功能完善的H5客户端。
本章节所有开发示例均为展示基本功能的实现，仅供参考。
1. 准备样式。
   
   绘制H5串流页面时，首先需要配置页面样式。主要分如下三类：
   1. 配置基础样式。 准备html、body基础样式，支持全屏播放。
      ```
      <head>
          <style>
              html, body {
                  width: 100%;   /* 宽度占满整个浏览器窗口 */
                  height: 100%;  /* 高度占满整个浏览器窗口 */
              }
              body {
                  margin: 0;     /* 清除body默认的外边距（避免页面出现空白边） */
              }
          </style>
      </head>
      ```
      
   
   2. 配置容器布局样式。 请参考以下配置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>
      ```
      
   
   
   
   3. 配置其他样式。 请参考以下配置，进行输入框和按钮的配置。
      ```
      <head>
          <style>
              input {
                  width: 500px;       /* 输入框宽度500px（留边距） */
                  height: 30px;       /* 输入框高度30px */
                  margin-top: 20px;   /* 顶部外边距20px（和上方元素拉开距离） */
                  margin-bottom: 20px;/* 底部外边距20px（和下方元素拉开距离） */
              }
              button {
                  padding: 5px;  /* 按钮内部的内边距5px（让按钮文字和边框之间有空白，不会贴边） */
              }
          </style>
      </head>
      ```
      
   
   
   
   
2. 准备Html页面元素。 
   1. 准备输入框及按钮元素。 请参考以下布局，设计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>
      ```
      
   
   2. 引入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();
        ```
        
       
   
   
   
   
3. 自动获取鉴权信息。
   
   在[快速开始](https://support.huaweicloud.com/devg-koophone/kp-00_00014.html)中提供了手动获取鉴权信息进入串流的方案。这里给出自动获取鉴权并进入串流的使用指导，方便用户直接进行工程化部署。
   1. 部署本地Proxy。
      1. 在串流时，由于浏览器跨域问题，无法在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}`);
         });
         ```
         
      
      2. 准备请求串流鉴权的URL地址和请求IAM Token的URL地址。
         - 请求串流鉴权的URL地址，在[API Explorer](https://console.huaweicloud.com/apiexplorer/#/openapi/KooPhone/doc?api=GetDeviceToken)中获取。举例：如果是北京4区域，地址为https://koophone.myhuaweicloud.com。
         
         - 请求IAM Token的URL地址，在[构造IAM请求](https://support.huaweicloud.com/api-iam/iam_02_0008.html)中获取。举例：如果是北京4区域，地址为https://iam.cn-north-4.myhuaweicloud.com
          
      
      3. 启动本地代理。
         1. 安装依赖包npm install http-proxy。
         
         2. 在命令行窗口（如CMD/Terminal控制台等）中，通过cd命令切换至*proxy.js* 文件所在目录。执行以下命令切换 ：
            ```
            node proxy.js https://koophone.***.com https://iam.***.com
            ```
            上述命令中的**\*\*\*** 需替换为[3.a.ii]中获取的串流鉴权的URL地址和请求IAM Token的URL地址。
            执行结束后，如果启动成功，如[图1]所示。
            图1启动本地代理   
            ![](https://support.huaweicloud.com/devg-koophone/zh-cn_image_0000002531746000.png "点击放大") 
          
       
   
   2. 获取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');
          });
      }
      ```
      
   
   3. 获取串流鉴权信息。 获取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"]);
              }
          });
      }
      ```
      
   
   
   
   
4. 初始化SDK并启动串流。 
   在完成步骤[1]\~[3]后，已经准备好初始化所需的元素mountPoint（串流云机所需的挂载点，String或HTMLElement类型，且tagName必须是div）。
   如果对串流有其他要求（比如音量，视频宽高等），可以按照JS SDK参考准备其他参数。
   1. 初始化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创建失败！');
      }
      ```
      
   
   2. 启动串流。 在获取到实例串流信息，并初始化player之后，即可开始串流。
      1. 检查浏览器支持情况。
         ```
         player.isBrowserSupport()
         ```
         目前暂不支持UC浏览器和MacOS/iPad原生浏览器。如果浏览器无法使用，建议使用Chrome浏览器。
         
      
      2. 设置所需回调。
         ```
         // 举例1：设置状态变化回调
         player.on("onStateChange", function (event) {
             console.log("onStateChange:" + event.state);
         });
         // 举例2：设置用户动作回调
         player.on("onUserAction", function (event) {
             console.log("useraction：" + event.action);
         });
         ```
         
      
      3. 启动串流。
         ```
         // 获取输入框参数
         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并启动串流   
         ![](https://support.huaweicloud.com/devg-koophone/zh-cn_image_0000002531905942.png "点击放大") 
      
      4. 退出串流。
         ```
         player.closeMediaStream();
         ```
         在浏览器中单击"退出串流"，关闭云机画面。
         图3退出串流   
         ![](https://support.huaweicloud.com/devg-koophone/zh-cn_image_0000002562665905.png "点击放大") 
       
   
   
   
   
#### （可选）设置音频、视频
**音频**
- 设置音量 绘制音量输入框，设置音量按钮。
  ```
  <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);
  }
  ```
  
- 设置静音 绘制静音选择框，选择后单击**设置player静音开关**按钮。
  ```
  <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();
  }
  ```
  
 
